@littlecarlito/blorktools 0.50.4 → 0.51.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 (114) hide show
  1. package/bin/cli.js +69 -0
  2. package/package.json +13 -7
  3. package/src/asset_debugger/axis-indicator/axis-indicator.css +6 -0
  4. package/src/asset_debugger/axis-indicator/axis-indicator.html +20 -0
  5. package/src/asset_debugger/axis-indicator/axis-indicator.js +822 -0
  6. package/src/asset_debugger/debugger-scene/debugger-scene.css +142 -0
  7. package/src/asset_debugger/debugger-scene/debugger-scene.html +80 -0
  8. package/src/asset_debugger/debugger-scene/debugger-scene.js +791 -0
  9. package/src/asset_debugger/header/header.css +73 -0
  10. package/src/asset_debugger/header/header.html +24 -0
  11. package/src/asset_debugger/header/header.js +224 -0
  12. package/src/asset_debugger/index.html +76 -0
  13. package/src/asset_debugger/landing-page/landing-page.css +396 -0
  14. package/src/asset_debugger/landing-page/landing-page.html +81 -0
  15. package/src/asset_debugger/landing-page/landing-page.js +611 -0
  16. package/src/asset_debugger/loading-splash/loading-splash.css +195 -0
  17. package/src/asset_debugger/loading-splash/loading-splash.html +22 -0
  18. package/src/asset_debugger/loading-splash/loading-splash.js +59 -0
  19. package/src/asset_debugger/loading-splash/preview-loading-splash.js +66 -0
  20. package/src/asset_debugger/main.css +14 -0
  21. package/src/asset_debugger/modals/examples-modal/examples-modal.css +41 -0
  22. package/src/asset_debugger/modals/examples-modal/examples-modal.html +18 -0
  23. package/src/asset_debugger/modals/examples-modal/examples-modal.js +111 -0
  24. package/src/asset_debugger/modals/examples-modal/examples.js +125 -0
  25. package/src/asset_debugger/modals/html-editor-modal/html-editor-modal.css +452 -0
  26. package/src/asset_debugger/modals/html-editor-modal/html-editor-modal.html +87 -0
  27. package/src/asset_debugger/modals/html-editor-modal/html-editor-modal.js +675 -0
  28. package/src/asset_debugger/modals/mesh-info-modal/mesh-info-modal.css +219 -0
  29. package/src/asset_debugger/modals/mesh-info-modal/mesh-info-modal.html +20 -0
  30. package/src/asset_debugger/modals/mesh-info-modal/mesh-info-modal.js +548 -0
  31. package/src/asset_debugger/modals/settings-modal/settings-modal.css +103 -0
  32. package/src/asset_debugger/modals/settings-modal/settings-modal.html +158 -0
  33. package/src/asset_debugger/modals/settings-modal/settings-modal.js +475 -0
  34. package/src/asset_debugger/panels/asset-panel/asset-panel.css +263 -0
  35. package/src/asset_debugger/panels/asset-panel/asset-panel.html +123 -0
  36. package/src/asset_debugger/panels/asset-panel/asset-panel.js +136 -0
  37. package/src/asset_debugger/panels/asset-panel/atlas-heading/atlas-heading.css +94 -0
  38. package/src/asset_debugger/panels/asset-panel/atlas-heading/atlas-heading.js +312 -0
  39. package/src/asset_debugger/panels/asset-panel/mesh-heading/mesh-heading.css +129 -0
  40. package/src/asset_debugger/panels/asset-panel/mesh-heading/mesh-heading.js +486 -0
  41. package/src/asset_debugger/panels/asset-panel/rig-heading/rig-heading.css +545 -0
  42. package/src/asset_debugger/panels/asset-panel/rig-heading/rig-heading.js +538 -0
  43. package/src/asset_debugger/panels/asset-panel/uv-heading/uv-heading.css +70 -0
  44. package/src/asset_debugger/panels/asset-panel/uv-heading/uv-heading.js +586 -0
  45. package/src/asset_debugger/panels/world-panel/world-panel.css +364 -0
  46. package/src/asset_debugger/panels/world-panel/world-panel.html +173 -0
  47. package/src/asset_debugger/panels/world-panel/world-panel.js +1891 -0
  48. package/src/asset_debugger/router.js +190 -0
  49. package/src/asset_debugger/util/animation/playback/animation-playback-controller.js +150 -0
  50. package/src/asset_debugger/util/animation/playback/animation-preview-controller.js +316 -0
  51. package/src/asset_debugger/util/animation/playback/css3d-bounce-controller.js +400 -0
  52. package/src/asset_debugger/util/animation/playback/css3d-reversal-controller.js +821 -0
  53. package/src/asset_debugger/util/animation/render/css3d-prerender-controller.js +696 -0
  54. package/src/asset_debugger/util/animation/render/debug-texture-factory.js +0 -0
  55. package/src/asset_debugger/util/animation/render/iframe2texture-render-controller.js +199 -0
  56. package/src/asset_debugger/util/animation/render/image2texture-prerender-controller.js +461 -0
  57. package/src/asset_debugger/util/animation/render/pbr-material-factory.js +82 -0
  58. package/src/asset_debugger/util/common.css +280 -0
  59. package/src/asset_debugger/util/data/animation-classifier.js +323 -0
  60. package/src/asset_debugger/util/data/duplicate-handler.js +20 -0
  61. package/src/asset_debugger/util/data/glb-buffer-manager.js +407 -0
  62. package/src/asset_debugger/util/data/glb-classifier.js +290 -0
  63. package/src/asset_debugger/util/data/html-formatter.js +76 -0
  64. package/src/asset_debugger/util/data/html-linter.js +276 -0
  65. package/src/asset_debugger/util/data/localstorage-manager.js +265 -0
  66. package/src/asset_debugger/util/data/mesh-html-manager.js +295 -0
  67. package/src/asset_debugger/util/data/string-serder.js +303 -0
  68. package/src/asset_debugger/util/data/texture-classifier.js +663 -0
  69. package/src/asset_debugger/util/data/upload/background-file-handler.js +292 -0
  70. package/src/asset_debugger/util/data/upload/dropzone-preview-controller.js +396 -0
  71. package/src/asset_debugger/util/data/upload/file-upload-manager.js +495 -0
  72. package/src/asset_debugger/util/data/upload/glb-file-handler.js +36 -0
  73. package/src/asset_debugger/util/data/upload/glb-preview-controller.js +317 -0
  74. package/src/asset_debugger/util/data/upload/lighting-file-handler.js +194 -0
  75. package/src/asset_debugger/util/data/upload/model-file-manager.js +104 -0
  76. package/src/asset_debugger/util/data/upload/texture-file-handler.js +166 -0
  77. package/src/asset_debugger/util/data/upload/zip-handler.js +686 -0
  78. package/src/asset_debugger/util/loaders/html2canvas-loader.js +107 -0
  79. package/src/asset_debugger/util/rig/bone-kinematics.js +403 -0
  80. package/src/asset_debugger/util/rig/rig-constraint-manager.js +618 -0
  81. package/src/asset_debugger/util/rig/rig-controller.js +612 -0
  82. package/src/asset_debugger/util/rig/rig-factory.js +628 -0
  83. package/src/asset_debugger/util/rig/rig-handle-factory.js +46 -0
  84. package/src/asset_debugger/util/rig/rig-label-factory.js +441 -0
  85. package/src/asset_debugger/util/rig/rig-mouse-handler.js +377 -0
  86. package/src/asset_debugger/util/rig/rig-state-manager.js +175 -0
  87. package/src/asset_debugger/util/rig/rig-tooltip-manager.js +267 -0
  88. package/src/asset_debugger/util/rig/rig-ui-factory.js +700 -0
  89. package/src/asset_debugger/util/scene/background-manager.js +284 -0
  90. package/src/asset_debugger/util/scene/camera-controller.js +243 -0
  91. package/src/asset_debugger/util/scene/css3d-debug-controller.js +406 -0
  92. package/src/asset_debugger/util/scene/css3d-frame-factory.js +113 -0
  93. package/src/asset_debugger/util/scene/css3d-scene-manager.js +529 -0
  94. package/src/asset_debugger/util/scene/glb-controller.js +208 -0
  95. package/src/asset_debugger/util/scene/lighting-manager.js +690 -0
  96. package/src/asset_debugger/util/scene/threejs-model-manager.js +437 -0
  97. package/src/asset_debugger/util/scene/threejs-preview-manager.js +207 -0
  98. package/src/asset_debugger/util/scene/threejs-preview-setup.js +478 -0
  99. package/src/asset_debugger/util/scene/threejs-scene-controller.js +286 -0
  100. package/src/asset_debugger/util/scene/ui-manager.js +107 -0
  101. package/src/asset_debugger/util/state/animation-state.js +128 -0
  102. package/src/asset_debugger/util/state/css3d-state.js +83 -0
  103. package/src/asset_debugger/util/state/glb-preview-state.js +31 -0
  104. package/src/asset_debugger/util/state/log-util.js +197 -0
  105. package/src/asset_debugger/util/state/scene-state.js +452 -0
  106. package/src/asset_debugger/util/state/threejs-state.js +54 -0
  107. package/src/asset_debugger/util/workers/lighting-worker.js +61 -0
  108. package/src/asset_debugger/util/workers/model-worker.js +109 -0
  109. package/src/asset_debugger/util/workers/texture-worker.js +54 -0
  110. package/src/asset_debugger/util/workers/worker-manager.js +212 -0
  111. package/src/asset_debugger/widgets/mesh-info-widget.js +280 -0
  112. package/src/index.html +261 -0
  113. package/src/index.js +8 -0
  114. package/vite.config.js +66 -0
