@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,107 @@
1
+ // Track if we've attempted to load html2canvas
2
+ let html2canvasLoaded = false;
3
+ let html2canvasLoading = false;
4
+ let html2canvasCallbacks = [];
5
+
6
+ /**
7
+ * Ensure html2canvas is loaded
8
+ * @returns {Promise<boolean>} Promise that resolves to true if html2canvas is available
9
+ */
10
+ export function loadHtml2Canvas() {
11
+ return new Promise((resolve) => {
12
+ // If already loaded, resolve immediately
13
+ if (window.html2canvas) {
14
+ resolve(true);
15
+ return;
16
+ }
17
+
18
+ // If we already tried and failed to load, don't try again
19
+ if (html2canvasLoaded && !window.html2canvas) {
20
+ console.error('[HTML2CANVAS] Library could not be loaded previously');
21
+ resolve(false);
22
+ return;
23
+ }
24
+
25
+ // If already loading, add to callbacks
26
+ if (html2canvasLoading) {
27
+ console.debug('[HTML2CANVAS] Library already loading, waiting');
28
+ html2canvasCallbacks.push(resolve);
29
+ return;
30
+ }
31
+
32
+ // Start loading
33
+ html2canvasLoading = true;
34
+ console.log('[HTML2CANVAS] Starting library load');
35
+
36
+ // Define potential sources for html2canvas
37
+ const sources = [
38
+ 'https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js',
39
+ 'https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js',
40
+ 'https://unpkg.com/html2canvas@1.4.1/dist/html2canvas.min.js',
41
+ '/node_modules/html2canvas/dist/html2canvas.min.js',
42
+ '/lib/html2canvas.min.js'
43
+ ];
44
+
45
+ // Try to load from the first source
46
+ loadFromSource(0);
47
+
48
+ function loadFromSource(index) {
49
+ if (index >= sources.length) {
50
+ console.error('[HTML2CANVAS] All sources failed, could not load the library');
51
+ html2canvasLoaded = true; // Mark as loaded but failed
52
+ html2canvasLoading = false;
53
+ resolve(false);
54
+
55
+ // Resolve any pending callbacks with failure
56
+ html2canvasCallbacks.forEach(callback => callback(false));
57
+ html2canvasCallbacks = [];
58
+ return;
59
+ }
60
+
61
+ const source = sources[index];
62
+ console.log(`[HTML2CANVAS] Trying to load from source: ${source}`);
63
+
64
+ // Add script to page
65
+ const script = document.createElement('script');
66
+ script.src = source;
67
+ script.async = true;
68
+
69
+ let timeout = setTimeout(() => {
70
+ console.warn(`[HTML2CANVAS] Timeout loading from ${source}, trying next source`);
71
+ script.onload = null;
72
+ script.onerror = null;
73
+ if (script.parentNode) {
74
+ script.parentNode.removeChild(script);
75
+ }
76
+ loadFromSource(index + 1);
77
+ }, 5000); // 5 second timeout
78
+
79
+ script.onload = () => {
80
+ clearTimeout(timeout);
81
+ if (window.html2canvas) {
82
+ console.log(`[HTML2CANVAS] Successfully loaded from ${source}`);
83
+ html2canvasLoaded = true;
84
+ html2canvasLoading = false;
85
+ resolve(true);
86
+
87
+ // Resolve any pending callbacks
88
+ html2canvasCallbacks.forEach(callback => callback(true));
89
+ html2canvasCallbacks = [];
90
+ } else {
91
+ console.warn(`[HTML2CANVAS] Loaded script from ${source}, but html2canvas is not available, trying next source`);
92
+ loadFromSource(index + 1);
93
+ }
94
+ };
95
+
96
+ script.onerror = () => {
97
+ clearTimeout(timeout);
98
+ console.warn(`[HTML2CANVAS] Error loading from ${source}, trying next source`);
99
+ loadFromSource(index + 1);
100
+ };
101
+
102
+ document.head.appendChild(script);
103
+ }
104
+ });
105
+ }
106
+
107
+
@@ -0,0 +1,403 @@
1
+ import * as THREE from 'three';
2
+ import { rigOptions } from './rig-controller';
3
+ import { getIsDragging } from './rig-mouse-handler';
4
+ import { getState } from '../state/scene-state';
5
+
6
+ // These variables need to be exported so they're available to both modules
7
+ export let bones = [];
8
+ export let boneVisualsGroup = null;
9
+ export let boneMaterial = null;
10
+ export let boneJointMaterial = null;
11
+ export let boneSideMaterial = null;
12
+
13
+ // IK settings
14
+ const IK_CHAIN_LENGTH = 3;
15
+ const IK_ITERATIONS = 10;
16
+ const IK_WEIGHT = 0.1;
17
+
18
+ // Map to track locked bones - Initialize as Set instead of Map for consistency
19
+ export let lockedBones = new Set();
20
+
21
+ /**
22
+ * Find bone by name in the scene
23
+ * @param {string} name - The name of the bone to find
24
+ * @returns {Object|null} The bone object or null if not found
25
+ */
26
+ export function findBoneByName(name) {
27
+ return bones.find(bone => bone.name === name) || null;
28
+ }
29
+
30
+ /**
31
+ * Reset the bones array to empty
32
+ * This function clears all stored bone references, effectively resetting the rig state
33
+ * @returns {void}
34
+ */
35
+ export function resetBones() {
36
+ bones = [];
37
+ lockedBones = new Set();
38
+ }
39
+
40
+ /**
41
+ * Set the bone material
42
+ * @param {Object} material - The material to set
43
+ */
44
+ export function setBoneMaterial(material) {
45
+ boneMaterial = material;
46
+ }
47
+
48
+ /**
49
+ * Set the bone side material
50
+ * @param {Object} material - The material to set
51
+ */
52
+ export function setBoneSideMaterial(material) {
53
+ boneSideMaterial = material;
54
+ }
55
+
56
+ /**
57
+ * Set the bone joint material
58
+ * @param {Object} material - The material to set
59
+ */
60
+ export function setBoneJointMaterial(material) {
61
+ boneJointMaterial = material;
62
+ }
63
+
64
+ /**
65
+ * Find the furthest bone from the root
66
+ * @returns {Object} The furthest bone
67
+ */
68
+ export function findFarthestBone() {
69
+ if (!bones.length) return null;
70
+
71
+ const endBones = [];
72
+
73
+ bones.forEach(bone => {
74
+ let isEndBone = true;
75
+ for (let i = 0; i < bone.children.length; i++) {
76
+ const child = bone.children[i];
77
+ if (child.isBone || child.name.toLowerCase().includes('bone')) {
78
+ isEndBone = false;
79
+ break;
80
+ }
81
+ }
82
+
83
+ if (isEndBone) {
84
+ endBones.push(bone);
85
+ }
86
+ });
87
+
88
+ if (endBones.length > 0) {
89
+ console.log('Found end bone:', endBones[0].name);
90
+ return endBones[0];
91
+ }
92
+
93
+ console.log('No end bones found, using last bone:', bones[bones.length - 1].name);
94
+ return bones[bones.length - 1];
95
+ }
96
+
97
+ /**
98
+ * Reset the bone visual group
99
+ * @param {Object} scene - The Three.js scene
100
+ */
101
+ export function resetBoneVisualGroup(scene) {
102
+ boneVisualsGroup = new THREE.Group();
103
+ boneVisualsGroup.name = "BoneVisualizations";
104
+ boneVisualsGroup.visible = rigOptions.displayRig;
105
+ scene.add(boneVisualsGroup);
106
+ }
107
+
108
+ /**
109
+ * Find associated bone for a control by its name
110
+ * @param {String} controlName - Name of the control
111
+ * @param {Array} bones - Array of bones to search
112
+ * @returns {Object|null} Associated bone or null if not found
113
+ */
114
+ export function findAssociatedBone(controlName, bones) {
115
+ const boneName = controlName.replace('control', 'bone')
116
+ .replace('ctrl', 'bone')
117
+ .replace('handle', 'bone');
118
+
119
+ let matchedBone = null;
120
+ bones.forEach(bone => {
121
+ if (bone.name === boneName || bone.name.includes(boneName) || boneName.includes(bone.name)) {
122
+ matchedBone = bone;
123
+ }
124
+ });
125
+
126
+ return matchedBone;
127
+ }
128
+
129
+ /**
130
+ * Restore locked bone rotations
131
+ */
132
+ export function restoreLockedBoneRotations() {
133
+ lockedBones.forEach((boneData) => {
134
+ if (boneData.bone && boneData.rotation) {
135
+ boneData.bone.rotation.x = boneData.rotation.x;
136
+ boneData.bone.rotation.y = boneData.rotation.y;
137
+ boneData.bone.rotation.z = boneData.rotation.z;
138
+ boneData.bone.updateMatrix();
139
+ }
140
+ });
141
+
142
+ updateAllBoneMatrices();
143
+ }
144
+
145
+ /**
146
+ * Lock or unlock a bone's rotation
147
+ * @param {Object} bone - The bone to lock/unlock
148
+ * @param {boolean} locked - Whether to lock (true) or unlock (false)
149
+ */
150
+ export function toggleBoneLock(bone, locked) {
151
+ if (!bone) return;
152
+
153
+ if (locked) {
154
+ const rotationBackup = new THREE.Euler(
155
+ bone.rotation.x,
156
+ bone.rotation.y,
157
+ bone.rotation.z,
158
+ bone.rotation.order
159
+ );
160
+
161
+ const existingData = Array.from(lockedBones).find(data => data.bone === bone);
162
+ if (existingData) {
163
+ lockedBones.delete(existingData);
164
+ }
165
+
166
+ lockedBones.add({
167
+ bone: bone,
168
+ rotation: rotationBackup,
169
+ uuid: bone.uuid
170
+ });
171
+ console.log(`Locked rotation for bone: ${bone.name}`);
172
+ } else {
173
+ const existingData = Array.from(lockedBones).find(data => data.bone === bone);
174
+ if (existingData) {
175
+ lockedBones.delete(existingData);
176
+ }
177
+ console.log(`Unlocked rotation for bone: ${bone.name}`);
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Update matrices for all bones in the scene
183
+ * @param {boolean} forceUpdate - Force update even if no bones
184
+ */
185
+ export function updateAllBoneMatrices(forceUpdate = false) {
186
+ if (!bones || bones.length === 0) {
187
+ if (!forceUpdate) return;
188
+ }
189
+
190
+ let armature = null;
191
+
192
+ for (let i = 0; i < bones.length; i++) {
193
+ const bone = bones[i];
194
+ if (!bone.parent || !bone.parent.isBone) {
195
+ if (bone.parent) {
196
+ armature = bone.parent;
197
+ break;
198
+ }
199
+ }
200
+ }
201
+
202
+ if (!armature) {
203
+ bones.forEach(bone => {
204
+ if (bone && bone.updateMatrixWorld) {
205
+ bone.updateMatrix();
206
+ bone.updateMatrixWorld(true);
207
+ }
208
+ });
209
+ } else {
210
+ armature.updateMatrixWorld(true);
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Update the bone visual meshes to match bone positions and rotations
216
+ */
217
+ export function updateBoneVisuals() {
218
+ if (boneVisualsGroup) {
219
+ boneVisualsGroup.children.forEach(boneGroup => {
220
+ if (boneGroup.userData.updatePosition) {
221
+ boneGroup.userData.updatePosition();
222
+ }
223
+ });
224
+ }
225
+
226
+ const state = getState();
227
+ const labelGroup = state.scene ? state.scene.getObjectByName("JointLabels") : null;
228
+ if (labelGroup) {
229
+ labelGroup.children.forEach(label => {
230
+ if (label.userData && label.userData.updatePosition) {
231
+ label.userData.updatePosition();
232
+ }
233
+ });
234
+ }
235
+
236
+ const boneLabelsGroup = state.scene ? state.scene.getObjectByName("BoneLabels") : null;
237
+ if (boneLabelsGroup) {
238
+ boneLabelsGroup.children.forEach(label => {
239
+ if (label.userData && label.userData.updatePosition) {
240
+ label.userData.updatePosition();
241
+ }
242
+ });
243
+ }
244
+ }
245
+
246
+ /**
247
+ * Move a chain of bones to reach a target position
248
+ * @param {Object} targetBone - The target bone being controlled
249
+ * @param {THREE.Vector3} targetPosition - The target world position
250
+ */
251
+ export function moveBonesForTarget(targetBone, targetPosition) {
252
+ if (!targetBone) return;
253
+
254
+ const boneChain = [];
255
+ let currentBone = targetBone;
256
+
257
+ while (currentBone && bones.includes(currentBone)) {
258
+ boneChain.unshift(currentBone);
259
+ currentBone = currentBone.parent;
260
+
261
+ if (!currentBone || !currentBone.isBone) break;
262
+ }
263
+
264
+ if (boneChain.length === 0) {
265
+ boneChain.push(targetBone);
266
+ }
267
+
268
+ console.log(`Applying IK to chain of ${boneChain.length} bones`);
269
+
270
+ const rotationBackups = new Map();
271
+ boneChain.forEach(bone => {
272
+ rotationBackups.set(bone.uuid, {
273
+ bone: bone,
274
+ rotation: new THREE.Euler(
275
+ bone.rotation.x,
276
+ bone.rotation.y,
277
+ bone.rotation.z,
278
+ bone.rotation.order
279
+ )
280
+ });
281
+ });
282
+
283
+ applyIKToBoneChain(boneChain, targetPosition);
284
+
285
+ boneChain.forEach(bone => {
286
+ const lockedData = Array.from(lockedBones).find(data => data.bone === bone);
287
+ if (lockedData) {
288
+ const backup = rotationBackups.get(bone.uuid);
289
+ if (backup) {
290
+ bone.rotation.copy(backup.rotation);
291
+ }
292
+ }
293
+ });
294
+
295
+ updateAllBoneMatrices();
296
+ }
297
+
298
+ /**
299
+ * Apply inverse kinematics to a chain of bones to reach a target
300
+ * @param {Array} boneChain - Array of bones from parent to child
301
+ * @param {THREE.Vector3} targetPosition - The target world position
302
+ */
303
+ function applyIKToBoneChain(boneChain, targetPosition) {
304
+ if (boneChain.length === 0) return;
305
+
306
+ const iterations = 10;
307
+
308
+ for (let iteration = 0; iteration < iterations; iteration++) {
309
+ for (let i = boneChain.length - 1; i >= 0; i--) {
310
+ const bone = boneChain[i];
311
+
312
+ const lockedData = Array.from(lockedBones).find(data => data.bone === bone);
313
+ if (lockedData) {
314
+ continue;
315
+ }
316
+
317
+ const endEffector = new THREE.Vector3();
318
+ boneChain[boneChain.length - 1].getWorldPosition(endEffector);
319
+
320
+ const bonePos = new THREE.Vector3();
321
+ bone.getWorldPosition(bonePos);
322
+
323
+ const dirToEffector = new THREE.Vector3().subVectors(endEffector, bonePos).normalize();
324
+ const dirToTarget = new THREE.Vector3().subVectors(targetPosition, bonePos).normalize();
325
+
326
+ let rotAngle = Math.acos(Math.min(1, Math.max(-1, dirToEffector.dot(dirToTarget))));
327
+
328
+ if (rotAngle < 0.01) continue;
329
+
330
+ rotAngle = Math.min(rotAngle, 0.1);
331
+
332
+ const rotAxis = new THREE.Vector3().crossVectors(dirToEffector, dirToTarget).normalize();
333
+
334
+ if (rotAxis.lengthSq() < 0.01) continue;
335
+
336
+ const boneWorldQuat = new THREE.Quaternion();
337
+ bone.getWorldQuaternion(boneWorldQuat);
338
+ const localRotAxis = rotAxis.clone().applyQuaternion(boneWorldQuat.clone().invert()).normalize();
339
+
340
+ bone.rotateOnAxis(localRotAxis, rotAngle);
341
+
342
+ updateBoneChainMatrices(boneChain);
343
+
344
+ const newEffectorPos = new THREE.Vector3();
345
+ boneChain[boneChain.length - 1].getWorldPosition(newEffectorPos);
346
+
347
+ if (newEffectorPos.distanceTo(targetPosition) < 0.1) {
348
+ break;
349
+ }
350
+ }
351
+ }
352
+
353
+ if (boneChain.length >= 2) {
354
+ const lastBone = boneChain[boneChain.length - 1];
355
+ const secondLastBone = boneChain[boneChain.length - 2];
356
+
357
+ const lockedData = Array.from(lockedBones).find(data => data.bone === lastBone);
358
+ if (!lockedData) {
359
+ const secondLastPos = new THREE.Vector3();
360
+ secondLastBone.getWorldPosition(secondLastPos);
361
+
362
+ const dirToTarget = new THREE.Vector3().subVectors(targetPosition, secondLastPos).normalize();
363
+
364
+ const lastBoneDir = new THREE.Vector3(0, 1, 0);
365
+ lastBoneDir.applyQuaternion(lastBone.getWorldQuaternion(new THREE.Quaternion()));
366
+
367
+ const alignQuat = new THREE.Quaternion();
368
+ alignQuat.setFromUnitVectors(lastBoneDir, dirToTarget);
369
+
370
+ const worldQuatInverse = new THREE.Quaternion();
371
+ secondLastBone.getWorldQuaternion(worldQuatInverse).invert();
372
+
373
+ const localQuat = new THREE.Quaternion().multiplyQuaternions(worldQuatInverse, alignQuat);
374
+
375
+ lastBone.quaternion.multiply(localQuat);
376
+
377
+ updateBoneChainMatrices(boneChain);
378
+ }
379
+ }
380
+ }
381
+
382
+ /**
383
+ * Update matrices for a specific chain of bones
384
+ * This helps avoid unnecessary updates to the entire hierarchy
385
+ * @param {Array} boneChain - The bone chain to update
386
+ */
387
+ function updateBoneChainMatrices(boneChain) {
388
+ if (!boneChain || boneChain.length === 0) return;
389
+
390
+ boneChain.forEach(bone => {
391
+ if (bone.updateMatrix && bone.updateMatrixWorld) {
392
+ bone.updateMatrix();
393
+ bone.updateMatrixWorld(true);
394
+ }
395
+ });
396
+ }
397
+
398
+ /**
399
+ * Clear the bone visuals group reference
400
+ */
401
+ export function clearBoneVisualsGroup() {
402
+ boneVisualsGroup = null;
403
+ }