@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,377 @@
1
+ import * as THREE from 'three';
2
+ import { rigOptions, getLabelGroup } from './rig-controller';
3
+ import { restoreLockedBoneRotations, updateBoneVisuals, moveBonesForTarget } from './bone-kinematics';
4
+ import { getState } from '../state/scene-state';
5
+ import { primaryRigHandle } from './rig-handle-factory';
6
+
7
+ // Raycaster for mouse interaction
8
+ let raycaster = new THREE.Raycaster();
9
+ let mouse = new THREE.Vector2();
10
+ let hoveredHandle = null;
11
+ let hoveredLabelHeader = null; // Track which label header is currently hovered
12
+ // Drag state tracking
13
+ let isDragging = false;
14
+ let dragStartPosition = new THREE.Vector3();
15
+ let dragPlane = new THREE.Plane();
16
+ let dragOffset = new THREE.Vector3();
17
+ let dragTarget = null;
18
+ let dragTargetPosition = new THREE.Vector3();
19
+
20
+ /**
21
+ * Set up mouse listeners for handle interaction
22
+ * @param {Object} scene - The Three.js scene
23
+ */
24
+ export function setupMouseListeners(scene) {
25
+ const state = getState();
26
+ const renderer = state.renderer;
27
+ if (!renderer) return;
28
+ const domElement = renderer.domElement;
29
+ // Mouse move handler
30
+ domElement.addEventListener('mousemove', (event) => {
31
+ // Calculate mouse position in normalized device coordinates (-1 to +1)
32
+ const rect = domElement.getBoundingClientRect();
33
+ mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
34
+ mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
35
+ // Update raycaster with the new mouse position
36
+ if (state.camera) {
37
+ raycaster.setFromCamera(mouse, state.camera);
38
+ }
39
+ // Check for handle hover
40
+ checkHandleHover();
41
+ // Handle dragging
42
+ if (getIsDragging() && dragTarget) {
43
+ handleDrag();
44
+ }
45
+ });
46
+ // Mouse down handler
47
+ domElement.addEventListener('mousedown', (event) => {
48
+ if (event.button !== 0) return; // Only handle left mouse button
49
+ // Skip if Display Rig is not enabled
50
+ if (!rigOptions.displayRig) return;
51
+ const state = getState();
52
+ if (!state.camera) return;
53
+
54
+ // Since we're removing label header click handling, just check for control handle clicks
55
+ raycaster.setFromCamera(mouse, state.camera);
56
+ const intersects = raycaster.intersectObject(primaryRigHandle);
57
+ if (intersects.length > 0) {
58
+ console.log('Starting drag on handle:', primaryRigHandle.name);
59
+ startDrag(intersects[0], primaryRigHandle);
60
+ event.preventDefault();
61
+ }
62
+ });
63
+ // Mouse up handler
64
+ domElement.addEventListener('mouseup', (event) => {
65
+ if (getIsDragging()) {
66
+ stopDrag();
67
+ event.preventDefault();
68
+ }
69
+ });
70
+ // Mouse leave handler
71
+ domElement.addEventListener('mouseleave', (event) => {
72
+ if (getIsDragging()) {
73
+ stopDrag();
74
+ }
75
+
76
+ // Reset any hover states when mouse leaves the canvas
77
+ resetHoveredStates();
78
+ });
79
+ }
80
+
81
+ /**
82
+ * Reset all hovered states when mouse leaves canvas or on other events
83
+ */
84
+ function resetHoveredStates() {
85
+ // Reset control handle hover state
86
+ if (hoveredHandle && hoveredHandle.material) {
87
+ hoveredHandle.material.color.setHex(rigOptions.normalColor);
88
+ hoveredHandle.material.needsUpdate = true;
89
+ hoveredHandle = null;
90
+ }
91
+
92
+ // Reset label header hover state
93
+ if (hoveredLabelHeader && hoveredLabelHeader.material) {
94
+ hoveredLabelHeader.material.opacity = 0.8; // Default opacity
95
+ hoveredLabelHeader.material.needsUpdate = true;
96
+ hoveredLabelHeader = null;
97
+ }
98
+
99
+ // Reset mouse cursor and controls
100
+ document.body.style.cursor = 'auto';
101
+ const state = getState();
102
+ if (state.controls && !state.controls.enabled && !getIsDragging()) {
103
+ state.controls.enabled = true;
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Get all label sprites from the scene
109
+ * @returns {Array} Array of label sprites
110
+ */
111
+ function getAllLabels() {
112
+ const labels = [];
113
+
114
+ // Add joint labels if they exist
115
+ const jointLabelGroup = getLabelGroup('joint');
116
+ if (jointLabelGroup) {
117
+ jointLabelGroup.children.forEach(label => {
118
+ if (label.userData && (label.userData.isJointLabel || label.userData.isBoneLabel)) {
119
+ labels.push(label);
120
+ }
121
+ });
122
+ }
123
+
124
+ // Add bone labels if they exist
125
+ const boneLabelGroup = getLabelGroup('bone');
126
+ if (boneLabelGroup) {
127
+ boneLabelGroup.children.forEach(label => {
128
+ if (label.userData && label.userData.isBoneLabel) {
129
+ labels.push(label);
130
+ }
131
+ });
132
+ }
133
+
134
+ return labels;
135
+ }
136
+
137
+ /**
138
+ * Check if mouse is hovering over the control handle or any label headers
139
+ */
140
+ export function checkHandleHover() {
141
+ // Don't check for hover if rig display is disabled
142
+ if (!rigOptions.displayRig) return;
143
+
144
+ const state = getState();
145
+ const camera = state.camera;
146
+ const controls = state.controls; // Get orbit controls reference
147
+ if (!camera) return;
148
+
149
+ // Skip hover processing if we're dragging
150
+ if (getIsDragging()) return;
151
+
152
+ // Update the picking ray with the camera and mouse position
153
+ raycaster.setFromCamera(mouse, state.camera);
154
+
155
+ // Track if any hover was detected in this cycle
156
+ let hoverDetected = false;
157
+
158
+ // First check the furthest bone handle
159
+ if (primaryRigHandle) {
160
+ const handleIntersects = raycaster.intersectObject(primaryRigHandle);
161
+
162
+ if (handleIntersects.length > 0) {
163
+ // We hit the handle
164
+ hoverDetected = true;
165
+
166
+ // Set or update hover state
167
+ if (hoveredHandle !== primaryRigHandle) {
168
+ // Reset any previously hovered label header
169
+ if (hoveredLabelHeader) {
170
+ hoveredLabelHeader.material.opacity = 0.8; // Default opacity
171
+ hoveredLabelHeader.material.needsUpdate = true;
172
+ hoveredLabelHeader = null;
173
+ }
174
+
175
+ // Set the handle as hovered
176
+ hoveredHandle = primaryRigHandle;
177
+ hoveredHandle.material.color.setHex(rigOptions.hoverColor);
178
+ hoveredHandle.material.needsUpdate = true;
179
+ }
180
+
181
+ // Update cursor and controls
182
+ document.body.style.cursor = 'pointer';
183
+ if (controls && controls.enabled) {
184
+ controls.enabled = false;
185
+ }
186
+ }
187
+ else if (hoveredHandle === primaryRigHandle) {
188
+ // We had the handle hovered but no longer
189
+ hoveredHandle.material.color.setHex(rigOptions.normalColor);
190
+ hoveredHandle.material.needsUpdate = true;
191
+ hoveredHandle = null;
192
+ }
193
+ }
194
+
195
+ // Now check label headers if nothing else is hovered
196
+ if (!hoverDetected) {
197
+ const allLabels = getAllLabels();
198
+ const labelIntersects = raycaster.intersectObjects(allLabels);
199
+
200
+ if (labelIntersects.length > 0) {
201
+ for (let i = 0; i < labelIntersects.length; i++) {
202
+ const label = labelIntersects[i].object;
203
+
204
+ // Skip if the label doesn't have header hover checking
205
+ if (!label.userData || !label.userData.checkHeaderHover) continue;
206
+
207
+ // Convert intersection point to local coordinates
208
+ const localPoint = label.worldToLocal(labelIntersects[i].point.clone());
209
+
210
+ // Check if over header area
211
+ if (label.userData.checkHeaderHover(localPoint)) {
212
+ // Hovering over a header
213
+ hoverDetected = true;
214
+
215
+ // Update hover state
216
+ if (hoveredLabelHeader !== label) {
217
+ // Reset previous hover states
218
+ if (hoveredHandle) {
219
+ hoveredHandle.material.color.setHex(rigOptions.normalColor);
220
+ hoveredHandle.material.needsUpdate = true;
221
+ hoveredHandle = null;
222
+ }
223
+
224
+ if (hoveredLabelHeader) {
225
+ hoveredLabelHeader.material.opacity = 0.8; // Default opacity
226
+ hoveredLabelHeader.material.needsUpdate = true;
227
+ }
228
+
229
+ // Set new hover state
230
+ hoveredLabelHeader = label;
231
+ hoveredLabelHeader.material.opacity = 1.0; // Full opacity on hover
232
+ hoveredLabelHeader.material.needsUpdate = true;
233
+
234
+ // Store the hover state
235
+ label.userData.isMouseOverHeader = true;
236
+ }
237
+
238
+ // Update cursor and controls
239
+ document.body.style.cursor = 'pointer';
240
+ if (controls && controls.enabled) {
241
+ controls.enabled = false;
242
+ }
243
+
244
+ break; // Found a header hover, no need to check more
245
+ }
246
+ else if (hoveredLabelHeader === label) {
247
+ // We were hovering this label but now we're not over the header
248
+ hoveredLabelHeader.material.opacity = 0.8; // Reset to default opacity
249
+ hoveredLabelHeader.material.needsUpdate = true;
250
+ hoveredLabelHeader.userData.isMouseOverHeader = false;
251
+ hoveredLabelHeader = null;
252
+ }
253
+ }
254
+ }
255
+ }
256
+
257
+ // If no hover detected anywhere, reset everything
258
+ if (!hoverDetected) {
259
+ // Reset control handle hover
260
+ if (hoveredHandle) {
261
+ hoveredHandle.material.color.setHex(rigOptions.normalColor);
262
+ hoveredHandle.material.needsUpdate = true;
263
+ hoveredHandle = null;
264
+ }
265
+
266
+ // Reset label header hover
267
+ if (hoveredLabelHeader) {
268
+ hoveredLabelHeader.material.opacity = 0.8; // Reset to default opacity
269
+ hoveredLabelHeader.material.needsUpdate = true;
270
+ hoveredLabelHeader.userData.isMouseOverHeader = false;
271
+ hoveredLabelHeader = null;
272
+ }
273
+
274
+ // Reset cursor and controls
275
+ document.body.style.cursor = 'auto';
276
+ if (controls && !controls.enabled) {
277
+ controls.enabled = true;
278
+ }
279
+ }
280
+ }
281
+
282
+ /**
283
+ * Handle dragging logic
284
+ */
285
+ function handleDrag() {
286
+ if (!isDragging || !dragTarget) return;
287
+ const state = getState();
288
+ // Get current intersection point with drag plane
289
+ const planeIntersection = new THREE.Vector3();
290
+ // Check if ray intersects plane
291
+ if (raycaster.ray.intersectPlane(dragPlane, planeIntersection)) {
292
+ // Apply the offset to maintain the grab point
293
+ planeIntersection.add(dragOffset);
294
+ // Move handle to new position
295
+ dragTarget.position.copy(planeIntersection);
296
+ // Apply IK if this is the furthest bone handle
297
+ if (dragTarget === primaryRigHandle && dragTarget.userData.controlledBone) {
298
+ const controlledBone = dragTarget.userData.controlledBone;
299
+ // Even if the controlled bone is locked, we still want to move other bones in the chain
300
+ // This is different from before - we don't check if the target bone is locked here
301
+ // Store current locked bone rotations
302
+ restoreLockedBoneRotations();
303
+ // Use the moveBonesForTarget function to handle IK chain properly
304
+ moveBonesForTarget(controlledBone, planeIntersection);
305
+ // Restore locked bone rotations again
306
+ restoreLockedBoneRotations();
307
+ // Force immediate update of visual bone meshes during drag
308
+ updateBoneVisuals();
309
+ }
310
+ }
311
+ }
312
+
313
+ /**
314
+ * Start dragging a control handle
315
+ * @param {Object} intersection - The intersection data from raycaster
316
+ * @param {Object} handle - The handle being dragged
317
+ */
318
+ function startDrag(intersection, handle) {
319
+ // Ensure the handle exists
320
+ if (!handle) return;
321
+ // Set the dragging state to true
322
+ setIsDragging(true);
323
+ dragTarget = handle;
324
+ // Update material to active color
325
+ if (handle.material) {
326
+ handle.material.color.setHex(rigOptions.activeColor);
327
+ handle.material.needsUpdate = true;
328
+ }
329
+ // Store the initial position
330
+ dragTargetPosition.copy(handle.position);
331
+ // Get the state
332
+ const state = getState();
333
+ // Create a drag plane perpendicular to the camera
334
+ const planeNormal = new THREE.Vector3(0, 0, 1).applyQuaternion(state.camera.quaternion);
335
+ dragPlane.setFromNormalAndCoplanarPoint(planeNormal, dragTargetPosition);
336
+ // Calculate offset for precise dragging
337
+ const dragIntersectionPoint = new THREE.Vector3();
338
+ raycaster.ray.intersectPlane(dragPlane, dragIntersectionPoint);
339
+ dragOffset.subVectors(dragTargetPosition, dragIntersectionPoint);
340
+ console.log('Drag started at', dragTargetPosition);
341
+ }
342
+
343
+ /**
344
+ * Stop dragging operation
345
+ */
346
+ function stopDrag() {
347
+ if (!getIsDragging() || !dragTarget) return;
348
+ setIsDragging(false);
349
+ // Reset material to normal or hover color based on current state
350
+ if (dragTarget.material) {
351
+ const isHovered = dragTarget === hoveredHandle;
352
+ dragTarget.material.color.setHex(isHovered ? rigOptions.hoverColor : rigOptions.normalColor);
353
+ dragTarget.material.needsUpdate = true;
354
+ }
355
+ // Re-enable orbit controls
356
+ const state = getState();
357
+ if (state.controls && !state.controls.enabled) {
358
+ state.controls.enabled = true;
359
+ document.body.style.cursor = 'auto';
360
+ }
361
+ }
362
+
363
+ /**
364
+ * Update the isDragging state
365
+ * @param {Boolean} dragging - The new dragging state
366
+ */
367
+ export function setIsDragging(dragging) {
368
+ isDragging = dragging;
369
+ }
370
+
371
+ /**
372
+ * Returns the current dragging state
373
+ * @returns {Boolean} The current dragging state
374
+ */
375
+ export function getIsDragging() {
376
+ return isDragging;
377
+ }
@@ -0,0 +1,175 @@
1
+ import * as THREE from 'three';
2
+ import { bones } from './bone-kinematics';
3
+ import { jointPreviousValues } from '../../panels/asset-panel/rig-heading/rig-heading';
4
+ import { disableApplyButton, enableApplyButton } from './rig-ui-factory';
5
+
6
+ // Add debug flag
7
+ export let jointSettingsDebug = true;
8
+
9
+ /**
10
+ * Store current bone world positions and rotations
11
+ * @returns {Map} Map of bone states keyed by bone name
12
+ */
13
+ export function storeBoneCurrentState() {
14
+ const boneCurrentState = new Map();
15
+
16
+ bones.forEach(bone => {
17
+ if (bone) {
18
+ bone.updateWorldMatrix(true, false);
19
+ const worldPosition = new THREE.Vector3();
20
+ const worldQuaternion = new THREE.Quaternion();
21
+ bone.getWorldPosition(worldPosition);
22
+ bone.getWorldQuaternion(worldQuaternion);
23
+
24
+ boneCurrentState.set(bone.name, {
25
+ position: worldPosition.clone(),
26
+ quaternion: worldQuaternion.clone()
27
+ });
28
+ }
29
+ });
30
+
31
+ return boneCurrentState;
32
+ }
33
+
34
+ /**
35
+ * Update previous values for all constraint dropdowns
36
+ * @param {NodeList} constraintSelects - List of constraint select elements
37
+ */
38
+ export function updatePreviousValues(constraintSelects) {
39
+ constraintSelects.forEach(select => {
40
+ const boneName = select.getAttribute('data-bone-name');
41
+ jointPreviousValues.set(boneName, select.value);
42
+ });
43
+ }
44
+
45
+ /**
46
+ * Update the state of all bone constraint settings
47
+ */
48
+ export function updateConstraintSettingsState() {
49
+ const constraintSelects = document.querySelectorAll('select[data-bone-constraint]');
50
+ let allConstraintsInPreviousState = true;
51
+
52
+ constraintSelects.forEach(select => {
53
+ const boneName = select.getAttribute('data-bone-name');
54
+ const previousValue = jointPreviousValues.get(boneName);
55
+ const currentValue = select.value;
56
+
57
+ if (previousValue !== currentValue) {
58
+ allConstraintsInPreviousState = false;
59
+ }
60
+
61
+ // Also check constraint parameters (for advanced constraints)
62
+ if (currentValue === 'SINGLE_AXIS_ROTATION') {
63
+ // Check hinge parameters
64
+ const itemElem = select.closest('.rig-item');
65
+ if (itemElem) {
66
+ const minInput = itemElem.querySelector('.rig-min-input');
67
+ const maxInput = itemElem.querySelector('.rig-max-input');
68
+ const axisSelect = itemElem.querySelector('.rig-axis-select');
69
+
70
+ if (minInput && maxInput && axisSelect) {
71
+ // Get stored values (default if not stored)
72
+ const storedConfig = jointPreviousValues.get(`${boneName}:hinge-config`);
73
+
74
+ // If no stored config, this is the first time, so store current values
75
+ if (!storedConfig) {
76
+ jointPreviousValues.set(`${boneName}:hinge-config`, {
77
+ axis: axisSelect.value,
78
+ min: parseInt(minInput.value),
79
+ max: parseInt(maxInput.value)
80
+ });
81
+ } else {
82
+ // Check if current values match stored values
83
+ if (storedConfig.axis !== axisSelect.value ||
84
+ storedConfig.min !== parseInt(minInput.value) ||
85
+ storedConfig.max !== parseInt(maxInput.value)) {
86
+ allConstraintsInPreviousState = false;
87
+ }
88
+ }
89
+ }
90
+ }
91
+ } else if (currentValue === 'LIMIT_ROTATION_XYZ') {
92
+ // Check rotation limits
93
+ const itemElem = select.closest('.rig-item');
94
+ if (itemElem) {
95
+ const rotLimitContainers = itemElem.querySelectorAll('.rig-axis-limits');
96
+ const storedConfig = jointPreviousValues.get(`${boneName}:rotation-limits`);
97
+
98
+ // Create a new config object from current values
99
+ const currentConfig = { x: {}, y: {}, z: {} };
100
+ let hasChanges = false;
101
+
102
+ rotLimitContainers.forEach((container, index) => {
103
+ const axis = ['x', 'y', 'z'][index];
104
+ const minInput = container.querySelector('.rig-min-limit input');
105
+ const maxInput = container.querySelector('.rig-max-limit input');
106
+
107
+ if (minInput && maxInput) {
108
+ currentConfig[axis].min = parseInt(minInput.value);
109
+ currentConfig[axis].max = parseInt(maxInput.value);
110
+ }
111
+ });
112
+
113
+ // If no stored config, store current values
114
+ if (!storedConfig) {
115
+ jointPreviousValues.set(`${boneName}:rotation-limits`, JSON.parse(JSON.stringify(currentConfig)));
116
+ } else {
117
+ // Check each axis for changes
118
+ ['x', 'y', 'z'].forEach(axis => {
119
+ if (storedConfig[axis]?.min !== currentConfig[axis]?.min ||
120
+ storedConfig[axis]?.max !== currentConfig[axis]?.max) {
121
+ hasChanges = true;
122
+ }
123
+ });
124
+
125
+ if (hasChanges) {
126
+ allConstraintsInPreviousState = false;
127
+ }
128
+ }
129
+ }
130
+ } else if (currentValue === 'DYNAMIC_SPRING') {
131
+ // Check spring parameters
132
+ const itemElem = select.closest('.rig-item');
133
+ if (itemElem) {
134
+ const stiffnessInput = itemElem.querySelector('.rig-stiffness-input');
135
+ const dampingInput = itemElem.querySelector('.rig-damping-input');
136
+ const gravityInput = itemElem.querySelector('.rig-gravity-input');
137
+
138
+ // Get stored values
139
+ const storedConfig = jointPreviousValues.get(`${boneName}:spring-config`);
140
+
141
+ // If no stored config, store current values
142
+ if (!storedConfig && stiffnessInput && dampingInput && gravityInput) {
143
+ jointPreviousValues.set(`${boneName}:spring-config`, {
144
+ stiffness: parseInt(stiffnessInput.value),
145
+ damping: parseInt(dampingInput.value),
146
+ gravity: parseFloat(gravityInput.value)
147
+ });
148
+ } else if (stiffnessInput && dampingInput && gravityInput) {
149
+ // Check if current values match stored values
150
+ if (storedConfig.stiffness !== parseInt(stiffnessInput.value) ||
151
+ storedConfig.damping !== parseInt(dampingInput.value) ||
152
+ storedConfig.gravity !== parseFloat(gravityInput.value)) {
153
+ allConstraintsInPreviousState = false;
154
+ }
155
+ }
156
+ }
157
+ }
158
+ });
159
+
160
+ if (jointSettingsDebug) {
161
+ console.log(`All Bone Constraints in previous state: ${allConstraintsInPreviousState}`);
162
+ }
163
+
164
+ // Control Apply Changes button state based on changes
165
+ const applyButton = document.getElementById('apply-bone-constraints-button');
166
+ if (applyButton) {
167
+ if (allConstraintsInPreviousState) {
168
+ disableApplyButton(applyButton);
169
+ } else {
170
+ enableApplyButton(applyButton);
171
+ }
172
+ }
173
+
174
+ return allConstraintsInPreviousState;
175
+ }