Rotating a bone by moving tail only in an Axis

I have a bone in my scene, and want to rotate the bone so that it results in a different tail position, but by moving the tail only towards Z+ direction.


So, let’s say that there’s the bone head in this position.


And the original rotation for the bone makes the tail located in the certain position in the space.


I want to get the rotation that would put the tail in the new position (red) that keeps the angle and the original length?(I don’t know how to call it, but the red point in the XY plane is on the line that connects the (0,0) and the original (x,y).

This is my try so far, but it just moves the bone to some weird location. Any help would be appreciated!

        function rotateBoneByDeltaZ(boneName: string, deltaZ: number) {
            let children: Map<string, string> = new Map();

            const targetBone = skeletonHelper.bones.find(bone => bone.name === boneName);
            const childBone = skeletonHelper.bones.find(bone => bone.name === children.get(boneName));

            const head = targetBone.position;
            const tail = childBone.position;

            let world_head = new THREE.Vector3();
            targetBone.getWorldPosition(world_head);

            let world_tail = new THREE.Vector3();
            childBone.getWorldPosition(world_tail);
            world_tail = new Vector3().subVectors(world_tail, world_head);
            const new_world_tail = adjustZ(world_tail, deltaZ);

            const targetAngle = calculateTargetAngle(world_tail, new_world_tail);
            rotateBoneByGlobalAxis(boneName, targetAngle, 'Z');
        }

        function adjustZ(vector: Vector3, deltaZ: number): Vector3 {
            const x = vector.x;
            const y = vector.y;
            const r = vector.length();
            const theta = Math.atan2(y, x);
            const z1 = vector.z + deltaZ;

            if (z1 ** 2 > r ** 2) {
                return vector;
            }

            const x1 = Math.sqrt(r ** 2 - z1 ** 2) * Math.cos(theta);
            const y1 = Math.sqrt(r ** 2 - z1 ** 2) * Math.sin(theta);

            // // print old size and new size
            console.log(r, Math.sqrt(x1 ** 2 + y1 ** 2 + z1 ** 2));

            return new Vector3(x1, y1, z1);
        }

        function calculateTargetAngle(tail: Vector3, newTail: Vector3): number {
            const length = tail.length();
            const v0 = tail.clone();
            const v1 = newTail.clone();

            const theta0 = Math.asin(v0.z / length);
            const theta1 = Math.asin(v1.z / length);

            return theta1 - theta0;
        }

        function rotateBoneByGlobalAxis(boneName: string, angle: number, axis: 'X' | 'Y' | 'Z') {
            const targetBone = skeletonHelper.bones.find(bone => bone.name === boneName);
            rotateAroundParallelAxis(targetBone, angle, axis);
        }

        function rotateAroundParallelAxis(object: Object3D, angle: number, axis: string) {
            // Store the original position
            const originalPosition = object.position.clone();

            // Step 1: Translate the object such that the point coincides with the origin
            object.position.sub(originalPosition);

            // Step 2: Rotate the object around the global Z axis
            switch (axis) {
                case 'X':
                    object.rotation.x += angle;
                    break;
                case 'Y':
                    object.rotation.y += angle;
                    break;
                case 'Z':
                    object.rotation.z += angle;
                    break;
            }

            // Step 3: Translate the object back to its original position
            object.position.add(originalPosition);
        }

The long axis of the bone isn’t always consistent. I’ve had some modellers that export Y as the long axis and some that use Z.

You can still make it work, but you might have to transform the vector you use to target.

Another trick is to use bone.lookAt instead of manually computing the transform…

So, say you have the point in worldspace you want to point the bone at…

you can do something like…


bone.worldToLocal( boneLookTarget  )

to convert the look target into bone space…


bone.lookAt( boneLookTarget )

to point the bone at the target.

If your bones have a different axis set as up, you might need to rotate the target point first…

by something like…


bone.worldToLocal( boneLookTarget  )
// swap the y with z (for weird bones that sometimes come from FBX models)
boneLookTarget.set( boneLookTarget.x , boneLookTarget.z, boneLookTarget.y );
bone.lookAt( boneLookTarget );

THEN… you might also hit problems with the twist of the bone… this can be controlled by changing the bone.up vector to the local axis that you want to be “up”

like
bone.up = new THREE.Vector3( 0, 0, 1); //Change the up vector of this bone at init time.

It’s always fiddly to get these to work right, but those are some of the main tools I’ve used in the past to do it, and this is off the top of my head, so YMMV :slight_smile: good luck!

I didn’t expect I would get an answer this quick!
Thank you, you’ve made my day :blush:
I’ll try it and let you know

1 Like