@@ -0,0 +1,618 @@
1
+ import * as THREE from 'three';
2
+ import { jointPreviousValues } from "../../panels/asset-panel/rig-heading/rig-heading";
3
+ import { findBoneByName, updateAllBoneMatrices } from "./bone-kinematics";
4
+ import { rigDetails, rigOptions } from "./rig-controller";
5
+ import { storeBoneCurrentState, updateConstraintSettingsState, updatePreviousValues } from "./rig-state-manager";
6
+ import { disableApplyButton } from "./rig-ui-factory";
7
+
8
+ /**
9
+ * Create a none constraint (removes constraint)
10
+ * @param {Object} currentState - Current bone state
11
+ * @returns {Object} None constraint
12
+ */
13
+ function createNoneConstraint(currentState) {
14
+ return {
15
+ type: 'none',
16
+ preservePosition: currentState ? true : false
17
+ };
18
+ }
19
+
20
+ /**
21
+ * Create a dynamic spring constraint
22
+ * @param {Object} item - Bone item data
23
+ * @param {Object} currentState - Current bone state
24
+ * @param {HTMLElement} select - Constraint select element
25
+ * @param {string} boneName - Bone name
26
+ * @returns {Object} Spring constraint
27
+ */
28
+ function createSpringConstraint(item, currentState, select, boneName) {
29
+ const constraint = {
30
+ type: 'spring',
31
+ stiffness: item?.spring?.stiffness || 50,
32
+ damping: item?.spring?.damping || 5,
33
+ gravity: item?.spring?.gravity || 1.0,
34
+ preservePosition: currentState ? true : false
35
+ };
36
+
37
+ const springItemElem = select.closest('.rig-item');
38
+ if (springItemElem) {
39
+ const stiffnessInput = springItemElem.querySelector('.rig-stiffness-input');
40
+ const dampingInput = springItemElem.querySelector('.rig-damping-input');
41
+ const gravityInput = springItemElem.querySelector('.rig-gravity-input');
42
+
43
+ if (stiffnessInput && dampingInput && gravityInput) {
44
+ jointPreviousValues.set(`${boneName}:spring-config`, {
45
+ stiffness: parseInt(stiffnessInput.value),
46
+ damping: parseInt(dampingInput.value),
47
+ gravity: parseFloat(gravityInput.value)
48
+ });
49
+ }
50
+ }
51
+
52
+ return constraint;
53
+ }
54
+
55
+ /**
56
+ * Create a limit rotation constraint
57
+ * @param {Object} item - Bone item data
58
+ * @param {Object} currentState - Current bone state
59
+ * @param {HTMLElement} select - Constraint select element
60
+ * @param {string} boneName - Bone name
61
+ * @returns {Object} Limit rotation constraint
62
+ */
63
+ function createLimitRotationConstraint(item, currentState, select, boneName) {
64
+ const constraint = {
65
+ type: 'limitRotation',
66
+ limits: item?.rotationLimits || {
67
+ x: { min: -Math.PI/4, max: Math.PI/4 },
68
+ y: { min: -Math.PI/4, max: Math.PI/4 },
69
+ z: { min: -Math.PI/4, max: Math.PI/4 }
70
+ },
71
+ preservePosition: currentState ? true : false
72
+ };
73
+
74
+ const rotItemElem = select.closest('.rig-item');
75
+ if (rotItemElem) {
76
+ const currentConfig = { x: {}, y: {}, z: {} };
77
+ const rotLimitContainers = rotItemElem.querySelectorAll('.rig-axis-limits');
78
+
79
+ rotLimitContainers.forEach((container, index) => {
80
+ const axis = ['x', 'y', 'z'][index];
81
+ const minInput = container.querySelector('.rig-min-limit input');
82
+ const maxInput = container.querySelector('.rig-max-limit input');
83
+
84
+ if (minInput && maxInput) {
85
+ currentConfig[axis].min = parseInt(minInput.value);
86
+ currentConfig[axis].max = parseInt(maxInput.value);
87
+ }
88
+ });
89
+
90
+ jointPreviousValues.set(`${boneName}:rotation-limits`, JSON.parse(JSON.stringify(currentConfig)));
91
+ }
92
+
93
+ return constraint;
94
+ }
95
+
96
+ /**
97
+ * Create a single axis rotation (hinge) constraint
98
+ * @param {Object} item - Bone item data
99
+ * @param {Object} currentState - Current bone state
100
+ * @param {HTMLElement} select - Constraint select element
101
+ * @param {string} boneName - Bone name
102
+ * @returns {Object} Hinge constraint
103
+ */
104
+ function createHingeConstraint(item, currentState, select, boneName) {
105
+ const constraint = {
106
+ type: 'hinge',
107
+ axis: item?.hingeAxis || 'y',
108
+ min: item?.hingeMin || -Math.PI/2,
109
+ max: item?.hingeMax || Math.PI/2,
110
+ preservePosition: currentState ? true : false
111
+ };
112
+
113
+ const itemElem = select.closest('.rig-item');
114
+ if (itemElem) {
115
+ const minInput = itemElem.querySelector('.rig-min-input');
116
+ const maxInput = itemElem.querySelector('.rig-max-input');
117
+ const axisSelect = itemElem.querySelector('.rig-axis-select');
118
+
119
+ if (minInput && maxInput && axisSelect) {
120
+ jointPreviousValues.set(`${boneName}:hinge-config`, {
121
+ axis: axisSelect.value,
122
+ min: parseInt(minInput.value),
123
+ max: parseInt(maxInput.value)
124
+ });
125
+ }
126
+ }
127
+
128
+ return constraint;
129
+ }
130
+
131
+ /**
132
+ * Create a fixed position constraint
133
+ * @param {Object} currentState - Current bone state
134
+ * @returns {Object} Fixed position constraint
135
+ */
136
+ function createFixedPositionConstraint(currentState) {
137
+ return {
138
+ type: 'fixed',
139
+ preservePosition: currentState ? true : false
140
+ };
141
+ }
142
+
143
+ /**
144
+ * Create constraint object based on constraint type
145
+ * @param {string} constraintType - Type of constraint
146
+ * @param {Object} item - Bone item data
147
+ * @param {Object} currentState - Current bone state
148
+ * @param {HTMLElement} select - Constraint select element
149
+ * @param {string} boneName - Bone name
150
+ * @returns {Object} Constraint object
151
+ */
152
+ function createConstraintByType(constraintType, item, currentState, select, boneName) {
153
+ switch (constraintType) {
154
+ case 'FIXED_POSITION':
155
+ return createFixedPositionConstraint(currentState);
156
+ case 'SINGLE_AXIS_ROTATION':
157
+ return createHingeConstraint(item, currentState, select, boneName);
158
+ case 'LIMIT_ROTATION_XYZ':
159
+ return createLimitRotationConstraint(item, currentState, select, boneName);
160
+ case 'DYNAMIC_SPRING':
161
+ return createSpringConstraint(item, currentState, select, boneName);
162
+ case 'NONE':
163
+ default:
164
+ return createNoneConstraint(currentState);
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Apply constraint to a bone and update rig details
170
+ * @param {Object} bone - Three.js bone object
171
+ * @param {Object} constraint - Constraint object
172
+ * @param {Object} currentState - Current bone state
173
+ * @param {string} boneName - Bone name
174
+ */
175
+ function applyConstraintToBone(bone, constraint, currentState, boneName) {
176
+ console.log(`Applying ${constraint.type} constraint to ${boneName}`);
177
+
178
+ if (currentState && constraint.preservePosition) {
179
+ constraint.currentPosition = currentState.position;
180
+ constraint.currentQuaternion = currentState.quaternion;
181
+ }
182
+
183
+ applyJointConstraints(bone, constraint);
184
+
185
+ if (rigDetails.constraints) {
186
+ const existingIndex = rigDetails.constraints.findIndex(c =>
187
+ c.boneName === boneName || c.nodeName === boneName);
188
+
189
+ if (existingIndex >= 0) {
190
+ rigDetails.constraints[existingIndex] = {
191
+ boneName: boneName,
192
+ type: constraint.type,
193
+ data: constraint
194
+ };
195
+ } else {
196
+ rigDetails.constraints.push({
197
+ boneName: boneName,
198
+ type: constraint.type,
199
+ data: constraint
200
+ });
201
+ }
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Process a single bone constraint
207
+ * @param {HTMLElement} select - Constraint select element
208
+ * @param {Map} boneCurrentState - Map of current bone states
209
+ */
210
+ export function processBoneConstraint(select, boneCurrentState) {
211
+ const boneName = select.getAttribute('data-bone-name');
212
+ const constraintType = select.value;
213
+
214
+ console.log(`Processing bone ${boneName}, constraint type: ${constraintType}`);
215
+
216
+ const bone = findBoneByName(boneName);
217
+ if (!bone) return;
218
+
219
+ let item = null;
220
+ if (rigDetails && rigDetails.bones) {
221
+ item = rigDetails.bones.find(b => b.name === boneName);
222
+ }
223
+
224
+ const currentState = boneCurrentState.get(boneName);
225
+
226
+ const constraint = createConstraintByType(constraintType, item, currentState, select, boneName);
227
+
228
+ if (constraint) {
229
+ applyConstraintToBone(bone, constraint, currentState, boneName);
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Handle the Apply Constraints button click
235
+ * @param {HTMLElement} button - The Apply Constraints button element
236
+ */
237
+ export function handleApplyConstraints(button) {
238
+ console.log('Applying bone constraint changes...');
239
+
240
+ const constraintSelects = document.querySelectorAll('select[data-bone-constraint]');
241
+ const boneCurrentState = storeBoneCurrentState();
242
+
243
+ constraintSelects.forEach(select => {
244
+ processBoneConstraint(select, boneCurrentState);
245
+ });
246
+
247
+ updateAllBoneMatrices(true);
248
+ updatePreviousValues(constraintSelects);
249
+ updateConstraintSettingsState();
250
+ disableApplyButton(button);
251
+
252
+ console.log('Bone constraints applied successfully');
253
+ }
254
+
255
+ /**
256
+ * Apply joint constraints to a bone
257
+ * @param {Object} bone - The bone to apply constraints to
258
+ * @param {Object} constraints - Constraint data
259
+ */
260
+ export function applyJointConstraints(bone, constraints) {
261
+ if (!bone || !constraints) return;
262
+
263
+ console.log(`Applying ${constraints.type} constraint to ${bone.name}`);
264
+
265
+ // Store constraint data in bone userData for reference
266
+ bone.userData.constraints = constraints;
267
+
268
+ // Check if we should preserve the current position
269
+ const preserveCurrentPose = constraints.preservePosition &&
270
+ constraints.currentPosition &&
271
+ constraints.currentQuaternion;
272
+
273
+ // If we're preserving the current position, store it before applying constraints
274
+ if (preserveCurrentPose) {
275
+ console.log(`Preserving current pose for ${bone.name}`);
276
+ bone.userData.preservedPosition = constraints.currentPosition.clone();
277
+ bone.userData.preservedQuaternion = constraints.currentQuaternion.clone();
278
+ }
279
+
280
+ // Apply different constraint types
281
+ switch (constraints.type) {
282
+ case 'fixed':
283
+ // Fixed joints don't allow rotation - enforce this by marking as locked
284
+ bone.userData.isLocked = true;
285
+
286
+ // If we're preserving the current pose, store it as the fixed position
287
+ if (preserveCurrentPose) {
288
+ // For fixed constraints, we want to maintain the current world position
289
+ bone.userData.fixedPosition = constraints.currentPosition.clone();
290
+ bone.userData.fixedQuaternion = constraints.currentQuaternion.clone();
291
+ console.log(`Applied fixed constraint to ${bone.name} at current position - joint will be locked`);
292
+ } else {
293
+ console.log(`Applied fixed constraint to ${bone.name} - joint will be locked`);
294
+ }
295
+ break;
296
+
297
+ case 'hinge':
298
+ // Hinge joints only allow rotation on one axis
299
+ bone.userData.hinge = {
300
+ axis: constraints.axis || 'y',
301
+ min: constraints.min !== undefined ? constraints.min : -Math.PI/2,
302
+ max: constraints.max !== undefined ? constraints.max : Math.PI/2
303
+ };
304
+
305
+ // If preserving position, store current rotation as the initial state
306
+ if (preserveCurrentPose) {
307
+ bone.userData.initialWorldQuaternion = constraints.currentQuaternion.clone();
308
+ }
309
+
310
+ console.log(`Applied hinge constraint to ${bone.name} on ${bone.userData.hinge.axis} axis with limits [${bone.userData.hinge.min}, ${bone.userData.hinge.max}]`);
311
+ break;
312
+
313
+ case 'limitRotation':
314
+ // Limit rotation within specific ranges for each axis
315
+ bone.userData.rotationLimits = {
316
+ x: constraints.limits?.x || { min: -Math.PI/4, max: Math.PI/4 },
317
+ y: constraints.limits?.y || { min: -Math.PI/4, max: Math.PI/4 },
318
+ z: constraints.limits?.z || { min: -Math.PI/4, max: Math.PI/4 }
319
+ };
320
+
321
+ // If preserving position, store current rotation
322
+ if (preserveCurrentPose) {
323
+ bone.userData.initialWorldQuaternion = constraints.currentQuaternion.clone();
324
+ }
325
+
326
+ console.log(`Applied rotation limits to ${bone.name}:`, bone.userData.rotationLimits);
327
+ break;
328
+
329
+ case 'spring':
330
+ // Spring joints try to return to their rest position
331
+ bone.userData.spring = {
332
+ stiffness: constraints.stiffness || 50,
333
+ damping: constraints.damping || 5,
334
+ gravity: constraints.gravity || 1.0,
335
+ // Store the current pose as the rest position without applying immediate forces
336
+ restPosition: preserveCurrentPose
337
+ ? new THREE.Vector3().copy(constraints.currentPosition)
338
+ : new THREE.Vector3().copy(bone.position),
339
+ restRotation: preserveCurrentPose
340
+ ? new THREE.Euler().setFromQuaternion(constraints.currentQuaternion)
341
+ : new THREE.Euler(
342
+ bone.rotation.x,
343
+ bone.rotation.y,
344
+ bone.rotation.z,
345
+ bone.rotation.order
346
+ ),
347
+ // Add velocity tracking for proper spring physics
348
+ velocity: new THREE.Vector3(0, 0, 0),
349
+ lastPosition: null, // Will be set during first update
350
+ lastTime: Date.now()
351
+ };
352
+ console.log(`Applied spring constraint to ${bone.name} with stiffness ${bone.userData.spring.stiffness} - rest pose preserved`);
353
+ break;
354
+
355
+ case 'none':
356
+ default:
357
+ // No constraints (default) - unrestricted rotation
358
+ // No need to do anything special here, allow free movement
359
+ break;
360
+ }
361
+
362
+ // Add a custom update function to enforce constraints during animation
363
+ const originalUpdateMatrix = bone.updateMatrix;
364
+ bone.updateMatrix = function() {
365
+ // Apply constraints to rotation before updating matrix
366
+ if (this.userData.constraints) {
367
+ enforceJointConstraints(this);
368
+ }
369
+ // Call the original update function
370
+ originalUpdateMatrix.call(this);
371
+ };
372
+
373
+ // If we're preserving position, apply it now
374
+ if (preserveCurrentPose) {
375
+ // For fixed constraints, directly set the world position and quaternion
376
+ if (constraints.type === 'fixed' && bone.parent) {
377
+ // We need to convert world position back to local
378
+ const worldPos = bone.userData.preservedPosition;
379
+ const worldQuat = bone.userData.preservedQuaternion;
380
+
381
+ // Convert to parent space
382
+ const parentWorldInverse = new THREE.Matrix4().invert(bone.parent.matrixWorld);
383
+ const localPos = worldPos.clone().applyMatrix4(parentWorldInverse);
384
+
385
+ const parentQuatInverse = new THREE.Quaternion().copy(bone.parent.quaternion).invert();
386
+ const localQuat = worldQuat.clone().multiply(parentQuatInverse);
387
+
388
+ // Apply the local position and rotation
389
+ bone.position.copy(localPos);
390
+ bone.quaternion.copy(localQuat);
391
+ }
392
+
393
+ // Update the bone matrix to reflect these changes
394
+ bone.updateMatrix();
395
+ }
396
+ }
397
+
398
+ /**
399
+ * Enforce joint constraints on a bone
400
+ * @param {Object} bone - The bone to enforce constraints on
401
+ */
402
+ function enforceJointConstraints(bone) {
403
+ if (!bone || !bone.userData.constraints) return;
404
+
405
+ const constraints = bone.userData.constraints;
406
+
407
+ switch (constraints.type) {
408
+ case 'fixed':
409
+ // Fixed joints - maintain position and orientation
410
+ if (bone.userData.fixedPosition && bone.userData.fixedQuaternion) {
411
+ // Use the stored fixed position and orientation (world coordinates)
412
+ // We need to convert back to local space relative to the parent
413
+ if (bone.parent) {
414
+ // Get parent world inverse
415
+ const parentWorldInverse = new THREE.Matrix4().copy(bone.parent.matrixWorld).invert();
416
+ const localPos = bone.userData.fixedPosition.clone().applyMatrix4(parentWorldInverse);
417
+
418
+ // Get parent quaternion inverse
419
+ const parentQuatInverse = new THREE.Quaternion().copy(bone.parent.quaternion).invert();
420
+ const localQuat = new THREE.Quaternion().copy(bone.userData.fixedQuaternion)
421
+ .premultiply(parentQuatInverse);
422
+
423
+ // Set local position and rotation
424
+ bone.position.copy(localPos);
425
+ bone.quaternion.copy(localQuat);
426
+ }
427
+ }
428
+ // If no fixed position is stored, use initial rotation
429
+ else if (bone.userData.initialRotation) {
430
+ bone.rotation.set(
431
+ bone.userData.initialRotation.x,
432
+ bone.userData.initialRotation.y,
433
+ bone.userData.initialRotation.z
434
+ );
435
+ bone.rotation.order = bone.userData.initialRotation.order;
436
+ }
437
+ break;
438
+
439
+ case 'hinge':
440
+ // Hinge joints - only allow rotation on one axis, reset others
441
+ if (bone.userData.hinge) {
442
+ const hinge = bone.userData.hinge;
443
+ const initial = bone.userData.initialRotation || { x: 0, y: 0, z: 0 };
444
+
445
+ // Reset rotation on locked axes
446
+ if (hinge.axis !== 'x') bone.rotation.x = initial.x;
447
+ if (hinge.axis !== 'y') bone.rotation.y = initial.y;
448
+ if (hinge.axis !== 'z') bone.rotation.z = initial.z;
449
+
450
+ // Clamp rotation on the free axis
451
+ if (hinge.axis === 'x') {
452
+ bone.rotation.x = Math.max(hinge.min, Math.min(hinge.max, bone.rotation.x));
453
+ } else if (hinge.axis === 'y') {
454
+ bone.rotation.y = Math.max(hinge.min, Math.min(hinge.max, bone.rotation.y));
455
+ } else if (hinge.axis === 'z') {
456
+ bone.rotation.z = Math.max(hinge.min, Math.min(hinge.max, bone.rotation.z));
457
+ }
458
+ }
459
+ break;
460
+
461
+ case 'limitRotation':
462
+ // Apply rotation limits on each axis
463
+ if (bone.userData.rotationLimits) {
464
+ const limits = bone.userData.rotationLimits;
465
+
466
+ if (limits.x) {
467
+ bone.rotation.x = Math.max(limits.x.min, Math.min(limits.x.max, bone.rotation.x));
468
+ }
469
+ if (limits.y) {
470
+ bone.rotation.y = Math.max(limits.y.min, Math.min(limits.y.max, bone.rotation.y));
471
+ }
472
+ if (limits.z) {
473
+ bone.rotation.z = Math.max(limits.z.min, Math.min(limits.z.max, bone.rotation.z));
474
+ }
475
+ }
476
+ break;
477
+
478
+ case 'spring':
479
+ // Ragdoll-like spring implementation that respects hierarchy
480
+ if (bone.userData.spring) {
481
+ const spring = bone.userData.spring;
482
+
483
+ // Initialize tracking if this is the first update
484
+ const now = Date.now();
485
+ if (!spring.lastPosition) {
486
+ spring.lastPosition = new THREE.Vector3().copy(bone.position);
487
+ spring.lastTime = now;
488
+ spring.velocityX = 0;
489
+ spring.velocityY = 0;
490
+ spring.velocityZ = 0;
491
+ spring.lastRotation = new THREE.Euler().copy(bone.rotation);
492
+ break;
493
+ }
494
+
495
+ // Calculate time delta with safeguards
496
+ const deltaTime = Math.min((now - spring.lastTime) / 1000, 0.05); // Cap at 50ms for stability
497
+ if (deltaTime <= 0) break;
498
+
499
+ // Store hierarchy information
500
+ if (!spring.hierarchyFactor) {
501
+ // Set influence based on depth in hierarchy (1.0 for root, decreases for children)
502
+ let hierarchyDepth = 0;
503
+ let parent = bone.parent;
504
+ while (parent && parent.isBone) {
505
+ hierarchyDepth++;
506
+ parent = parent.parent;
507
+ }
508
+ // Deeper bones receive less influence to prevent cascading oscillations
509
+ spring.hierarchyFactor = Math.max(0.2, 1.0 / (hierarchyDepth + 1));
510
+ }
511
+
512
+ // Calculate rotational differences from rest pose
513
+ const diffX = spring.restRotation.x - bone.rotation.x;
514
+ const diffY = spring.restRotation.y - bone.rotation.y;
515
+ const diffZ = spring.restRotation.z - bone.rotation.z;
516
+
517
+ // Calculate rotational velocity (change since last frame)
518
+ const rotVelX = (bone.rotation.x - spring.lastRotation.x) / deltaTime;
519
+ const rotVelY = (bone.rotation.y - spring.lastRotation.y) / deltaTime;
520
+ const rotVelZ = (bone.rotation.z - spring.lastRotation.z) / deltaTime;
521
+
522
+ // Proper spring forces adjusted by hierarchy factor and mass (simulated by bone size)
523
+ const boneSize = bone.scale.length() || 1;
524
+ const massInfluence = 1.0 / (boneSize * 0.5 + 0.5); // Larger bones have more mass
525
+
526
+ // Apply hierarchy and mass adjustments to spring calculations
527
+ const adjustedStiffness = spring.stiffness * spring.hierarchyFactor * massInfluence;
528
+ const adjustedDamping = spring.damping * (1 + (1 - spring.hierarchyFactor) * 2); // More damping for deeper bones
529
+
530
+ // Calculate spring force with adjusted parameters
531
+ const springForceX = diffX * adjustedStiffness;
532
+ const springForceY = diffY * adjustedStiffness;
533
+ const springForceZ = diffZ * adjustedStiffness;
534
+
535
+ // Apply damping force proportional to current velocity
536
+ const dampingForceX = -rotVelX * adjustedDamping;
537
+ const dampingForceY = -rotVelY * adjustedDamping;
538
+ const dampingForceZ = -rotVelZ * adjustedDamping;
539
+
540
+ // Combine forces
541
+ const totalForceX = springForceX + dampingForceX;
542
+ const totalForceY = springForceY + dampingForceY;
543
+ const totalForceZ = springForceZ + dampingForceZ;
544
+
545
+ // Update velocities with forces
546
+ spring.velocityX += totalForceX * deltaTime;
547
+ spring.velocityY += totalForceY * deltaTime;
548
+ spring.velocityZ += totalForceZ * deltaTime;
549
+
550
+ // Apply absolute velocity decay to ensure the motion eventually stops
551
+ // This decay factor is separate from the damping which only affects the instantaneous force
552
+ const velocityDecayFactor = Math.max(0, 1.0 - (spring.damping * 0.01 + 0.05) * deltaTime);
553
+ spring.velocityX *= velocityDecayFactor;
554
+ spring.velocityY *= velocityDecayFactor;
555
+ spring.velocityZ *= velocityDecayFactor;
556
+
557
+ // Apply velocity threshold to stop tiny movements
558
+ const velocityThreshold = 0.001;
559
+ if (Math.abs(spring.velocityX) < velocityThreshold) spring.velocityX = 0;
560
+ if (Math.abs(spring.velocityY) < velocityThreshold) spring.velocityY = 0;
561
+ if (Math.abs(spring.velocityZ) < velocityThreshold) spring.velocityZ = 0;
562
+
563
+ // Apply velocity limits to prevent extreme oscillations
564
+ const maxVelocity = 15.0;
565
+ spring.velocityX = Math.max(-maxVelocity, Math.min(maxVelocity, spring.velocityX));
566
+ spring.velocityY = Math.max(-maxVelocity, Math.min(maxVelocity, spring.velocityY));
567
+ spring.velocityZ = Math.max(-maxVelocity, Math.min(maxVelocity, spring.velocityZ));
568
+
569
+ // Apply velocities to rotation
570
+ bone.rotation.x += spring.velocityX * deltaTime;
571
+ bone.rotation.y += spring.velocityY * deltaTime;
572
+ bone.rotation.z += spring.velocityZ * deltaTime;
573
+
574
+ // Apply gravity effect based on hierarchy - deeper bones droop more
575
+ // Check if the bone should be affected by gravity
576
+ if (spring.hierarchyFactor < 0.8) { // Only affect non-root bones
577
+ // Get global gravity value from rigOptions if available, or use a reasonable default
578
+ const worldGravity = (rigOptions && rigOptions.worldGravity !== undefined) ?
579
+ rigOptions.worldGravity : 1.0;
580
+
581
+ // Simple gravity effect that pulls parts downward around local X axis
582
+ // This creates a more natural ragdoll droop effect
583
+ // The gravity influence is scaled by hierarchy and the global gravity value
584
+ const gravityInfluence = (1 - spring.hierarchyFactor) * 0.5 * Math.abs(worldGravity);
585
+
586
+ // Apply gravity with correct direction (positive or negative based on worldGravity sign)
587
+ const gravityDirection = worldGravity >= 0 ? 1 : -1;
588
+ bone.rotation.x += gravityInfluence * deltaTime * 2.0 * gravityDirection;
589
+ }
590
+
591
+ // Store current state for next update
592
+ spring.lastPosition.copy(bone.position);
593
+ spring.lastTime = now;
594
+ spring.lastRotation.copy(bone.rotation);
595
+
596
+ // If this bone has children, ensure the children also update their constraints
597
+ // This ensures force propagation through the hierarchy
598
+ if (bone.children && bone.children.length > 0) {
599
+ for (let i = 0; i < bone.children.length; i++) {
600
+ const child = bone.children[i];
601
+ if (child.isBone && child.userData.constraints &&
602
+ child.userData.constraints.type === 'spring') {
603
+ // Force immediate update of child springs for proper cascade effect
604
+ enforceJointConstraints(child);
605
+ }
606
+ }
607
+ }
608
+ }
609
+ break;
610
+
611
+ case 'none':
612
+ default:
613
+ // No constraints (default) - unrestricted rotation
614
+ // No need to do anything special here, allow free movement
615
+ break;
616
+ }
617
+ }
618
+