@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.
- package/bin/cli.js +69 -0
- package/package.json +13 -7
- package/src/asset_debugger/axis-indicator/axis-indicator.css +6 -0
- package/src/asset_debugger/axis-indicator/axis-indicator.html +20 -0
- package/src/asset_debugger/axis-indicator/axis-indicator.js +822 -0
- package/src/asset_debugger/debugger-scene/debugger-scene.css +142 -0
- package/src/asset_debugger/debugger-scene/debugger-scene.html +80 -0
- package/src/asset_debugger/debugger-scene/debugger-scene.js +791 -0
- package/src/asset_debugger/header/header.css +73 -0
- package/src/asset_debugger/header/header.html +24 -0
- package/src/asset_debugger/header/header.js +224 -0
- package/src/asset_debugger/index.html +76 -0
- package/src/asset_debugger/landing-page/landing-page.css +396 -0
- package/src/asset_debugger/landing-page/landing-page.html +81 -0
- package/src/asset_debugger/landing-page/landing-page.js +611 -0
- package/src/asset_debugger/loading-splash/loading-splash.css +195 -0
- package/src/asset_debugger/loading-splash/loading-splash.html +22 -0
- package/src/asset_debugger/loading-splash/loading-splash.js +59 -0
- package/src/asset_debugger/loading-splash/preview-loading-splash.js +66 -0
- package/src/asset_debugger/main.css +14 -0
- package/src/asset_debugger/modals/examples-modal/examples-modal.css +41 -0
- package/src/asset_debugger/modals/examples-modal/examples-modal.html +18 -0
- package/src/asset_debugger/modals/examples-modal/examples-modal.js +111 -0
- package/src/asset_debugger/modals/examples-modal/examples.js +125 -0
- package/src/asset_debugger/modals/html-editor-modal/html-editor-modal.css +452 -0
- package/src/asset_debugger/modals/html-editor-modal/html-editor-modal.html +87 -0
- package/src/asset_debugger/modals/html-editor-modal/html-editor-modal.js +675 -0
- package/src/asset_debugger/modals/mesh-info-modal/mesh-info-modal.css +219 -0
- package/src/asset_debugger/modals/mesh-info-modal/mesh-info-modal.html +20 -0
- package/src/asset_debugger/modals/mesh-info-modal/mesh-info-modal.js +548 -0
- package/src/asset_debugger/modals/settings-modal/settings-modal.css +103 -0
- package/src/asset_debugger/modals/settings-modal/settings-modal.html +158 -0
- package/src/asset_debugger/modals/settings-modal/settings-modal.js +475 -0
- package/src/asset_debugger/panels/asset-panel/asset-panel.css +263 -0
- package/src/asset_debugger/panels/asset-panel/asset-panel.html +123 -0
- package/src/asset_debugger/panels/asset-panel/asset-panel.js +136 -0
- package/src/asset_debugger/panels/asset-panel/atlas-heading/atlas-heading.css +94 -0
- package/src/asset_debugger/panels/asset-panel/atlas-heading/atlas-heading.js +312 -0
- package/src/asset_debugger/panels/asset-panel/mesh-heading/mesh-heading.css +129 -0
- package/src/asset_debugger/panels/asset-panel/mesh-heading/mesh-heading.js +486 -0
- package/src/asset_debugger/panels/asset-panel/rig-heading/rig-heading.css +545 -0
- package/src/asset_debugger/panels/asset-panel/rig-heading/rig-heading.js +538 -0
- package/src/asset_debugger/panels/asset-panel/uv-heading/uv-heading.css +70 -0
- package/src/asset_debugger/panels/asset-panel/uv-heading/uv-heading.js +586 -0
- package/src/asset_debugger/panels/world-panel/world-panel.css +364 -0
- package/src/asset_debugger/panels/world-panel/world-panel.html +173 -0
- package/src/asset_debugger/panels/world-panel/world-panel.js +1891 -0
- package/src/asset_debugger/router.js +190 -0
- package/src/asset_debugger/util/animation/playback/animation-playback-controller.js +150 -0
- package/src/asset_debugger/util/animation/playback/animation-preview-controller.js +316 -0
- package/src/asset_debugger/util/animation/playback/css3d-bounce-controller.js +400 -0
- package/src/asset_debugger/util/animation/playback/css3d-reversal-controller.js +821 -0
- package/src/asset_debugger/util/animation/render/css3d-prerender-controller.js +696 -0
- package/src/asset_debugger/util/animation/render/debug-texture-factory.js +0 -0
- package/src/asset_debugger/util/animation/render/iframe2texture-render-controller.js +199 -0
- package/src/asset_debugger/util/animation/render/image2texture-prerender-controller.js +461 -0
- package/src/asset_debugger/util/animation/render/pbr-material-factory.js +82 -0
- package/src/asset_debugger/util/common.css +280 -0
- package/src/asset_debugger/util/data/animation-classifier.js +323 -0
- package/src/asset_debugger/util/data/duplicate-handler.js +20 -0
- package/src/asset_debugger/util/data/glb-buffer-manager.js +407 -0
- package/src/asset_debugger/util/data/glb-classifier.js +290 -0
- package/src/asset_debugger/util/data/html-formatter.js +76 -0
- package/src/asset_debugger/util/data/html-linter.js +276 -0
- package/src/asset_debugger/util/data/localstorage-manager.js +265 -0
- package/src/asset_debugger/util/data/mesh-html-manager.js +295 -0
- package/src/asset_debugger/util/data/string-serder.js +303 -0
- package/src/asset_debugger/util/data/texture-classifier.js +663 -0
- package/src/asset_debugger/util/data/upload/background-file-handler.js +292 -0
- package/src/asset_debugger/util/data/upload/dropzone-preview-controller.js +396 -0
- package/src/asset_debugger/util/data/upload/file-upload-manager.js +495 -0
- package/src/asset_debugger/util/data/upload/glb-file-handler.js +36 -0
- package/src/asset_debugger/util/data/upload/glb-preview-controller.js +317 -0
- package/src/asset_debugger/util/data/upload/lighting-file-handler.js +194 -0
- package/src/asset_debugger/util/data/upload/model-file-manager.js +104 -0
- package/src/asset_debugger/util/data/upload/texture-file-handler.js +166 -0
- package/src/asset_debugger/util/data/upload/zip-handler.js +686 -0
- package/src/asset_debugger/util/loaders/html2canvas-loader.js +107 -0
- package/src/asset_debugger/util/rig/bone-kinematics.js +403 -0
- package/src/asset_debugger/util/rig/rig-constraint-manager.js +618 -0
- package/src/asset_debugger/util/rig/rig-controller.js +612 -0
- package/src/asset_debugger/util/rig/rig-factory.js +628 -0
- package/src/asset_debugger/util/rig/rig-handle-factory.js +46 -0
- package/src/asset_debugger/util/rig/rig-label-factory.js +441 -0
- package/src/asset_debugger/util/rig/rig-mouse-handler.js +377 -0
- package/src/asset_debugger/util/rig/rig-state-manager.js +175 -0
- package/src/asset_debugger/util/rig/rig-tooltip-manager.js +267 -0
- package/src/asset_debugger/util/rig/rig-ui-factory.js +700 -0
- package/src/asset_debugger/util/scene/background-manager.js +284 -0
- package/src/asset_debugger/util/scene/camera-controller.js +243 -0
- package/src/asset_debugger/util/scene/css3d-debug-controller.js +406 -0
- package/src/asset_debugger/util/scene/css3d-frame-factory.js +113 -0
- package/src/asset_debugger/util/scene/css3d-scene-manager.js +529 -0
- package/src/asset_debugger/util/scene/glb-controller.js +208 -0
- package/src/asset_debugger/util/scene/lighting-manager.js +690 -0
- package/src/asset_debugger/util/scene/threejs-model-manager.js +437 -0
- package/src/asset_debugger/util/scene/threejs-preview-manager.js +207 -0
- package/src/asset_debugger/util/scene/threejs-preview-setup.js +478 -0
- package/src/asset_debugger/util/scene/threejs-scene-controller.js +286 -0
- package/src/asset_debugger/util/scene/ui-manager.js +107 -0
- package/src/asset_debugger/util/state/animation-state.js +128 -0
- package/src/asset_debugger/util/state/css3d-state.js +83 -0
- package/src/asset_debugger/util/state/glb-preview-state.js +31 -0
- package/src/asset_debugger/util/state/log-util.js +197 -0
- package/src/asset_debugger/util/state/scene-state.js +452 -0
- package/src/asset_debugger/util/state/threejs-state.js +54 -0
- package/src/asset_debugger/util/workers/lighting-worker.js +61 -0
- package/src/asset_debugger/util/workers/model-worker.js +109 -0
- package/src/asset_debugger/util/workers/texture-worker.js +54 -0
- package/src/asset_debugger/util/workers/worker-manager.js +212 -0
- package/src/asset_debugger/widgets/mesh-info-widget.js +280 -0
- package/src/index.html +261 -0
- package/src/index.js +8 -0
- 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
|
+
}
|