@littlecarlito/blorktools 0.50.3 → 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,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
|
+
|