UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

102 lines (80 loc) 3.41 kB
import Quaternion from "../../../../core/geom/Quaternion.js"; import Vector3 from "../../../../core/geom/Vector3.js"; import { fabrik3d_solve_primitive } from "./fabrik3d_solve_primitive.js"; const scratch_positions_count = 64; const scratch_positions = new Float32Array(scratch_positions_count * 3); const scratch_v3_0 = new Vector3(); const scratch_v3_1 = new Vector3(); const scratch_quat = new Quaternion(); /** * Note that bones are defined as a combination of Joint ({@link Transform}) and bone length. Joint defines the origin of the bone. * Last joint's position is expected to be bone_length distance from the target if target is reachable and be oriented towards that target ({@link Transform.lookAt}) * @param {Transform[]} joints Will be updated as a result of the solve * @param {number[]} lengths distance to next bone * @param {Vector3} origin where should the first joint be placed at * @param {Vector3} target where should the last joint be placed at * @param {number} [max_iterations] More steps will lead to higher accuracy, but at the cost of computation. Generally solution will be reached in just a few iteration so this is just a ceiling * @param {number} [distance_tolerance] Minimum squared distance to be achieved to the target, used to terminate earlier before maximum number of iterations is reached */ export function fabrik_solve( joints, lengths, origin, target, max_iterations = 4, distance_tolerance = 1e-7 ) { const joint_count = joints.length; let positions; if (joint_count <= scratch_positions_count) { positions = scratch_positions; } else { positions = new Float32Array(joint_count * 3); } for (let i = 0; i < joint_count; i++) { const offset = i * 3; const joint = joints[i]; joint.position.toArray(positions, offset); } fabrik3d_solve_primitive( joint_count, positions, lengths, origin.x, origin.y, origin.z, target.x, target.y, target.z, max_iterations, distance_tolerance ); // apply rotation for (let i = 1; i < joint_count; i++) { const current_joint = joints[i]; const previous_joint = joints[i - 1]; const i3 = i * 3; const previous_x = positions[i3 - 3]; const previous_y = positions[i3 - 2]; const previous_z = positions[i3 - 1]; const current_x = positions[i3]; const current_y = positions[i3 + 1]; const current_z = positions[i3 + 2]; scratch_v3_0.copy(current_joint.position); scratch_v3_0.sub(previous_joint.position); if (scratch_v3_0.lengthSqr() === 0) { continue; } scratch_v3_1.set( current_x - previous_x, current_y - previous_y, current_z - previous_z, ); if (scratch_v3_1.lengthSqr() === 0) { continue; } scratch_v3_0.normalize(); scratch_v3_1.normalize(); scratch_quat.fromUnitVectors(scratch_v3_0, scratch_v3_1); previous_joint.rotation.multiplyQuaternions(scratch_quat, previous_joint.rotation); } // apply positions for (let i = 0; i < joint_count; i++) { joints[i].position.fromArray(positions, i * 3); } }