Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions js/IKSolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,21 @@ BaseSolver.JOINTTYPES = { OMNI: 0, HINGE: 1, BALLSOCKET: 2 }; // omni is just th


class FABRIKSolver extends BaseSolver {
poleTarget = null;
poleAngle = 0;
poleRotationMatrix = new THREE.Matrix4();
tmpVectors = [
new THREE.Vector3(),
new THREE.Vector3(),
new THREE.Vector3(),
new THREE.Vector3(),
]
setPoleTarget(obj){
this.poleTarget = obj;
}
setPoleAngle(angle){
this.poleAngle = angle;
}
update ( ){
let bones = this.skeleton.bones;
let positions = this._positions;
Expand Down Expand Up @@ -455,6 +470,58 @@ class FABRIKSolver extends BaseSolver {
targetPositions[ chain[0] ].copy( currTargetPoint );


//pole Target
if(this.poleTarget && chain.length < 4){
const polePos = this.tmpVectors[0];
polePos.setFromMatrixPosition(this.poleTarget.matrixWorld);

const midPoint = this.tmpVectors[1]
.addVectors(targetPositions[ chain[0] ], targetPositions[ chain[2] ])
.multiplyScalar(0.5);

const chainAxis = this.tmpVectors[2]
.subVectors(targetPositions[ chain[2] ], targetPositions[ chain[0] ])
.normalize();

// Calculate pole direction with stability checks
const poleDirection = this.tmpVectors[3]
.subVectors(polePos, midPoint);

// Check if pole position is too close to the chain axis (causes instability)
const poleDistanceFromAxis = poleDirection.length();
if (poleDistanceFromAxis < 0.001) {
// Pole is too close to chain axis, skip pole constraint
continue;
}

// Project pole direction onto plane perpendicular to chain axis
const axialComponent = poleDirection.dot(chainAxis);
poleDirection.addScaledVector(chainAxis, -axialComponent);

// Check if projected pole direction is too small (near-perpendicular case)
if (poleDirection.lengthSq() < 0.000001) {
// Use a default perpendicular direction to avoid singularity
poleDirection.set(0, 1, 0);
if (Math.abs(chainAxis.dot(poleDirection)) > 0.9) {
poleDirection.set(1, 0, 0);
}
poleDirection.crossVectors(poleDirection, chainAxis);
}

poleDirection.normalize();

const projectedLength = targetPositions[ chain[1] ].distanceTo(midPoint);

if (this.poleAngle) {
const rotationMatrix = this.poleRotationMatrix.makeRotationAxis(chainAxis, THREE.MathUtils.degToRad(this.poleAngle));
poleDirection.applyMatrix4(rotationMatrix).normalize();
}

targetPositions[ chain[1] ].copy(
midPoint.clone().add(poleDirection.multiplyScalar(projectedLength))
);
}


// compute rotations
// from parent to effector
Expand Down
28 changes: 24 additions & 4 deletions js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ class App {
this.models = [eva, lowPoly, Michelle];
this.currentModel = this.models[1];

this.gui = new GUI(this);
this.gui = new GUI(this);

}

init() {
Expand Down Expand Up @@ -119,9 +119,11 @@ class App {

this.initLights();

this.poleTarget = new THREE.Mesh(new THREE.IcosahedronGeometry(0.1));
this.scene.add(this.poleTarget)
//load models and add them to the scene
this.initCharacter();

}

initLights() {
Expand Down Expand Up @@ -206,6 +208,13 @@ class App {
}
}

character.skeleton.bones[character.bonesIdxs["LeftForeArm"]].getWorldPosition(this.poleTarget.position)
this.poleTarget.position.z -= 0.2
this.poleTarget.position.y -= 0.1
this.poleTarget.userData = {poleAngle: 0}
character.poleTarget = this.poleTarget;


//Add character to the scene and put it visible if it's the current model selected
character.model.name = "Character_" + character.name;
character.model.visible = this.currentModel.name == character.name;
Expand Down Expand Up @@ -321,6 +330,13 @@ class App {
transfControl.size = 0.6;
transfControl.name = "control"+ chain.name;
this.scene.add( transfControl );


let transfControl2 = new TransformControls( this.camera, this.renderer.domElement );
transfControl2.addEventListener( 'dragging-changed', ( event ) => { this.controls.enabled = ! event.value; } );
transfControl2.attach( this.poleTarget );
transfControl2.size = 0.6;
this.scene.add( transfControl2 );
}
target.name = 'IKTarget' + chain.name;

Expand Down Expand Up @@ -355,7 +371,11 @@ class App {
}
character.chains[ikChain.name] = ikChain;

if ( !character.FABRIKSolver ){ character.FABRIKSolver = new FABRIKSolver( character.skeleton ); }
if ( !character.FABRIKSolver ){
character.FABRIKSolver = new FABRIKSolver( character.skeleton );
character.FABRIKSolver.setPoleTarget(character.poleTarget);
character.FABRIKSolver.setPoleAngle(character.poleTarget.userData.poleAngle)
}
character.FABRIKSolver.createChain(ikChain.bones, ikChain.constraints, ikChain.target, ikChain.name);

if ( !character.CCDIKSolver ){ character.CCDIKSolver = new CCDIKSolver( character.skeleton ); }
Expand Down
4 changes: 4 additions & 0 deletions js/gui.js
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,10 @@ class GUI {
widgets.on_refresh();
}});

widgets.addRange("pole Angle", 0, (value, event) => {
this.editor.currentModel.FABRIKSolver.poleAngle = value;
}, { min: -360, max: 360, step: 1, width: "100%" });

if(newChain.target) {
widgets.sameLine(2);
widgets.addText("Target", newChain.target == true ? null : newChain.target, null, { width: '80%', disabled: true});
Expand Down