@zephyr3d/scene 0.7.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/dist/animation/animation.js +0 -18
  2. package/dist/animation/animation.js.map +1 -1
  3. package/dist/animation/animationset.js +2 -2
  4. package/dist/animation/animationset.js.map +1 -1
  5. package/dist/animation/ik/ccd_solver.js +372 -0
  6. package/dist/animation/ik/ccd_solver.js.map +1 -0
  7. package/dist/animation/ik/fabrik_solver.js +399 -0
  8. package/dist/animation/ik/fabrik_solver.js.map +1 -0
  9. package/dist/animation/ik/ik_angle_constraint.js +181 -0
  10. package/dist/animation/ik/ik_angle_constraint.js.map +1 -0
  11. package/dist/animation/ik/ik_chain.js +163 -0
  12. package/dist/animation/ik/ik_chain.js.map +1 -0
  13. package/dist/animation/ik/ik_chain_builder.js +68 -0
  14. package/dist/animation/ik/ik_chain_builder.js.map +1 -0
  15. package/dist/animation/ik/ik_constraint.js +25 -0
  16. package/dist/animation/ik/ik_constraint.js.map +1 -0
  17. package/dist/animation/ik/ik_pole_constraint.js +123 -0
  18. package/dist/animation/ik/ik_pole_constraint.js.map +1 -0
  19. package/dist/animation/ik/ik_solver.js +48 -0
  20. package/dist/animation/ik/ik_solver.js.map +1 -0
  21. package/dist/animation/ik/ik_track.js +96 -0
  22. package/dist/animation/ik/ik_track.js.map +1 -0
  23. package/dist/animation/ik/ik_utils.js +270 -0
  24. package/dist/animation/ik/ik_utils.js.map +1 -0
  25. package/dist/animation/ik/two_bone_ik_solver.js +332 -0
  26. package/dist/animation/ik/two_bone_ik_solver.js.map +1 -0
  27. package/dist/animation/ik_modifier.js +61 -0
  28. package/dist/animation/ik_modifier.js.map +1 -0
  29. package/dist/animation/ik_postprocessor.js +71 -0
  30. package/dist/animation/ik_postprocessor.js.map +1 -0
  31. package/dist/animation/manual_transform_processor.js +156 -0
  32. package/dist/animation/manual_transform_processor.js.map +1 -0
  33. package/dist/animation/skeleton.js +55 -51
  34. package/dist/animation/skeleton.js.map +1 -1
  35. package/dist/animation/skeleton_modifier.js +38 -0
  36. package/dist/animation/skeleton_modifier.js.map +1 -0
  37. package/dist/animation/skeleton_postprocessor.js +50 -0
  38. package/dist/animation/skeleton_postprocessor.js.map +1 -0
  39. package/dist/animation/spring/multi_chain_spring_system.js +503 -0
  40. package/dist/animation/spring/multi_chain_spring_system.js.map +1 -0
  41. package/dist/animation/spring/spring_chain.js +103 -0
  42. package/dist/animation/spring/spring_chain.js.map +1 -0
  43. package/dist/animation/spring/spring_collider.js +247 -0
  44. package/dist/animation/spring/spring_collider.js.map +1 -0
  45. package/dist/animation/spring/spring_constraint.js +21 -0
  46. package/dist/animation/spring/spring_constraint.js.map +1 -0
  47. package/dist/animation/spring/spring_particle.js +20 -0
  48. package/dist/animation/spring/spring_particle.js.map +1 -0
  49. package/dist/animation/spring/spring_system.js +526 -0
  50. package/dist/animation/spring/spring_system.js.map +1 -0
  51. package/dist/animation/spring_modifier.js +45 -0
  52. package/dist/animation/spring_modifier.js.map +1 -0
  53. package/dist/animation/spring_postprocessor.js +54 -0
  54. package/dist/animation/spring_postprocessor.js.map +1 -0
  55. package/dist/app/screen.js +1 -1
  56. package/dist/app/screen.js.map +1 -1
  57. package/dist/asset/model.js +8 -4
  58. package/dist/asset/model.js.map +1 -1
  59. package/dist/camera/perspectivecamera.js +3 -3
  60. package/dist/camera/perspectivecamera.js.map +1 -1
  61. package/dist/index.d.ts +1385 -4
  62. package/dist/index.js +16 -0
  63. package/dist/index.js.map +1 -1
  64. package/dist/material/mixins/lightmodel/blinnphong.js +66 -4
  65. package/dist/material/mixins/lightmodel/blinnphong.js.map +1 -1
  66. package/dist/material/shader/helper.js +5 -3
  67. package/dist/material/shader/helper.js.map +1 -1
  68. package/dist/scene/mesh.js +15 -23
  69. package/dist/scene/mesh.js.map +1 -1
  70. package/dist/utility/serialization/scene/animation.js +21 -20
  71. package/dist/utility/serialization/scene/animation.js.map +1 -1
  72. package/dist/utility/serialization/scene/material.js +62 -0
  73. package/dist/utility/serialization/scene/material.js.map +1 -1
  74. package/package.json +2 -2
