import { Object3D, Color, Matrix4, AxesHelper, ArrowHelper, Mesh, ConeBufferGeometry, MeshLambertMaterial, Vector3 } from 'three';
/**
* Mesh for representing an IKJoint.
* @private
* @extends {THREE.Object3d}
*/
class BoneHelper extends Object3D {
/**
* @param {number} height
* @param {number?} boneSize
* @param {number?} axesSize
*/
constructor(height, boneSize, axesSize) {
super();
// If our bone has 0 height (like an end effector),
// use a dummy Object3D instead, otherwise the ConeBufferGeometry
// will fall back to its default and not use 0 height.
if (height !== 0) {
const geo = new ConeBufferGeometry(boneSize, height, 4);
geo.applyMatrix4(new Matrix4().makeRotationAxis(new Vector3(1, 0, 0), Math.PI/2));
this.boneMesh = new Mesh(geo, new MeshLambertMaterial({
color: 0xff0000,
wireframe: true,
depthTest: false,
depthWrite: false,
}));
} else {
this.boneMesh = new Object3D();
}
// Offset the bone so that its rotation point is at the base of the bone
this.boneMesh.position.z = height / 2;
this.add(this.boneMesh);
this.axesHelper = new AxesHelper(axesSize);
this.add(this.axesHelper);
}
}
/**
* Class for visualizing an IK system.
* @extends {THREE.Object3d}
*/
class IKHelper extends Object3D {
/**
* Creates a visualization for an IK.
*
* @param {IK} ik
* @param {Object} config
* @param {THREE.Color} [config.color]
* @param {boolean} [config.showBones]
* @param {boolean} [config.showAxes]
* @param {boolean} [config.wireframe]
* @param {number} [config.axesSize]
* @param {number} [config.boneSize]
*/
constructor(ik, { color, showBones, boneSize, showAxes, axesSize, wireframe } = {}) {
super();
boneSize = boneSize || 0.1;
axesSize = axesSize || 0.2;
if (!ik.isIK) {
throw new Error('IKHelper must receive an IK instance.');
}
this.ik = ik;
this._meshes = new Map();
for (let rootChain of this.ik.chains) {
const chainsToMeshify = [rootChain];
while (chainsToMeshify.length) {
const chain = chainsToMeshify.shift();
for (let i = 0; i < chain.joints.length; i++) {
const joint = chain.joints[i];
const nextJoint = chain.joints[i+1];
const distance = nextJoint ? nextJoint.distance : 0;
// If a sub base, don't make another bone
if (chain.base === joint && chain !== rootChain) {
continue;
}
const mesh = new BoneHelper(distance, boneSize, axesSize);
mesh.matrixAutoUpdate = false;
this._meshes.set(joint, mesh);
this.add(mesh);
}
for (let subChains of chain.chains.values()) {
for (let subChain of subChains) {
chainsToMeshify.push(subChain);
}
}
}
}
/**
* Whether this IKHelper's bones are visible or not.
*
* @name IKHelper#showBones
* @type boolean
* @default true
*/
this.showBones = showBones !== undefined ? showBones : true;
/**
* Whether this IKHelper's axes are visible or not.
*
* @name IKHelper#showAxes
* @type boolean
* @default true
*/
this.showAxes = showAxes !== undefined ? showAxes : true;
/**
* Whether this IKHelper should be rendered as wireframes or not.
*
* @name IKHelper#wireframe
* @type boolean
* @default true
*/
this.wireframe = wireframe !== undefined ? wireframe : true;
/**
* The color of this IKHelper's bones.
*
* @name IKHelper#color
* @type THREE.Color
* @default new THREE.Color(0xff0077)
*/
this.color = color || new Color(0xff0077);
}
get showBones() { return this._showBones; }
set showBones(showBones) {
if (showBones === this._showBones) {
return;
}
for (let [joint, mesh] of this._meshes) {
if (showBones) {
mesh.add(mesh.boneMesh);
} else {
mesh.remove(mesh.boneMesh);
}
}
this._showBones = showBones;
}
get showAxes() { return this._showAxes; }
set showAxes(showAxes) {
if (showAxes === this._showAxes) {
return;
}
for (let [joint, mesh] of this._meshes) {
if (showAxes) {
mesh.add(mesh.axesHelper);
} else {
mesh.remove(mesh.axesHelper);
}
}
this._showAxes = showAxes;
}
get wireframe() { return this._wireframe; }
set wireframe(wireframe) {
if (wireframe === this._wireframe) {
return;
}
for (let [joint, mesh] of this._meshes) {
if (mesh.boneMesh.material) {
mesh.boneMesh.material.wireframe = wireframe;
}
}
this._wireframe = wireframe;
}
get color() { return this._color; }
set color(color) {
if (this._color && this._color.equals(color)) {
return;
}
color = (color && color.isColor) ? color : new Color(color);
for (let [joint, mesh] of this._meshes) {
if (mesh.boneMesh.material) {
mesh.boneMesh.material.color = color;
}
}
this._color = color;
}
updateMatrixWorld(force) {
for (let [joint, mesh] of this._meshes) {
mesh.matrix.copy(joint.bone.matrixWorld);
}
super.updateMatrixWorld(force);
}
}
export default IKHelper;