@@ -0,0 +1,181 @@
1
+ import { Vector3 } from '@zephyr3d/base';
2
+ import { IKConstraint } from './ik_constraint.js';
3
+
4
+ /**
5
+ * Angle constraint for IK joints.
6
+ *
7
+ * Limits the angle between the bone from parent to this joint
8
+ * and the bone from this joint to child.
9
+ *
10
+ * @remarks
11
+ * This is useful for joints like elbows and knees that have
12
+ * limited range of motion.
13
+ *
14
+ * @public
15
+ */ class IKAngleConstraint extends IKConstraint {
16
+ /** Minimum angle in degrees */ _minAngle;
17
+ /** Maximum angle in degrees */ _maxAngle;
18
+ /**
19
+ * Create an angle constraint.
20
+ *
21
+ * @param jointIndex - Index of the joint in the chain
22
+ * @param minAngle - Minimum angle in degrees (0 = straight)
23
+ * @param maxAngle - Maximum angle in degrees (180 = fully bent)
24
+ */ constructor(jointIndex, minAngle, maxAngle){
25
+ super(jointIndex);
26
+ this._minAngle = Math.max(0, minAngle);
27
+ this._maxAngle = Math.min(180, maxAngle);
28
+ if (this._minAngle > this._maxAngle) {
29
+ throw new Error('IKAngleConstraint: minAngle must be <= maxAngle');
30
+ }
31
+ }
32
+ /**
33
+ * Get the minimum angle in degrees.
34
+ */ get minAngle() {
35
+ return this._minAngle;
36
+ }
37
+ /**
38
+ * Set the minimum angle in degrees.
39
+ */ set minAngle(value) {
40
+ this._minAngle = Math.max(0, Math.min(value, this._maxAngle));
41
+ }
42
+ /**
43
+ * Get the maximum angle in degrees.
44
+ */ get maxAngle() {
45
+ return this._maxAngle;
46
+ }
47
+ /**
48
+ * Set the maximum angle in degrees.
49
+ */ set maxAngle(value) {
50
+ this._maxAngle = Math.min(180, Math.max(value, this._minAngle));
51
+ }
52
+ /**
53
+ * Apply the angle constraint to limit joint bending.
54
+ */ apply(joints) {
55
+ const index = this._jointIndex;
56
+ // For end effector (last joint), we constrain the angle between the last two bones
57
+ // For other joints, we constrain the angle at the joint itself
58
+ if (index === joints.length - 1) {
59
+ // End effector: constrain angle between (parent-1 -> parent) and (parent -> this)
60
+ if (index < 2) {
61
+ return; // Need at least 3 joints total
62
+ }
63
+ this._applyEndEffectorConstraint(joints, index);
64
+ } else {
65
+ // Regular joint: constrain angle between (parent -> this) and (this -> child)
66
+ if (index < 1) {
67
+ return; // Root joint cannot be constrained this way
68
+ }
69
+ this._applyRegularConstraint(joints, index);
70
+ }
71
+ }
72
+ /**
73
+ * Apply constraint to end effector joint.
74
+ */ _applyEndEffectorConstraint(joints, index) {
75
+ const grandParentJoint = joints[index - 2];
76
+ const parentJoint = joints[index - 1];
77
+ const currentJoint = joints[index];
78
+ // Calculate bone vectors
79
+ const incomingBone = Vector3.sub(parentJoint.position, grandParentJoint.position, new Vector3());
80
+ const outgoingBone = Vector3.sub(currentJoint.position, parentJoint.position, new Vector3());
81
+ const incomingLen = incomingBone.magnitude;
82
+ const outgoingLen = outgoingBone.magnitude;
83
+ if (incomingLen < 0.000001 || outgoingLen < 0.000001) {
84
+ return;
85
+ }
86
+ // Normalize
87
+ const incomingNorm = Vector3.scale(incomingBone, 1 / incomingLen, new Vector3());
88
+ const outgoingNorm = Vector3.scale(outgoingBone, 1 / outgoingLen, new Vector3());
89
+ // Calculate current angle
90
+ const dot = Vector3.dot(incomingNorm, outgoingNorm);
91
+ const currentAngleDeg = Math.acos(Math.max(-1, Math.min(1, dot))) * (180 / Math.PI);
92
+ // Check if constraint is violated
93
+ let targetAngleDeg = currentAngleDeg;
94
+ if (currentAngleDeg < this._minAngle) {
95
+ targetAngleDeg = this._minAngle;
96
+ } else if (currentAngleDeg > this._maxAngle) {
97
+ targetAngleDeg = this._maxAngle;
98
+ } else {
99
+ return;
100
+ }
101
+ // Apply the same constraint logic as regular joints
102
+ this._adjustJointAngle(incomingNorm, outgoingNorm, currentAngleDeg, targetAngleDeg, parentJoint, currentJoint);
103
+ }
104
+ /**
105
+ * Apply constraint to regular joint.
106
+ */ _applyRegularConstraint(joints, index) {
107
+ const parentJoint = joints[index - 1];
108
+ const currentJoint = joints[index];
109
+ const childJoint = joints[index + 1];
110
+ // Calculate bone vectors: incoming bone (parent->current) and outgoing bone (current->child)
111
+ const incomingBone = Vector3.sub(currentJoint.position, parentJoint.position, new Vector3());
112
+ const outgoingBone = Vector3.sub(childJoint.position, currentJoint.position, new Vector3());
113
+ const incomingLen = incomingBone.magnitude;
114
+ const outgoingLen = outgoingBone.magnitude;
115
+ if (incomingLen < 0.000001 || outgoingLen < 0.000001) {
116
+ return;
117
+ }
118
+ // Normalize
119
+ const incomingNorm = Vector3.scale(incomingBone, 1 / incomingLen, new Vector3());
120
+ const outgoingNorm = Vector3.scale(outgoingBone, 1 / outgoingLen, new Vector3());
121
+ // Calculate current angle between incoming and outgoing bones (interior angle)
122
+ const dot = Vector3.dot(incomingNorm, outgoingNorm);
123
+ const currentAngleDeg = Math.acos(Math.max(-1, Math.min(1, dot))) * (180 / Math.PI);
124
+ // Check if constraint is violated
125
+ let targetAngleDeg = currentAngleDeg;
126
+ if (currentAngleDeg < this._minAngle) {
127
+ targetAngleDeg = this._minAngle;
128
+ } else if (currentAngleDeg > this._maxAngle) {
129
+ targetAngleDeg = this._maxAngle;
130
+ } else {
131
+ return;
132
+ }
133
+ // Apply the constraint
134
+ this._adjustJointAngle(incomingNorm, outgoingNorm, currentAngleDeg, targetAngleDeg, currentJoint, childJoint);
135
+ }
136
+ /**
137
+ * Adjust joint angle by rotating the outgoing bone.
138
+ */ _adjustJointAngle(incomingNorm, outgoingNorm, currentAngleDeg, targetAngleDeg, pivotJoint, targetJoint) {
139
+ // Add a dead zone to prevent oscillation
140
+ const angleDifference = Math.abs(targetAngleDeg - currentAngleDeg);
141
+ const isHardConstraint = Math.abs(this._maxAngle - this._minAngle) < 0.01;
142
+ const deadZone = isHardConstraint ? 0.2 : 0.1;
143
+ if (angleDifference < deadZone) {
144
+ return; // Too close to target, avoid over-correction
145
+ }
146
+ // Calculate rotation axis (perpendicular to both bone vectors)
147
+ const axis = Vector3.cross(incomingNorm, outgoingNorm, new Vector3());
148
+ const axisLen = axis.magnitude;
149
+ // Use a smaller threshold to allow constraint to work at small angles
150
+ if (axisLen < 0.0001) {
151
+ return; // Vectors are too close to parallel or anti-parallel
152
+ }
153
+ axis.scaleBy(1 / axisLen);
154
+ // Calculate the angle we need to rotate
155
+ const targetAngleRad = targetAngleDeg * (Math.PI / 180);
156
+ const currentAngleRad = currentAngleDeg * (Math.PI / 180);
157
+ let deltaAngle = targetAngleRad - currentAngleRad;
158
+ // Apply full correction without damping to ensure constraints are enforced
159
+ // This is necessary because FABRIK will try to undo the constraint in the next iteration
160
+ // So we need strong constraint enforcement
161
+ // Rotate the outgoing bone vector around the axis by deltaAngle
162
+ const cos = Math.cos(deltaAngle);
163
+ const sin = Math.sin(deltaAngle);
164
+ const oneMinusCos = 1 - cos;
165
+ // Rodrigues' rotation formula
166
+ const rotated = new Vector3();
167
+ rotated.x = outgoingNorm.x * (cos + axis.x * axis.x * oneMinusCos) + outgoingNorm.y * (axis.x * axis.y * oneMinusCos - axis.z * sin) + outgoingNorm.z * (axis.x * axis.z * oneMinusCos + axis.y * sin);
168
+ rotated.y = outgoingNorm.x * (axis.y * axis.x * oneMinusCos + axis.z * sin) + outgoingNorm.y * (cos + axis.y * axis.y * oneMinusCos) + outgoingNorm.z * (axis.y * axis.z * oneMinusCos - axis.x * sin);
169
+ rotated.z = outgoingNorm.x * (axis.z * axis.x * oneMinusCos - axis.y * sin) + outgoingNorm.y * (axis.z * axis.y * oneMinusCos + axis.x * sin) + outgoingNorm.z * (cos + axis.z * axis.z * oneMinusCos);
170
+ // Update target joint position using the rotated bone vector
171
+ const boneLength = pivotJoint.boneLength;
172
+ rotated.scaleBy(boneLength);
173
+ Vector3.add(pivotJoint.position, rotated, targetJoint.position);
174
+ // Note: We do NOT recursively update subsequent joints here.
175
+ // Instead, we let FABRIK algorithm handle the propagation in subsequent iterations.
176
+ // This ensures that each joint's constraint is properly applied.
177
+ }
178
+ }
179
+
180
+ export { IKAngleConstraint };
181
+ //# sourceMappingURL=ik_angle_constraint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ik_angle_constraint.js","sources":["../../../src/animation/ik/ik_angle_constraint.ts"],"sourcesContent":["import { Vector3 } from '@zephyr3d/base';\r\nimport { IKConstraint } from './ik_constraint';\r\nimport type { IKJoint } from './ik_joint';\r\n\r\n/**\r\n * Angle constraint for IK joints.\r\n *\r\n * Limits the angle between the bone from parent to this joint\r\n * and the bone from this joint to child.\r\n *\r\n * @remarks\r\n * This is useful for joints like elbows and knees that have\r\n * limited range of motion.\r\n *\r\n * @public\r\n */\r\nexport class IKAngleConstraint extends IKConstraint {\r\n /** Minimum angle in degrees */\r\n private _minAngle: number;\r\n /** Maximum angle in degrees */\r\n private _maxAngle: number;\r\n\r\n /**\r\n * Create an angle constraint.\r\n *\r\n * @param jointIndex - Index of the joint in the chain\r\n * @param minAngle - Minimum angle in degrees (0 = straight)\r\n * @param maxAngle - Maximum angle in degrees (180 = fully bent)\r\n */\r\n constructor(jointIndex: number, minAngle: number, maxAngle: number) {\r\n super(jointIndex);\r\n this._minAngle = Math.max(0, minAngle);\r\n this._maxAngle = Math.min(180, maxAngle);\r\n\r\n if (this._minAngle > this._maxAngle) {\r\n throw new Error('IKAngleConstraint: minAngle must be <= maxAngle');\r\n }\r\n }\r\n\r\n /**\r\n * Get the minimum angle in degrees.\r\n */\r\n get minAngle(): number {\r\n return this._minAngle;\r\n }\r\n\r\n /**\r\n * Set the minimum angle in degrees.\r\n */\r\n set minAngle(value: number) {\r\n this._minAngle = Math.max(0, Math.min(value, this._maxAngle));\r\n }\r\n\r\n /**\r\n * Get the maximum angle in degrees.\r\n */\r\n get maxAngle(): number {\r\n return this._maxAngle;\r\n }\r\n\r\n /**\r\n * Set the maximum angle in degrees.\r\n */\r\n set maxAngle(value: number) {\r\n this._maxAngle = Math.min(180, Math.max(value, this._minAngle));\r\n }\r\n\r\n /**\r\n * Apply the angle constraint to limit joint bending.\r\n */\r\n apply(joints: IKJoint[]): void {\r\n const index = this._jointIndex;\r\n\r\n // For end effector (last joint), we constrain the angle between the last two bones\r\n // For other joints, we constrain the angle at the joint itself\r\n if (index === joints.length - 1) {\r\n // End effector: constrain angle between (parent-1 -> parent) and (parent -> this)\r\n if (index < 2) {\r\n return; // Need at least 3 joints total\r\n }\r\n this._applyEndEffectorConstraint(joints, index);\r\n } else {\r\n // Regular joint: constrain angle between (parent -> this) and (this -> child)\r\n if (index < 1) {\r\n return; // Root joint cannot be constrained this way\r\n }\r\n this._applyRegularConstraint(joints, index);\r\n }\r\n }\r\n\r\n /**\r\n * Apply constraint to end effector joint.\r\n */\r\n private _applyEndEffectorConstraint(joints: IKJoint[], index: number): void {\r\n const grandParentJoint = joints[index - 2];\r\n const parentJoint = joints[index - 1];\r\n const currentJoint = joints[index];\r\n\r\n // Calculate bone vectors\r\n const incomingBone = Vector3.sub(parentJoint.position, grandParentJoint.position, new Vector3());\r\n const outgoingBone = Vector3.sub(currentJoint.position, parentJoint.position, new Vector3());\r\n\r\n const incomingLen = incomingBone.magnitude;\r\n const outgoingLen = outgoingBone.magnitude;\r\n\r\n if (incomingLen < 0.000001 || outgoingLen < 0.000001) {\r\n return;\r\n }\r\n\r\n // Normalize\r\n const incomingNorm = Vector3.scale(incomingBone, 1 / incomingLen, new Vector3());\r\n const outgoingNorm = Vector3.scale(outgoingBone, 1 / outgoingLen, new Vector3());\r\n\r\n // Calculate current angle\r\n const dot = Vector3.dot(incomingNorm, outgoingNorm);\r\n const currentAngleDeg = Math.acos(Math.max(-1, Math.min(1, dot))) * (180 / Math.PI);\r\n\r\n // Check if constraint is violated\r\n let targetAngleDeg = currentAngleDeg;\r\n if (currentAngleDeg < this._minAngle) {\r\n targetAngleDeg = this._minAngle;\r\n } else if (currentAngleDeg > this._maxAngle) {\r\n targetAngleDeg = this._maxAngle;\r\n } else {\r\n return;\r\n }\r\n\r\n // Apply the same constraint logic as regular joints\r\n this._adjustJointAngle(\r\n incomingNorm,\r\n outgoingNorm,\r\n currentAngleDeg,\r\n targetAngleDeg,\r\n parentJoint,\r\n currentJoint\r\n );\r\n }\r\n\r\n /**\r\n * Apply constraint to regular joint.\r\n */\r\n private _applyRegularConstraint(joints: IKJoint[], index: number): void {\r\n const parentJoint = joints[index - 1];\r\n const currentJoint = joints[index];\r\n const childJoint = joints[index + 1];\r\n\r\n // Calculate bone vectors: incoming bone (parent->current) and outgoing bone (current->child)\r\n const incomingBone = Vector3.sub(currentJoint.position, parentJoint.position, new Vector3());\r\n const outgoingBone = Vector3.sub(childJoint.position, currentJoint.position, new Vector3());\r\n\r\n const incomingLen = incomingBone.magnitude;\r\n const outgoingLen = outgoingBone.magnitude;\r\n\r\n if (incomingLen < 0.000001 || outgoingLen < 0.000001) {\r\n return;\r\n }\r\n\r\n // Normalize\r\n const incomingNorm = Vector3.scale(incomingBone, 1 / incomingLen, new Vector3());\r\n const outgoingNorm = Vector3.scale(outgoingBone, 1 / outgoingLen, new Vector3());\r\n\r\n // Calculate current angle between incoming and outgoing bones (interior angle)\r\n const dot = Vector3.dot(incomingNorm, outgoingNorm);\r\n const currentAngleDeg = Math.acos(Math.max(-1, Math.min(1, dot))) * (180 / Math.PI);\r\n\r\n // Check if constraint is violated\r\n let targetAngleDeg = currentAngleDeg;\r\n if (currentAngleDeg < this._minAngle) {\r\n targetAngleDeg = this._minAngle;\r\n } else if (currentAngleDeg > this._maxAngle) {\r\n targetAngleDeg = this._maxAngle;\r\n } else {\r\n return;\r\n }\r\n\r\n // Apply the constraint\r\n this._adjustJointAngle(\r\n incomingNorm,\r\n outgoingNorm,\r\n currentAngleDeg,\r\n targetAngleDeg,\r\n currentJoint,\r\n childJoint\r\n );\r\n }\r\n\r\n /**\r\n * Adjust joint angle by rotating the outgoing bone.\r\n */\r\n private _adjustJointAngle(\r\n incomingNorm: Vector3,\r\n outgoingNorm: Vector3,\r\n currentAngleDeg: number,\r\n targetAngleDeg: number,\r\n pivotJoint: IKJoint,\r\n targetJoint: IKJoint\r\n ): void {\r\n // Add a dead zone to prevent oscillation\r\n const angleDifference = Math.abs(targetAngleDeg - currentAngleDeg);\r\n const isHardConstraint = Math.abs(this._maxAngle - this._minAngle) < 0.01;\r\n const deadZone = isHardConstraint ? 0.2 : 0.1;\r\n\r\n if (angleDifference < deadZone) {\r\n return; // Too close to target, avoid over-correction\r\n }\r\n\r\n // Calculate rotation axis (perpendicular to both bone vectors)\r\n const axis = Vector3.cross(incomingNorm, outgoingNorm, new Vector3());\r\n const axisLen = axis.magnitude;\r\n\r\n // Use a smaller threshold to allow constraint to work at small angles\r\n if (axisLen < 0.0001) {\r\n return; // Vectors are too close to parallel or anti-parallel\r\n }\r\n\r\n axis.scaleBy(1 / axisLen);\r\n\r\n // Calculate the angle we need to rotate\r\n const targetAngleRad = targetAngleDeg * (Math.PI / 180);\r\n const currentAngleRad = currentAngleDeg * (Math.PI / 180);\r\n let deltaAngle = targetAngleRad - currentAngleRad;\r\n\r\n // Apply full correction without damping to ensure constraints are enforced\r\n // This is necessary because FABRIK will try to undo the constraint in the next iteration\r\n // So we need strong constraint enforcement\r\n\r\n // Rotate the outgoing bone vector around the axis by deltaAngle\r\n const cos = Math.cos(deltaAngle);\r\n const sin = Math.sin(deltaAngle);\r\n const oneMinusCos = 1 - cos;\r\n\r\n // Rodrigues' rotation formula\r\n const rotated = new Vector3();\r\n rotated.x =\r\n outgoingNorm.x * (cos + axis.x * axis.x * oneMinusCos) +\r\n outgoingNorm.y * (axis.x * axis.y * oneMinusCos - axis.z * sin) +\r\n outgoingNorm.z * (axis.x * axis.z * oneMinusCos + axis.y * sin);\r\n\r\n rotated.y =\r\n outgoingNorm.x * (axis.y * axis.x * oneMinusCos + axis.z * sin) +\r\n outgoingNorm.y * (cos + axis.y * axis.y * oneMinusCos) +\r\n outgoingNorm.z * (axis.y * axis.z * oneMinusCos - axis.x * sin);\r\n\r\n rotated.z =\r\n outgoingNorm.x * (axis.z * axis.x * oneMinusCos - axis.y * sin) +\r\n outgoingNorm.y * (axis.z * axis.y * oneMinusCos + axis.x * sin) +\r\n outgoingNorm.z * (cos + axis.z * axis.z * oneMinusCos);\r\n\r\n // Update target joint position using the rotated bone vector\r\n const boneLength = pivotJoint.boneLength;\r\n rotated.scaleBy(boneLength);\r\n Vector3.add(pivotJoint.position, rotated, targetJoint.position);\r\n\r\n // Note: We do NOT recursively update subsequent joints here.\r\n // Instead, we let FABRIK algorithm handle the propagation in subsequent iterations.\r\n // This ensures that each joint's constraint is properly applied.\r\n }\r\n}\r\n"],"names":["IKAngleConstraint","IKConstraint","jointIndex","minAngle","maxAngle","_minAngle","Math","max","_maxAngle","min","Error","value","apply","joints","index","_jointIndex","length","_applyEndEffectorConstraint","_applyRegularConstraint","grandParentJoint","parentJoint","currentJoint","incomingBone","Vector3","sub","position","outgoingBone","incomingLen","magnitude","outgoingLen","incomingNorm","scale","outgoingNorm","dot","currentAngleDeg","acos","PI","targetAngleDeg","_adjustJointAngle","childJoint","pivotJoint","targetJoint","angleDifference","abs","isHardConstraint","deadZone","axis","cross","axisLen","scaleBy","targetAngleRad","currentAngleRad","deltaAngle","cos","sin","oneMinusCos","rotated","x","y","z","boneLength","add"],"mappings":"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"}
@@ -0,0 +1,163 @@
1
+ import { Vector3, Quaternion } from '@zephyr3d/base';
2
+ import { IKUtils } from './ik_utils.js';
3
+
4
+ /**
5
+ * Represents a chain of joints for IK solving.
6
+ *
7
+ * @public
8
+ */ class IKChain {
9
+ /** Array of joints in the chain (root to end effector) */ _joints;
10
+ /** Total length of the chain */ _totalLength;
11
+ /** Constraints applied to joints */ _constraints;
12
+ /**
13
+ * Create an IK chain from an array of scene nodes.
14
+ *
15
+ * @param nodes - Array of scene nodes from root to end effector
16
+ */ constructor(nodes){
17
+ if (nodes.length < 2) {
18
+ throw new Error('IK chain must have at least 2 joints');
19
+ }
20
+ this._joints = [];
21
+ this._totalLength = 0;
22
+ this._constraints = [];
23
+ // Create joints from nodes
24
+ for(let i = 0; i < nodes.length; i++){
25
+ const node = nodes[i];
26
+ const position = new Vector3();
27
+ const rotation = new Quaternion();
28
+ node.worldMatrix.decompose(null, rotation, position);
29
+ let boneLength = 0;
30
+ if (i < nodes.length - 1) {
31
+ const nextPosition = new Vector3();
32
+ nodes[i + 1].worldMatrix.decompose(null, null, nextPosition);
33
+ boneLength = IKUtils.calculateBoneLength(position, nextPosition);
34
+ this._totalLength += boneLength;
35
+ }
36
+ this._joints.push({
37
+ node,
38
+ position: position.clone(),
39
+ originalPosition: position.clone(),
40
+ rotation: rotation.clone(),
41
+ originalRotation: rotation.clone(),
42
+ boneLength
43
+ });
44
+ }
45
+ }
46
+ /**
47
+ * Create an IK chain by traversing from a start node to an end node.
48
+ *
49
+ * @remarks
50
+ * This method walks up the parent chain from the end node until it
51
+ * reaches the start node, building the joint chain in the process.
52
+ *
53
+ * @param startNode - The root joint of the chain
54
+ * @param endNode - The end effector joint
55
+ * @returns The created IK chain
56
+ */ static fromNodeHierarchy(startNode, endNode) {
57
+ const nodes = [];
58
+ let current = endNode;
59
+ // Walk up the hierarchy from end to start
60
+ while(current){
61
+ nodes.unshift(current); // Add to front
62
+ if (current === startNode) {
63
+ break;
64
+ }
65
+ current = current.parent;
66
+ }
67
+ if (nodes[0] !== startNode) {
68
+ throw new Error('End node is not a descendant of start node');
69
+ }
70
+ if (nodes.length < 2) {
71
+ throw new Error('IK chain must have at least 2 joints');
72
+ }
73
+ return new IKChain(nodes);
74
+ }
75
+ /**
76
+ * Get all joints in the chain.
77
+ */ get joints() {
78
+ return this._joints;
79
+ }
80
+ /**
81
+ * Get the root joint (first joint in the chain).
82
+ */ get root() {
83
+ return this._joints[0];
84
+ }
85
+ /**
86
+ * Get the end effector (last joint in the chain).
87
+ */ get endEffector() {
88
+ return this._joints[this._joints.length - 1];
89
+ }
90
+ /**
91
+ * Get the total length of the chain.
92
+ */ get totalLength() {
93
+ return this._totalLength;
94
+ }
95
+ /**
96
+ * Get the number of joints in the chain.
97
+ */ get length() {
98
+ return this._joints.length;
99
+ }
100
+ /**
101
+ * Update joint positions from their scene nodes.
102
+ */ updateFromNodes() {
103
+ for (const joint of this._joints){
104
+ joint.node.worldMatrix.decompose(null, joint.rotation, joint.position);
105
+ }
106
+ }
107
+ /**
108
+ * Store current positions as original positions.
109
+ */ storeOriginalPositions() {
110
+ for (const joint of this._joints){
111
+ joint.originalPosition.set(joint.position);
112
+ joint.originalRotation.set(joint.rotation);
113
+ }
114
+ }
115
+ /**
116
+ * Restore joints to their original positions.
117
+ */ restoreOriginalPositions() {
118
+ for (const joint of this._joints){
119
+ joint.position.set(joint.originalPosition);
120
+ joint.rotation.set(joint.originalRotation);
121
+ }
122
+ }
123
+ /**
124
+ * Add a constraint to a specific joint.
125
+ *
126
+ * @param constraint - The constraint to add
127
+ */ addConstraint(constraint) {
128
+ this._constraints.push(constraint);
129
+ }
130
+ /**
131
+ * Remove a constraint from the chain.
132
+ *
133
+ * @param constraint - The constraint to remove
134
+ * @returns True if the constraint was found and removed
135
+ */ removeConstraint(constraint) {
136
+ const index = this._constraints.indexOf(constraint);
137
+ if (index >= 0) {
138
+ this._constraints.splice(index, 1);
139
+ return true;
140
+ }
141
+ return false;
142
+ }
143
+ /**
144
+ * Clear all constraints from the chain.
145
+ */ clearConstraints() {
146
+ this._constraints = [];
147
+ }
148
+ /**
149
+ * Get all constraints in the chain.
150
+ */ get constraints() {
151
+ return this._constraints;
152
+ }
153
+ /**
154
+ * Apply all constraints to the joint chain.
155
+ */ applyConstraints() {
156
+ for (const constraint of this._constraints){
157
+ constraint.apply(this._joints);
158
+ }
159
+ }
160
+ }
161
+
162
+ export { IKChain };
163
+ //# sourceMappingURL=ik_chain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ik_chain.js","sources":["../../../src/animation/ik/ik_chain.ts"],"sourcesContent":["import { Vector3, Quaternion } from '@zephyr3d/base';\r\nimport type { IKJoint } from './ik_joint';\r\nimport type { SceneNode } from '../../scene/scene_node';\r\nimport type { IKConstraint } from './ik_constraint';\r\nimport { IKUtils } from './ik_utils';\r\n\r\n/**\r\n * Represents a chain of joints for IK solving.\r\n *\r\n * @public\r\n */\r\nexport class IKChain {\r\n /** Array of joints in the chain (root to end effector) */\r\n private _joints: IKJoint[];\r\n /** Total length of the chain */\r\n private _totalLength: number;\r\n /** Constraints applied to joints */\r\n private _constraints: IKConstraint[];\r\n\r\n /**\r\n * Create an IK chain from an array of scene nodes.\r\n *\r\n * @param nodes - Array of scene nodes from root to end effector\r\n */\r\n constructor(nodes: SceneNode[]) {\r\n if (nodes.length < 2) {\r\n throw new Error('IK chain must have at least 2 joints');\r\n }\r\n\r\n this._joints = [];\r\n this._totalLength = 0;\r\n this._constraints = [];\r\n\r\n // Create joints from nodes\r\n for (let i = 0; i < nodes.length; i++) {\r\n const node = nodes[i];\r\n const position = new Vector3();\r\n const rotation = new Quaternion();\r\n node.worldMatrix.decompose(null, rotation, position);\r\n\r\n let boneLength = 0;\r\n if (i < nodes.length - 1) {\r\n const nextPosition = new Vector3();\r\n nodes[i + 1].worldMatrix.decompose(null, null, nextPosition);\r\n boneLength = IKUtils.calculateBoneLength(position, nextPosition);\r\n this._totalLength += boneLength;\r\n }\r\n\r\n this._joints.push({\r\n node,\r\n position: position.clone(),\r\n originalPosition: position.clone(),\r\n rotation: rotation.clone(),\r\n originalRotation: rotation.clone(),\r\n boneLength\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Create an IK chain by traversing from a start node to an end node.\r\n *\r\n * @remarks\r\n * This method walks up the parent chain from the end node until it\r\n * reaches the start node, building the joint chain in the process.\r\n *\r\n * @param startNode - The root joint of the chain\r\n * @param endNode - The end effector joint\r\n * @returns The created IK chain\r\n */\r\n static fromNodeHierarchy(startNode: SceneNode, endNode: SceneNode): IKChain {\r\n const nodes: SceneNode[] = [];\r\n let current: SceneNode | null = endNode;\r\n\r\n // Walk up the hierarchy from end to start\r\n while (current) {\r\n nodes.unshift(current); // Add to front\r\n\r\n if (current === startNode) {\r\n break;\r\n }\r\n\r\n current = current.parent as SceneNode | null;\r\n }\r\n\r\n if (nodes[0] !== startNode) {\r\n throw new Error('End node is not a descendant of start node');\r\n }\r\n\r\n if (nodes.length < 2) {\r\n throw new Error('IK chain must have at least 2 joints');\r\n }\r\n\r\n return new IKChain(nodes);\r\n }\r\n\r\n /**\r\n * Get all joints in the chain.\r\n */\r\n get joints(): IKJoint[] {\r\n return this._joints;\r\n }\r\n\r\n /**\r\n * Get the root joint (first joint in the chain).\r\n */\r\n get root(): IKJoint {\r\n return this._joints[0];\r\n }\r\n\r\n /**\r\n * Get the end effector (last joint in the chain).\r\n */\r\n get endEffector(): IKJoint {\r\n return this._joints[this._joints.length - 1];\r\n }\r\n\r\n /**\r\n * Get the total length of the chain.\r\n */\r\n get totalLength(): number {\r\n return this._totalLength;\r\n }\r\n\r\n /**\r\n * Get the number of joints in the chain.\r\n */\r\n get length(): number {\r\n return this._joints.length;\r\n }\r\n\r\n /**\r\n * Update joint positions from their scene nodes.\r\n */\r\n updateFromNodes(): void {\r\n for (const joint of this._joints) {\r\n joint.node.worldMatrix.decompose(null, joint.rotation, joint.position);\r\n }\r\n }\r\n\r\n /**\r\n * Store current positions as original positions.\r\n */\r\n storeOriginalPositions(): void {\r\n for (const joint of this._joints) {\r\n joint.originalPosition.set(joint.position);\r\n joint.originalRotation.set(joint.rotation);\r\n }\r\n }\r\n\r\n /**\r\n * Restore joints to their original positions.\r\n */\r\n restoreOriginalPositions(): void {\r\n for (const joint of this._joints) {\r\n joint.position.set(joint.originalPosition);\r\n joint.rotation.set(joint.originalRotation);\r\n }\r\n }\r\n\r\n /**\r\n * Add a constraint to a specific joint.\r\n *\r\n * @param constraint - The constraint to add\r\n */\r\n addConstraint(constraint: IKConstraint): void {\r\n this._constraints.push(constraint);\r\n }\r\n\r\n /**\r\n * Remove a constraint from the chain.\r\n *\r\n * @param constraint - The constraint to remove\r\n * @returns True if the constraint was found and removed\r\n */\r\n removeConstraint(constraint: IKConstraint): boolean {\r\n const index = this._constraints.indexOf(constraint);\r\n if (index >= 0) {\r\n this._constraints.splice(index, 1);\r\n return true;\r\n }\r\n return false;\r\n }\r\n\r\n /**\r\n * Clear all constraints from the chain.\r\n */\r\n clearConstraints(): void {\r\n this._constraints = [];\r\n }\r\n\r\n /**\r\n * Get all constraints in the chain.\r\n */\r\n get constraints(): readonly IKConstraint[] {\r\n return this._constraints;\r\n }\r\n\r\n /**\r\n * Apply all constraints to the joint chain.\r\n */\r\n applyConstraints(): void {\r\n for (const constraint of this._constraints) {\r\n constraint.apply(this._joints);\r\n }\r\n }\r\n}\r\n"],"names":["IKChain","nodes","length","Error","_joints","_totalLength","_constraints","i","node","position","Vector3","rotation","Quaternion","worldMatrix","decompose","boneLength","nextPosition","IKUtils","calculateBoneLength","push","clone","originalPosition","originalRotation","fromNodeHierarchy","startNode","endNode","current","unshift","parent","joints","root","endEffector","totalLength","updateFromNodes","joint","storeOriginalPositions","set","restoreOriginalPositions","addConstraint","constraint","removeConstraint","index","indexOf","splice","clearConstraints","constraints","applyConstraints","apply"],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ import { IKChain } from './ik_chain.js';
2
+
3
+ /**
4
+ * Helper class for building IK chains from various sources.
5
+ *
6
+ * @public
7
+ */ class IKChainBuilder {
8
+ /**
9
+ * Create an IK chain from an array of scene nodes.
10
+ *
11
+ * @param nodes - Array of scene nodes from root to end effector
12
+ * @returns The created IK chain
13
+ */ static fromNodes(nodes) {
14
+ return new IKChain(nodes);
15
+ }
16
+ /**
17
+ * Create an IK chain from a skeleton using joint indices.
18
+ *
19
+ * @param skeleton - The skeleton containing the joints
20
+ * @param jointIndices - Array of joint indices from root to end effector
21
+ * @returns The created IK chain
22
+ */ static fromSkeleton(skeleton, jointIndices) {
23
+ if (jointIndices.length < 2) {
24
+ throw new Error('IK chain must have at least 2 joints');
25
+ }
26
+ const joints = skeleton.joints;
27
+ const nodes = [];
28
+ for (const index of jointIndices){
29
+ if (index < 0 || index >= joints.length) {
30
+ throw new Error(`Invalid joint index: ${index}`);
31
+ }
32
+ nodes.push(joints[index]);
33
+ }
34
+ return new IKChain(nodes);
35
+ }
36
+ /**
37
+ * Create an IK chain by traversing from a start node to an end node.
38
+ *
39
+ * @remarks
40
+ * This method walks up the parent chain from the end node until it
41
+ * reaches the start node, building the joint chain in the process.
42
+ *
43
+ * @param startNode - The root joint of the chain
44
+ * @param endNode - The end effector joint
45
+ * @returns The created IK chain
46
+ */ static fromNodeHierarchy(startNode, endNode) {
47
+ const nodes = [];
48
+ let current = endNode;
49
+ // Walk up the hierarchy from end to start
50
+ while(current){
51
+ nodes.unshift(current); // Add to front
52
+ if (current === startNode) {
53
+ break;
54
+ }
55
+ current = current.parent;
56
+ }
57
+ if (nodes[0] !== startNode) {
58
+ throw new Error('End node is not a descendant of start node');
59
+ }
60
+ if (nodes.length < 2) {
61
+ throw new Error('IK chain must have at least 2 joints');
62
+ }
63
+ return new IKChain(nodes);
64
+ }
65
+ }
66
+
67
+ export { IKChainBuilder };
68
+ //# sourceMappingURL=ik_chain_builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ik_chain_builder.js","sources":["../../../src/animation/ik/ik_chain_builder.ts"],"sourcesContent":["import { IKChain } from './ik_chain';\r\nimport type { SceneNode } from '../../scene/scene_node';\r\nimport type { Skeleton } from '../skeleton';\r\n\r\n/**\r\n * Helper class for building IK chains from various sources.\r\n *\r\n * @public\r\n */\r\nexport class IKChainBuilder {\r\n /**\r\n * Create an IK chain from an array of scene nodes.\r\n *\r\n * @param nodes - Array of scene nodes from root to end effector\r\n * @returns The created IK chain\r\n */\r\n static fromNodes(nodes: SceneNode[]): IKChain {\r\n return new IKChain(nodes);\r\n }\r\n\r\n /**\r\n * Create an IK chain from a skeleton using joint indices.\r\n *\r\n * @param skeleton - The skeleton containing the joints\r\n * @param jointIndices - Array of joint indices from root to end effector\r\n * @returns The created IK chain\r\n */\r\n static fromSkeleton(skeleton: Skeleton, jointIndices: number[]): IKChain {\r\n if (jointIndices.length < 2) {\r\n throw new Error('IK chain must have at least 2 joints');\r\n }\r\n\r\n const joints = skeleton.joints;\r\n const nodes: SceneNode[] = [];\r\n\r\n for (const index of jointIndices) {\r\n if (index < 0 || index >= joints.length) {\r\n throw new Error(`Invalid joint index: ${index}`);\r\n }\r\n nodes.push(joints[index]);\r\n }\r\n\r\n return new IKChain(nodes);\r\n }\r\n\r\n /**\r\n * Create an IK chain by traversing from a start node to an end node.\r\n *\r\n * @remarks\r\n * This method walks up the parent chain from the end node until it\r\n * reaches the start node, building the joint chain in the process.\r\n *\r\n * @param startNode - The root joint of the chain\r\n * @param endNode - The end effector joint\r\n * @returns The created IK chain\r\n */\r\n static fromNodeHierarchy(startNode: SceneNode, endNode: SceneNode): IKChain {\r\n const nodes: SceneNode[] = [];\r\n let current: SceneNode | null = endNode;\r\n\r\n // Walk up the hierarchy from end to start\r\n while (current) {\r\n nodes.unshift(current); // Add to front\r\n\r\n if (current === startNode) {\r\n break;\r\n }\r\n\r\n current = current.parent as SceneNode | null;\r\n }\r\n\r\n if (nodes[0] !== startNode) {\r\n throw new Error('End node is not a descendant of start node');\r\n }\r\n\r\n if (nodes.length < 2) {\r\n throw new Error('IK chain must have at least 2 joints');\r\n }\r\n\r\n return new IKChain(nodes);\r\n }\r\n}\r\n"],"names":["IKChainBuilder","fromNodes","nodes","IKChain","fromSkeleton","skeleton","jointIndices","length","Error","joints","index","push","fromNodeHierarchy","startNode","endNode","current","unshift","parent"],"mappings":";;AAIA;;;;AAIC,IACM,MAAMA,cAAAA,CAAAA;AACX;;;;;MAMA,OAAOC,SAAUC,CAAAA,KAAkB,EAAW;AAC5C,QAAA,OAAO,IAAIC,OAAQD,CAAAA,KAAAA,CAAAA;AACrB;AAEA;;;;;;AAMC,MACD,OAAOE,YAAAA,CAAaC,QAAkB,EAAEC,YAAsB,EAAW;QACvE,IAAIA,YAAAA,CAAaC,MAAM,GAAG,CAAG,EAAA;AAC3B,YAAA,MAAM,IAAIC,KAAM,CAAA,sCAAA,CAAA;AAClB;QAEA,MAAMC,MAAAA,GAASJ,SAASI,MAAM;AAC9B,QAAA,MAAMP,QAAqB,EAAE;QAE7B,KAAK,MAAMQ,SAASJ,YAAc,CAAA;AAChC,YAAA,IAAII,KAAQ,GAAA,CAAA,IAAKA,KAASD,IAAAA,MAAAA,CAAOF,MAAM,EAAE;AACvC,gBAAA,MAAM,IAAIC,KAAAA,CAAM,CAAC,qBAAqB,EAAEE,KAAO,CAAA,CAAA,CAAA;AACjD;AACAR,YAAAA,KAAAA,CAAMS,IAAI,CAACF,MAAM,CAACC,KAAM,CAAA,CAAA;AAC1B;AAEA,QAAA,OAAO,IAAIP,OAAQD,CAAAA,KAAAA,CAAAA;AACrB;AAEA;;;;;;;;;;AAUC,MACD,OAAOU,iBAAAA,CAAkBC,SAAoB,EAAEC,OAAkB,EAAW;AAC1E,QAAA,MAAMZ,QAAqB,EAAE;AAC7B,QAAA,IAAIa,OAA4BD,GAAAA,OAAAA;;AAGhC,QAAA,MAAOC,OAAS,CAAA;YACdb,KAAMc,CAAAA,OAAO,CAACD,OAAAA,CAAAA,CAAAA;AAEd,YAAA,IAAIA,YAAYF,SAAW,EAAA;AACzB,gBAAA;AACF;AAEAE,YAAAA,OAAAA,GAAUA,QAAQE,MAAM;AAC1B;AAEA,QAAA,IAAIf,KAAK,CAAC,CAAE,CAAA,KAAKW,SAAW,EAAA;AAC1B,YAAA,MAAM,IAAIL,KAAM,CAAA,4CAAA,CAAA;AAClB;QAEA,IAAIN,KAAAA,CAAMK,MAAM,GAAG,CAAG,EAAA;AACpB,YAAA,MAAM,IAAIC,KAAM,CAAA,sCAAA,CAAA;AAClB;AAEA,QAAA,OAAO,IAAIL,OAAQD,CAAAA,KAAAA,CAAAA;AACrB;AACF;;;;"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Base class for IK constraints.
3
+ *
4
+ * Constraints are applied after each FABRIK iteration to enforce
5
+ * joint limitations (angle limits, pole vectors, etc.).
6
+ *
7
+ * @public
8
+ */ class IKConstraint {
9
+ /** The joint index this constraint applies to */ _jointIndex;
10
+ /**
11
+ * Create an IK constraint.
12
+ *
13
+ * @param jointIndex - Index of the joint in the chain (0 = root)
14
+ */ constructor(jointIndex){
15
+ this._jointIndex = jointIndex;
16
+ }
17
+ /**
18
+ * Get the joint index this constraint applies to.
19
+ */ get jointIndex() {
20
+ return this._jointIndex;
21
+ }
22
+ }
23
+
24
+ export { IKConstraint };
25
+ //# sourceMappingURL=ik_constraint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ik_constraint.js","sources":["../../../src/animation/ik/ik_constraint.ts"],"sourcesContent":["import type { IKJoint } from './ik_joint';\r\n\r\n/**\r\n * Base class for IK constraints.\r\n *\r\n * Constraints are applied after each FABRIK iteration to enforce\r\n * joint limitations (angle limits, pole vectors, etc.).\r\n *\r\n * @public\r\n */\r\nexport abstract class IKConstraint {\r\n /** The joint index this constraint applies to */\r\n protected _jointIndex: number;\r\n\r\n /**\r\n * Create an IK constraint.\r\n *\r\n * @param jointIndex - Index of the joint in the chain (0 = root)\r\n */\r\n constructor(jointIndex: number) {\r\n this._jointIndex = jointIndex;\r\n }\r\n\r\n /**\r\n * Get the joint index this constraint applies to.\r\n */\r\n get jointIndex(): number {\r\n return this._jointIndex;\r\n }\r\n\r\n /**\r\n * Apply the constraint to the joint chain.\r\n *\r\n * This method modifies joint positions to satisfy the constraint.\r\n *\r\n * @param joints - Array of all joints in the chain\r\n */\r\n abstract apply(joints: IKJoint[]): void;\r\n}\r\n\r\n/**\r\n * Twist constraint configuration for a joint.\r\n *\r\n * @public\r\n */\r\nexport interface TwistConstraint {\r\n /** Minimum twist angle in radians */\r\n minTwist: number;\r\n /** Maximum twist angle in radians */\r\n maxTwist: number;\r\n /** Smoothing factor [0-1], 0 = no smoothing, 1 = full smoothing */\r\n smoothFactor: number;\r\n}\r\n"],"names":["IKConstraint","jointIndex","_jointIndex"],"mappings":"AAEA;;;;;;;AAOC,IACM,MAAeA,YAAAA,CAAAA;sDAEpB,WAA8B;AAE9B;;;;MAKA,WAAA,CAAYC,UAAkB,CAAE;QAC9B,IAAI,CAACC,WAAW,GAAGD,UAAAA;AACrB;AAEA;;AAEC,MACD,IAAIA,UAAqB,GAAA;QACvB,OAAO,IAAI,CAACC,WAAW;AACzB;AAUF;;;;"}
@@ -0,0 +1,123 @@
1
+ import { Vector3 } from '@zephyr3d/base';
2
+ import { IKConstraint } from './ik_constraint.js';
3
+
4
+ /**
5
+ * Pole vector constraint for IK chains.
6
+ *
7
+ * Controls the twist/rotation of a joint chain by ensuring the middle joint
8
+ * bends toward a specified pole vector position.
9
+ *
10
+ * @remarks
11
+ * This is commonly used for arms and legs to control which direction
12
+ * the elbow or knee points. For example, ensuring an elbow points
13
+ * backward or a knee points forward.
14
+ *
15
+ * The pole vector defines a plane, and the middle joint is constrained
16
+ * to lie on this plane.
17
+ *
18
+ * @public
19
+ */ class IKPoleVectorConstraint extends IKConstraint {
20
+ /** The pole vector position in world space */ _poleVector;
21
+ /** Weight of the constraint (0 = no effect, 1 = full effect) */ _weight;
22
+ /**
23
+ * Create a pole vector constraint.
24
+ *
25
+ * @param jointIndex - Index of the middle joint to constrain (typically elbow or knee)
26
+ * @param poleVector - Position in world space that the joint should point toward
27
+ * @param weight - Constraint weight (0-1, default: 1)
28
+ */ constructor(jointIndex, poleVector, weight = 1){
29
+ super(jointIndex);
30
+ this._poleVector = poleVector.clone();
31
+ this._weight = Math.max(0, Math.min(1, weight));
32
+ }
33
+ /**
34
+ * Get the pole vector position.
35
+ */ get poleVector() {
36
+ return this._poleVector;
37
+ }
38
+ /**
39
+ * Set the pole vector position.
40
+ */ set poleVector(value) {
41
+ this._poleVector.set(value);
42
+ }
43
+ /**
44
+ * Get the constraint weight.
45
+ */ get weight() {
46
+ return this._weight;
47
+ }
48
+ /**
49
+ * Set the constraint weight (0-1).
50
+ */ set weight(value) {
51
+ this._weight = Math.max(0, Math.min(1, value));
52
+ }
53
+ /**
54
+ * Apply the pole vector constraint.
55
+ *
56
+ * This adjusts the middle joint position to align with the pole vector
57
+ * while maintaining bone lengths.
58
+ */ apply(joints) {
59
+ const index = this._jointIndex;
60
+ // Need at least 3 joints (parent, this, child)
61
+ if (index < 1 || index >= joints.length - 1) {
62
+ return;
63
+ }
64
+ if (this._weight < 0.001) {
65
+ return; // No effect
66
+ }
67
+ const parentJoint = joints[index - 1];
68
+ const currentJoint = joints[index];
69
+ const childJoint = joints[index + 1];
70
+ // Calculate the plane defined by parent, child, and pole vector
71
+ const parentPos = parentJoint.position;
72
+ const childPos = childJoint.position;
73
+ const currentPos = currentJoint.position;
74
+ // Vector from parent to child (the "line" we're bending around)
75
+ const parentToChild = Vector3.sub(childPos, parentPos, new Vector3());
76
+ const lineLength = parentToChild.magnitude;
77
+ if (lineLength < 0.000001) {
78
+ return; // Parent and child are at same position
79
+ }
80
+ const lineDir = Vector3.scale(parentToChild, 1 / lineLength, new Vector3());
81
+ // Project current joint onto the parent-child line
82
+ const parentToCurrent = Vector3.sub(currentPos, parentPos, new Vector3());
83
+ const projectionLength = Vector3.dot(parentToCurrent, lineDir);
84
+ const projectionPoint = Vector3.scale(lineDir, projectionLength, new Vector3());
85
+ projectionPoint.addBy(parentPos);
86
+ // Vector from projection point to current joint (perpendicular to line)
87
+ const perpendicular = Vector3.sub(currentPos, projectionPoint, new Vector3());
88
+ const perpLength = perpendicular.magnitude;
89
+ if (perpLength < 0.000001) {
90
+ return; // Current joint is on the line
91
+ }
92
+ // Calculate desired direction toward pole vector
93
+ const parentToPole = Vector3.sub(this._poleVector, parentPos, new Vector3());
94
+ const poleProjectionLength = Vector3.dot(parentToPole, lineDir);
95
+ const poleProjectionPoint = Vector3.scale(lineDir, poleProjectionLength, new Vector3());
96
+ poleProjectionPoint.addBy(parentPos);
97
+ // Direction from projection to pole (perpendicular to line)
98
+ const poleDirection = Vector3.sub(this._poleVector, poleProjectionPoint, new Vector3());
99
+ const poleDirLength = poleDirection.magnitude;
100
+ if (poleDirLength < 0.000001) {
101
+ return; // Pole is on the line
102
+ }
103
+ poleDirection.scaleBy(1 / poleDirLength);
104
+ // Calculate new position for current joint
105
+ // Keep the same distance from the line, but rotate toward pole direction
106
+ const desiredPerpendicular = Vector3.scale(poleDirection, perpLength, new Vector3());
107
+ const newPosition = Vector3.add(projectionPoint, desiredPerpendicular, new Vector3());
108
+ // Apply weight (blend between original and constrained position)
109
+ if (this._weight < 0.999) {
110
+ // Manual lerp: result = a + (b - a) * t
111
+ const blended = new Vector3();
112
+ blended.x = currentPos.x + (newPosition.x - currentPos.x) * this._weight;
113
+ blended.y = currentPos.y + (newPosition.y - currentPos.y) * this._weight;
114
+ blended.z = currentPos.z + (newPosition.z - currentPos.z) * this._weight;
115
+ currentJoint.position.set(blended);
116
+ } else {
117
+ currentJoint.position.set(newPosition);
118
+ }
119
+ }
120
+ }
121
+
122
+ export { IKPoleVectorConstraint };
123
+ //# sourceMappingURL=ik_pole_constraint.js.map