@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,700 @@
|
|
|
1
|
+
import { jointPreviousValues } from "../../panels/asset-panel/rig-heading/rig-heading";
|
|
2
|
+
import { findBoneByName, lockedBones, toggleBoneLock } from "./bone-kinematics";
|
|
3
|
+
import { jointSettingsDebug, updateConstraintSettingsState } from "./rig-state-manager";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Add bone constraint controls to a bone item element
|
|
7
|
+
* @param {HTMLElement} itemElem - The bone item element
|
|
8
|
+
* @param {Object} item - The bone item data
|
|
9
|
+
* @param {Object} details - Rig details object
|
|
10
|
+
*/
|
|
11
|
+
export function addBoneConstraintControls(itemElem, item, details) {
|
|
12
|
+
const boneName = item.name;
|
|
13
|
+
const bone = findBoneByName(boneName);
|
|
14
|
+
|
|
15
|
+
if (!bone) return;
|
|
16
|
+
|
|
17
|
+
const constraintContainer = document.createElement('div');
|
|
18
|
+
constraintContainer.className = 'rig-constraint-container';
|
|
19
|
+
|
|
20
|
+
const constraintLabel = document.createElement('label');
|
|
21
|
+
constraintLabel.className = 'rig-constraint-label';
|
|
22
|
+
constraintLabel.textContent = 'Constraint:';
|
|
23
|
+
|
|
24
|
+
const constraintSelect = document.createElement('select');
|
|
25
|
+
constraintSelect.className = 'rig-constraint-select';
|
|
26
|
+
constraintSelect.setAttribute('data-bone-constraint', 'true');
|
|
27
|
+
constraintSelect.setAttribute('data-bone-name', boneName);
|
|
28
|
+
|
|
29
|
+
const constraintOptions = [
|
|
30
|
+
{ value: 'NONE', label: 'None' },
|
|
31
|
+
{ value: 'FIXED_POSITION', label: 'Fixed Position' },
|
|
32
|
+
{ value: 'SINGLE_AXIS_ROTATION', label: 'Single Axis Rotation' },
|
|
33
|
+
{ value: 'LIMIT_ROTATION_XYZ', label: 'Limit Rotation (XYZ)' },
|
|
34
|
+
{ value: 'DYNAMIC_SPRING', label: 'Dynamic Spring' }
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
constraintOptions.forEach(option => {
|
|
38
|
+
const optionElem = document.createElement('option');
|
|
39
|
+
optionElem.value = option.value;
|
|
40
|
+
optionElem.textContent = option.label;
|
|
41
|
+
constraintSelect.appendChild(optionElem);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
let initialConstraintType = 'NONE';
|
|
45
|
+
|
|
46
|
+
if (bone.userData && bone.userData.constraints) {
|
|
47
|
+
const constraintTypeMap = {
|
|
48
|
+
'none': 'NONE',
|
|
49
|
+
'fixed': 'FIXED_POSITION',
|
|
50
|
+
'hinge': 'SINGLE_AXIS_ROTATION',
|
|
51
|
+
'limitRotation': 'LIMIT_ROTATION_XYZ',
|
|
52
|
+
'spring': 'DYNAMIC_SPRING'
|
|
53
|
+
};
|
|
54
|
+
initialConstraintType = constraintTypeMap[bone.userData.constraints.type] || 'NONE';
|
|
55
|
+
} else if (details.constraints) {
|
|
56
|
+
const existingConstraint = details.constraints.find(c =>
|
|
57
|
+
c.boneName === boneName || c.nodeName === boneName);
|
|
58
|
+
if (existingConstraint) {
|
|
59
|
+
const constraintTypeMap = {
|
|
60
|
+
'none': 'NONE',
|
|
61
|
+
'fixed': 'FIXED_POSITION',
|
|
62
|
+
'hinge': 'SINGLE_AXIS_ROTATION',
|
|
63
|
+
'limitRotation': 'LIMIT_ROTATION_XYZ',
|
|
64
|
+
'spring': 'DYNAMIC_SPRING'
|
|
65
|
+
};
|
|
66
|
+
initialConstraintType = constraintTypeMap[existingConstraint.type] || 'NONE';
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
item.constraintType = initialConstraintType;
|
|
71
|
+
|
|
72
|
+
if (bone.userData && bone.userData.constraints) {
|
|
73
|
+
if (bone.userData.constraints.type === 'hinge') {
|
|
74
|
+
item.hingeAxis = bone.userData.hinge?.axis || 'y';
|
|
75
|
+
item.hingeMin = bone.userData.hinge?.min || -Math.PI/2;
|
|
76
|
+
item.hingeMax = bone.userData.hinge?.max || Math.PI/2;
|
|
77
|
+
} else if (bone.userData.constraints.type === 'limitRotation') {
|
|
78
|
+
item.rotationLimits = bone.userData.rotationLimits || {
|
|
79
|
+
x: { min: -Math.PI/4, max: Math.PI/4 },
|
|
80
|
+
y: { min: -Math.PI/4, max: Math.PI/4 },
|
|
81
|
+
z: { min: -Math.PI/4, max: Math.PI/4 }
|
|
82
|
+
};
|
|
83
|
+
} else if (bone.userData.constraints.type === 'spring') {
|
|
84
|
+
item.spring = {
|
|
85
|
+
stiffness: bone.userData.spring?.stiffness || 50,
|
|
86
|
+
damping: bone.userData.spring?.damping || 5
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
constraintSelect.value = initialConstraintType;
|
|
92
|
+
jointPreviousValues.set(boneName, initialConstraintType);
|
|
93
|
+
|
|
94
|
+
constraintSelect.addEventListener('change', () => {
|
|
95
|
+
item.constraintType = constraintSelect.value;
|
|
96
|
+
|
|
97
|
+
if (jointSettingsDebug) {
|
|
98
|
+
console.log(`Bone constraint ${boneName} changed to "${constraintSelect.value}"`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const controlSelectors = [
|
|
102
|
+
'.rig-constraint-controls',
|
|
103
|
+
'.rig-axis-container',
|
|
104
|
+
'.rig-limits-container',
|
|
105
|
+
'.rig-rotation-limits-container',
|
|
106
|
+
'.rig-spring-container'
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
controlSelectors.forEach(selector => {
|
|
110
|
+
const existingControls = itemElem.querySelectorAll(selector);
|
|
111
|
+
existingControls.forEach(control => {
|
|
112
|
+
itemElem.removeChild(control);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
if (constraintSelect.value === 'SINGLE_AXIS_ROTATION') {
|
|
117
|
+
addHingeAxisSelector(itemElem, item);
|
|
118
|
+
} else if (constraintSelect.value === 'LIMIT_ROTATION_XYZ') {
|
|
119
|
+
addRotationLimitControls(itemElem, item);
|
|
120
|
+
} else if (constraintSelect.value === 'DYNAMIC_SPRING') {
|
|
121
|
+
addSpringControls(itemElem, item);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
updateConstraintSettingsState();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
constraintContainer.appendChild(constraintLabel);
|
|
128
|
+
constraintContainer.appendChild(constraintSelect);
|
|
129
|
+
itemElem.appendChild(constraintContainer);
|
|
130
|
+
|
|
131
|
+
if (initialConstraintType === 'SINGLE_AXIS_ROTATION') {
|
|
132
|
+
addHingeAxisSelector(itemElem, item);
|
|
133
|
+
} else if (initialConstraintType === 'LIMIT_ROTATION_XYZ') {
|
|
134
|
+
addRotationLimitControls(itemElem, item);
|
|
135
|
+
} else if (initialConstraintType === 'DYNAMIC_SPRING') {
|
|
136
|
+
addSpringControls(itemElem, item);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const lockContainer = document.createElement('div');
|
|
140
|
+
lockContainer.className = 'rig-lock-container';
|
|
141
|
+
|
|
142
|
+
const lockLabel = document.createElement('label');
|
|
143
|
+
lockLabel.className = 'rig-lock-label';
|
|
144
|
+
lockLabel.textContent = 'Lock Rotation:';
|
|
145
|
+
|
|
146
|
+
const lockCheckbox = document.createElement('input');
|
|
147
|
+
lockCheckbox.type = 'checkbox';
|
|
148
|
+
lockCheckbox.className = 'rig-lock-checkbox';
|
|
149
|
+
lockCheckbox.checked = lockedBones.has(bone.uuid);
|
|
150
|
+
|
|
151
|
+
lockCheckbox.addEventListener('change', (e) => {
|
|
152
|
+
toggleBoneLock(bone, e.target.checked);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
lockContainer.appendChild(lockLabel);
|
|
156
|
+
lockContainer.appendChild(lockCheckbox);
|
|
157
|
+
itemElem.appendChild(lockContainer);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Add hinge axis selector to a joint item
|
|
162
|
+
* @param {HTMLElement} itemElem - The joint item element
|
|
163
|
+
* @param {Object} item - The joint data
|
|
164
|
+
*/
|
|
165
|
+
function addHingeAxisSelector(itemElem, item) {
|
|
166
|
+
const axisContainer = document.createElement('div');
|
|
167
|
+
axisContainer.className = 'rig-axis-container rig-constraint-controls';
|
|
168
|
+
|
|
169
|
+
const axisLabel = document.createElement('label');
|
|
170
|
+
axisLabel.className = 'rig-axis-label';
|
|
171
|
+
axisLabel.textContent = 'Locked Axis:';
|
|
172
|
+
|
|
173
|
+
const axisSelect = document.createElement('select');
|
|
174
|
+
axisSelect.className = 'rig-axis-select';
|
|
175
|
+
|
|
176
|
+
const axes = [
|
|
177
|
+
{ value: 'x', label: 'X Axis' },
|
|
178
|
+
{ value: 'y', label: 'Y Axis' },
|
|
179
|
+
{ value: 'z', label: 'Z Axis' }
|
|
180
|
+
];
|
|
181
|
+
|
|
182
|
+
axes.forEach(axis => {
|
|
183
|
+
const option = document.createElement('option');
|
|
184
|
+
option.value = axis.value;
|
|
185
|
+
option.textContent = axis.label;
|
|
186
|
+
axisSelect.appendChild(option);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Set initial value or default to Y
|
|
190
|
+
if (!item.hingeAxis) {
|
|
191
|
+
item.hingeAxis = 'y';
|
|
192
|
+
}
|
|
193
|
+
axisSelect.value = item.hingeAxis;
|
|
194
|
+
|
|
195
|
+
axisSelect.addEventListener('change', () => {
|
|
196
|
+
item.hingeAxis = axisSelect.value;
|
|
197
|
+
updateConstraintSettingsState();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
axisContainer.appendChild(axisLabel);
|
|
201
|
+
axisContainer.appendChild(axisSelect);
|
|
202
|
+
itemElem.appendChild(axisContainer);
|
|
203
|
+
|
|
204
|
+
// Add min/max angle inputs
|
|
205
|
+
const limitsContainer = document.createElement('div');
|
|
206
|
+
limitsContainer.className = 'rig-limits-container';
|
|
207
|
+
|
|
208
|
+
// Min angle
|
|
209
|
+
const minContainer = document.createElement('div');
|
|
210
|
+
minContainer.className = 'rig-min-container';
|
|
211
|
+
|
|
212
|
+
const minLabel = document.createElement('label');
|
|
213
|
+
minLabel.textContent = 'Min Angle:';
|
|
214
|
+
minLabel.className = 'rig-min-label';
|
|
215
|
+
|
|
216
|
+
const minControlWrapper = document.createElement('div');
|
|
217
|
+
minControlWrapper.className = 'rig-angle-control-wrapper';
|
|
218
|
+
|
|
219
|
+
// Add decrement button
|
|
220
|
+
const minDecBtn = document.createElement('button');
|
|
221
|
+
minDecBtn.className = 'rig-angle-btn';
|
|
222
|
+
minDecBtn.textContent = '−';
|
|
223
|
+
minDecBtn.type = 'button';
|
|
224
|
+
|
|
225
|
+
const minInput = document.createElement('input');
|
|
226
|
+
minInput.type = 'number';
|
|
227
|
+
minInput.className = 'rig-min-input';
|
|
228
|
+
minInput.min = -180;
|
|
229
|
+
minInput.max = 180;
|
|
230
|
+
minInput.step = 5;
|
|
231
|
+
minInput.value = item.hingeMin ? Math.round(item.hingeMin * 180 / Math.PI) : -90;
|
|
232
|
+
|
|
233
|
+
// Add increment button
|
|
234
|
+
const minIncBtn = document.createElement('button');
|
|
235
|
+
minIncBtn.className = 'rig-angle-btn';
|
|
236
|
+
minIncBtn.textContent = '+';
|
|
237
|
+
minIncBtn.type = 'button';
|
|
238
|
+
|
|
239
|
+
// Update item data when input changes
|
|
240
|
+
minInput.addEventListener('change', () => {
|
|
241
|
+
item.hingeMin = minInput.value * Math.PI / 180;
|
|
242
|
+
updateConstraintSettingsState();
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// Button event listeners
|
|
246
|
+
minDecBtn.addEventListener('click', () => {
|
|
247
|
+
minInput.value = parseInt(minInput.value) - parseInt(minInput.step);
|
|
248
|
+
// Trigger the change event manually
|
|
249
|
+
minInput.dispatchEvent(new Event('change'));
|
|
250
|
+
// Explicitly update constraint settings state for button click
|
|
251
|
+
updateConstraintSettingsState();
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
minIncBtn.addEventListener('click', () => {
|
|
255
|
+
minInput.value = parseInt(minInput.value) + parseInt(minInput.step);
|
|
256
|
+
// Trigger the change event manually
|
|
257
|
+
minInput.dispatchEvent(new Event('change'));
|
|
258
|
+
// Explicitly update constraint settings state for button click
|
|
259
|
+
updateConstraintSettingsState();
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
minControlWrapper.appendChild(minDecBtn);
|
|
263
|
+
minControlWrapper.appendChild(minInput);
|
|
264
|
+
minControlWrapper.appendChild(minIncBtn);
|
|
265
|
+
|
|
266
|
+
minContainer.appendChild(minLabel);
|
|
267
|
+
minContainer.appendChild(minControlWrapper);
|
|
268
|
+
|
|
269
|
+
// Max angle
|
|
270
|
+
const maxContainer = document.createElement('div');
|
|
271
|
+
maxContainer.className = 'rig-max-container';
|
|
272
|
+
|
|
273
|
+
const maxLabel = document.createElement('label');
|
|
274
|
+
maxLabel.textContent = 'Max Angle:';
|
|
275
|
+
maxLabel.className = 'rig-max-label';
|
|
276
|
+
|
|
277
|
+
const maxControlWrapper = document.createElement('div');
|
|
278
|
+
maxControlWrapper.className = 'rig-angle-control-wrapper';
|
|
279
|
+
|
|
280
|
+
// Add decrement button
|
|
281
|
+
const maxDecBtn = document.createElement('button');
|
|
282
|
+
maxDecBtn.className = 'rig-angle-btn';
|
|
283
|
+
maxDecBtn.textContent = '−';
|
|
284
|
+
maxDecBtn.type = 'button';
|
|
285
|
+
|
|
286
|
+
const maxInput = document.createElement('input');
|
|
287
|
+
maxInput.type = 'number';
|
|
288
|
+
maxInput.className = 'rig-max-input';
|
|
289
|
+
maxInput.min = -180;
|
|
290
|
+
maxInput.max = 180;
|
|
291
|
+
maxInput.step = 5;
|
|
292
|
+
maxInput.value = item.hingeMax ? Math.round(item.hingeMax * 180 / Math.PI) : 90;
|
|
293
|
+
|
|
294
|
+
// Add increment button
|
|
295
|
+
const maxIncBtn = document.createElement('button');
|
|
296
|
+
maxIncBtn.className = 'rig-angle-btn';
|
|
297
|
+
maxIncBtn.textContent = '+';
|
|
298
|
+
maxIncBtn.type = 'button';
|
|
299
|
+
|
|
300
|
+
maxInput.addEventListener('change', () => {
|
|
301
|
+
item.hingeMax = maxInput.value * Math.PI / 180;
|
|
302
|
+
updateConstraintSettingsState();
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// Button event listeners
|
|
306
|
+
maxDecBtn.addEventListener('click', () => {
|
|
307
|
+
maxInput.value = parseInt(maxInput.value) - parseInt(maxInput.step);
|
|
308
|
+
// Trigger the change event manually
|
|
309
|
+
maxInput.dispatchEvent(new Event('change'));
|
|
310
|
+
// Explicitly update constraint settings state for button click
|
|
311
|
+
updateConstraintSettingsState();
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
maxIncBtn.addEventListener('click', () => {
|
|
315
|
+
maxInput.value = parseInt(maxInput.value) + parseInt(maxInput.step);
|
|
316
|
+
// Trigger the change event manually
|
|
317
|
+
maxInput.dispatchEvent(new Event('change'));
|
|
318
|
+
// Explicitly update constraint settings state for button click
|
|
319
|
+
updateConstraintSettingsState();
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
maxControlWrapper.appendChild(maxDecBtn);
|
|
323
|
+
maxControlWrapper.appendChild(maxInput);
|
|
324
|
+
maxControlWrapper.appendChild(maxIncBtn);
|
|
325
|
+
|
|
326
|
+
maxContainer.appendChild(maxLabel);
|
|
327
|
+
maxContainer.appendChild(maxControlWrapper);
|
|
328
|
+
|
|
329
|
+
limitsContainer.appendChild(minContainer);
|
|
330
|
+
limitsContainer.appendChild(maxContainer);
|
|
331
|
+
itemElem.appendChild(limitsContainer);
|
|
332
|
+
|
|
333
|
+
// Store original values immediately when control is created
|
|
334
|
+
const boneName = itemElem.closest('.rig-item')?.querySelector('select[data-bone-constraint]')?.getAttribute('data-bone-name');
|
|
335
|
+
if (boneName) {
|
|
336
|
+
jointPreviousValues.set(`${boneName}:hinge-config`, {
|
|
337
|
+
axis: axisSelect.value,
|
|
338
|
+
min: parseInt(minInput.value),
|
|
339
|
+
max: parseInt(maxInput.value)
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
if (jointSettingsDebug) {
|
|
343
|
+
console.log(`Stored initial hinge config for ${boneName}:`, jointPreviousValues.get(`${boneName}:hinge-config`));
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Add rotation limit controls to a joint item
|
|
350
|
+
* @param {HTMLElement} itemElem - The joint item element
|
|
351
|
+
* @param {Object} item - The joint data
|
|
352
|
+
*/
|
|
353
|
+
function addRotationLimitControls(itemElem, item) {
|
|
354
|
+
// Initialize limits object if not exists
|
|
355
|
+
if (!item.rotationLimits) {
|
|
356
|
+
item.rotationLimits = {
|
|
357
|
+
x: { min: -Math.PI/4, max: Math.PI/4 },
|
|
358
|
+
y: { min: -Math.PI/4, max: Math.PI/4 },
|
|
359
|
+
z: { min: -Math.PI/4, max: Math.PI/4 }
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const limitsContainer = document.createElement('div');
|
|
364
|
+
limitsContainer.className = 'rig-rotation-limits-container rig-constraint-controls';
|
|
365
|
+
|
|
366
|
+
const axisLabels = ['X', 'Y', 'Z'];
|
|
367
|
+
|
|
368
|
+
// Create a config object to store initial values
|
|
369
|
+
const initialConfig = { x: {}, y: {}, z: {} };
|
|
370
|
+
|
|
371
|
+
// Create controls for each axis
|
|
372
|
+
axisLabels.forEach(axis => {
|
|
373
|
+
const axisLower = axis.toLowerCase();
|
|
374
|
+
|
|
375
|
+
const axisContainer = document.createElement('div');
|
|
376
|
+
axisContainer.className = 'rig-axis-limits';
|
|
377
|
+
|
|
378
|
+
const axisLabel = document.createElement('div');
|
|
379
|
+
axisLabel.className = 'rig-axis-limit-label';
|
|
380
|
+
axisLabel.textContent = `${axis} Axis:`;
|
|
381
|
+
axisContainer.appendChild(axisLabel);
|
|
382
|
+
|
|
383
|
+
// Min limit
|
|
384
|
+
const minContainer = document.createElement('div');
|
|
385
|
+
minContainer.className = 'rig-min-limit';
|
|
386
|
+
|
|
387
|
+
const minLabel = document.createElement('label');
|
|
388
|
+
minLabel.textContent = 'Min:';
|
|
389
|
+
|
|
390
|
+
const minControlWrapper = document.createElement('div');
|
|
391
|
+
minControlWrapper.className = 'rig-angle-control-wrapper';
|
|
392
|
+
|
|
393
|
+
// Add decrement button
|
|
394
|
+
const minDecBtn = document.createElement('button');
|
|
395
|
+
minDecBtn.className = 'rig-angle-btn';
|
|
396
|
+
minDecBtn.textContent = '−';
|
|
397
|
+
minDecBtn.type = 'button';
|
|
398
|
+
|
|
399
|
+
const minInput = document.createElement('input');
|
|
400
|
+
minInput.type = 'number';
|
|
401
|
+
minInput.min = -180;
|
|
402
|
+
minInput.max = 180;
|
|
403
|
+
minInput.step = 5;
|
|
404
|
+
minInput.value = Math.round((item.rotationLimits[axisLower]?.min || -45) * 180 / Math.PI);
|
|
405
|
+
|
|
406
|
+
// Store initial value in config
|
|
407
|
+
initialConfig[axisLower].min = parseInt(minInput.value);
|
|
408
|
+
|
|
409
|
+
// Add increment button
|
|
410
|
+
const minIncBtn = document.createElement('button');
|
|
411
|
+
minIncBtn.className = 'rig-angle-btn';
|
|
412
|
+
minIncBtn.textContent = '+';
|
|
413
|
+
minIncBtn.type = 'button';
|
|
414
|
+
|
|
415
|
+
minInput.addEventListener('change', () => {
|
|
416
|
+
if (!item.rotationLimits[axisLower]) {
|
|
417
|
+
item.rotationLimits[axisLower] = {};
|
|
418
|
+
}
|
|
419
|
+
item.rotationLimits[axisLower].min = minInput.value * Math.PI / 180;
|
|
420
|
+
updateConstraintSettingsState();
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
// Button event listeners
|
|
424
|
+
minDecBtn.addEventListener('click', () => {
|
|
425
|
+
minInput.value = parseInt(minInput.value) - parseInt(minInput.step || 5);
|
|
426
|
+
// Trigger the change event manually
|
|
427
|
+
minInput.dispatchEvent(new Event('change'));
|
|
428
|
+
// Explicitly update constraint settings state for button click
|
|
429
|
+
updateConstraintSettingsState();
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
minIncBtn.addEventListener('click', () => {
|
|
433
|
+
minInput.value = parseInt(minInput.value) + parseInt(minInput.step || 5);
|
|
434
|
+
// Trigger the change event manually
|
|
435
|
+
minInput.dispatchEvent(new Event('change'));
|
|
436
|
+
// Explicitly update constraint settings state for button click
|
|
437
|
+
updateConstraintSettingsState();
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
minControlWrapper.appendChild(minDecBtn);
|
|
441
|
+
minControlWrapper.appendChild(minInput);
|
|
442
|
+
minControlWrapper.appendChild(minIncBtn);
|
|
443
|
+
|
|
444
|
+
minContainer.appendChild(minLabel);
|
|
445
|
+
minContainer.appendChild(minControlWrapper);
|
|
446
|
+
|
|
447
|
+
// Max limit
|
|
448
|
+
const maxContainer = document.createElement('div');
|
|
449
|
+
maxContainer.className = 'rig-max-limit';
|
|
450
|
+
|
|
451
|
+
const maxLabel = document.createElement('label');
|
|
452
|
+
maxLabel.textContent = 'Max:';
|
|
453
|
+
|
|
454
|
+
const maxControlWrapper = document.createElement('div');
|
|
455
|
+
maxControlWrapper.className = 'rig-angle-control-wrapper';
|
|
456
|
+
|
|
457
|
+
// Add decrement button
|
|
458
|
+
const maxDecBtn = document.createElement('button');
|
|
459
|
+
maxDecBtn.className = 'rig-angle-btn';
|
|
460
|
+
maxDecBtn.textContent = '−';
|
|
461
|
+
maxDecBtn.type = 'button';
|
|
462
|
+
|
|
463
|
+
const maxInput = document.createElement('input');
|
|
464
|
+
maxInput.type = 'number';
|
|
465
|
+
maxInput.min = -180;
|
|
466
|
+
maxInput.max = 180;
|
|
467
|
+
maxInput.step = 5;
|
|
468
|
+
maxInput.value = Math.round((item.rotationLimits[axisLower]?.max || 45) * 180 / Math.PI);
|
|
469
|
+
|
|
470
|
+
// Store initial value in config
|
|
471
|
+
initialConfig[axisLower].max = parseInt(maxInput.value);
|
|
472
|
+
|
|
473
|
+
// Add increment button
|
|
474
|
+
const maxIncBtn = document.createElement('button');
|
|
475
|
+
maxIncBtn.className = 'rig-angle-btn';
|
|
476
|
+
maxIncBtn.textContent = '+';
|
|
477
|
+
maxIncBtn.type = 'button';
|
|
478
|
+
|
|
479
|
+
maxInput.addEventListener('change', () => {
|
|
480
|
+
if (!item.rotationLimits[axisLower]) {
|
|
481
|
+
item.rotationLimits[axisLower] = {};
|
|
482
|
+
}
|
|
483
|
+
item.rotationLimits[axisLower].max = maxInput.value * Math.PI / 180;
|
|
484
|
+
updateConstraintSettingsState();
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
// Button event listeners
|
|
488
|
+
maxDecBtn.addEventListener('click', () => {
|
|
489
|
+
maxInput.value = parseInt(maxInput.value) - parseInt(maxInput.step || 5);
|
|
490
|
+
// Trigger the change event manually
|
|
491
|
+
maxInput.dispatchEvent(new Event('change'));
|
|
492
|
+
// Explicitly update constraint settings state for button click
|
|
493
|
+
updateConstraintSettingsState();
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
maxIncBtn.addEventListener('click', () => {
|
|
497
|
+
maxInput.value = parseInt(maxInput.value) + parseInt(maxInput.step || 5);
|
|
498
|
+
// Trigger the change event manually
|
|
499
|
+
maxInput.dispatchEvent(new Event('change'));
|
|
500
|
+
// Explicitly update constraint settings state for button click
|
|
501
|
+
updateConstraintSettingsState();
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
maxControlWrapper.appendChild(maxDecBtn);
|
|
505
|
+
maxControlWrapper.appendChild(maxInput);
|
|
506
|
+
maxControlWrapper.appendChild(maxIncBtn);
|
|
507
|
+
|
|
508
|
+
maxContainer.appendChild(maxLabel);
|
|
509
|
+
maxContainer.appendChild(maxControlWrapper);
|
|
510
|
+
|
|
511
|
+
axisContainer.appendChild(minContainer);
|
|
512
|
+
axisContainer.appendChild(maxContainer);
|
|
513
|
+
limitsContainer.appendChild(axisContainer);
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
itemElem.appendChild(limitsContainer);
|
|
517
|
+
|
|
518
|
+
// Store initial rotation limits immediately when control is created
|
|
519
|
+
const boneName = itemElem.closest('.rig-item')?.querySelector('select[data-bone-constraint]')?.getAttribute('data-bone-name');
|
|
520
|
+
if (boneName) {
|
|
521
|
+
jointPreviousValues.set(`${boneName}:rotation-limits`, JSON.parse(JSON.stringify(initialConfig)));
|
|
522
|
+
|
|
523
|
+
if (jointSettingsDebug) {
|
|
524
|
+
console.log(`Stored initial rotation limits for ${boneName}:`, jointPreviousValues.get(`${boneName}:rotation-limits`));
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Add spring controls to a joint item
|
|
531
|
+
* @param {HTMLElement} itemElem - The joint item element
|
|
532
|
+
* @param {Object} item - The joint data
|
|
533
|
+
*/
|
|
534
|
+
function addSpringControls(itemElem, item) {
|
|
535
|
+
// Initialize spring properties if not exists
|
|
536
|
+
if (!item.spring) {
|
|
537
|
+
item.spring = {
|
|
538
|
+
stiffness: 50,
|
|
539
|
+
damping: 5,
|
|
540
|
+
gravity: 1.0
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
const springContainer = document.createElement('div');
|
|
545
|
+
springContainer.className = 'rig-spring-container rig-constraint-controls';
|
|
546
|
+
|
|
547
|
+
// Stiffness control
|
|
548
|
+
const stiffnessContainer = document.createElement('div');
|
|
549
|
+
stiffnessContainer.className = 'rig-stiffness-container';
|
|
550
|
+
|
|
551
|
+
const stiffnessLabel = document.createElement('label');
|
|
552
|
+
stiffnessLabel.textContent = 'Stiffness:';
|
|
553
|
+
stiffnessLabel.className = 'rig-stiffness-label';
|
|
554
|
+
|
|
555
|
+
// Create slider container
|
|
556
|
+
const stiffnessSliderContainer = document.createElement('div');
|
|
557
|
+
stiffnessSliderContainer.className = 'rig-slider-container';
|
|
558
|
+
|
|
559
|
+
const stiffnessInput = document.createElement('input');
|
|
560
|
+
stiffnessInput.type = 'range';
|
|
561
|
+
stiffnessInput.min = 1;
|
|
562
|
+
stiffnessInput.max = 100;
|
|
563
|
+
stiffnessInput.value = item.spring.stiffness || 50;
|
|
564
|
+
stiffnessInput.className = 'rig-stiffness-input';
|
|
565
|
+
|
|
566
|
+
const stiffnessValue = document.createElement('span');
|
|
567
|
+
stiffnessValue.textContent = stiffnessInput.value;
|
|
568
|
+
stiffnessValue.className = 'rig-stiffness-value';
|
|
569
|
+
|
|
570
|
+
stiffnessInput.addEventListener('input', () => {
|
|
571
|
+
item.spring.stiffness = parseInt(stiffnessInput.value);
|
|
572
|
+
stiffnessValue.textContent = stiffnessInput.value;
|
|
573
|
+
updateConstraintSettingsState();
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
// Assemble stiffness controls
|
|
577
|
+
stiffnessSliderContainer.appendChild(stiffnessInput);
|
|
578
|
+
stiffnessSliderContainer.appendChild(stiffnessValue);
|
|
579
|
+
stiffnessContainer.appendChild(stiffnessLabel);
|
|
580
|
+
stiffnessContainer.appendChild(stiffnessSliderContainer);
|
|
581
|
+
|
|
582
|
+
// Damping control
|
|
583
|
+
const dampingContainer = document.createElement('div');
|
|
584
|
+
dampingContainer.className = 'rig-damping-container';
|
|
585
|
+
|
|
586
|
+
const dampingLabel = document.createElement('label');
|
|
587
|
+
dampingLabel.textContent = 'Damping:';
|
|
588
|
+
dampingLabel.className = 'rig-damping-label';
|
|
589
|
+
|
|
590
|
+
// Create slider container
|
|
591
|
+
const dampingSliderContainer = document.createElement('div');
|
|
592
|
+
dampingSliderContainer.className = 'rig-slider-container';
|
|
593
|
+
|
|
594
|
+
const dampingInput = document.createElement('input');
|
|
595
|
+
dampingInput.type = 'range';
|
|
596
|
+
dampingInput.min = 0;
|
|
597
|
+
dampingInput.max = 20;
|
|
598
|
+
dampingInput.value = item.spring.damping || 5;
|
|
599
|
+
dampingInput.className = 'rig-damping-input';
|
|
600
|
+
|
|
601
|
+
const dampingValue = document.createElement('span');
|
|
602
|
+
dampingValue.textContent = dampingInput.value;
|
|
603
|
+
dampingValue.className = 'rig-damping-value';
|
|
604
|
+
|
|
605
|
+
dampingInput.addEventListener('input', () => {
|
|
606
|
+
item.spring.damping = parseInt(dampingInput.value);
|
|
607
|
+
dampingValue.textContent = dampingInput.value;
|
|
608
|
+
updateConstraintSettingsState();
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
// Assemble damping controls
|
|
612
|
+
dampingSliderContainer.appendChild(dampingInput);
|
|
613
|
+
dampingSliderContainer.appendChild(dampingValue);
|
|
614
|
+
dampingContainer.appendChild(dampingLabel);
|
|
615
|
+
dampingContainer.appendChild(dampingSliderContainer);
|
|
616
|
+
|
|
617
|
+
// Gravity influence control
|
|
618
|
+
const gravityContainer = document.createElement('div');
|
|
619
|
+
gravityContainer.className = 'rig-gravity-container';
|
|
620
|
+
|
|
621
|
+
const gravityLabel = document.createElement('label');
|
|
622
|
+
gravityLabel.textContent = 'Gravity:';
|
|
623
|
+
gravityLabel.className = 'rig-gravity-label';
|
|
624
|
+
|
|
625
|
+
// Create slider container
|
|
626
|
+
const gravitySliderContainer = document.createElement('div');
|
|
627
|
+
gravitySliderContainer.className = 'rig-slider-container';
|
|
628
|
+
|
|
629
|
+
const gravityInput = document.createElement('input');
|
|
630
|
+
gravityInput.type = 'range';
|
|
631
|
+
gravityInput.min = 0;
|
|
632
|
+
gravityInput.max = 20;
|
|
633
|
+
gravityInput.step = 0.1;
|
|
634
|
+
gravityInput.value = item.spring.gravity || 1.0;
|
|
635
|
+
gravityInput.className = 'rig-gravity-input';
|
|
636
|
+
|
|
637
|
+
const gravityValue = document.createElement('span');
|
|
638
|
+
gravityValue.textContent = gravityInput.value;
|
|
639
|
+
gravityValue.className = 'rig-gravity-value';
|
|
640
|
+
|
|
641
|
+
gravityInput.addEventListener('input', () => {
|
|
642
|
+
item.spring.gravity = parseFloat(gravityInput.value);
|
|
643
|
+
gravityValue.textContent = gravityInput.value;
|
|
644
|
+
updateConstraintSettingsState();
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
// Assemble gravity controls
|
|
648
|
+
gravitySliderContainer.appendChild(gravityInput);
|
|
649
|
+
gravitySliderContainer.appendChild(gravityValue);
|
|
650
|
+
gravityContainer.appendChild(gravityLabel);
|
|
651
|
+
gravityContainer.appendChild(gravitySliderContainer);
|
|
652
|
+
|
|
653
|
+
springContainer.appendChild(stiffnessContainer);
|
|
654
|
+
springContainer.appendChild(dampingContainer);
|
|
655
|
+
springContainer.appendChild(gravityContainer);
|
|
656
|
+
itemElem.appendChild(springContainer);
|
|
657
|
+
|
|
658
|
+
// Store original values immediately when control is created
|
|
659
|
+
const boneName = itemElem.closest('.rig-item')?.querySelector('select[data-bone-constraint]')?.getAttribute('data-bone-name');
|
|
660
|
+
if (boneName) {
|
|
661
|
+
jointPreviousValues.set(`${boneName}:spring-config`, {
|
|
662
|
+
stiffness: parseInt(stiffnessInput.value),
|
|
663
|
+
damping: parseInt(dampingInput.value),
|
|
664
|
+
gravity: parseFloat(gravityInput.value)
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
if (jointSettingsDebug) {
|
|
668
|
+
console.log(`Stored initial spring config for ${boneName}:`, jointPreviousValues.get(`${boneName}:spring-config`));
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* Disable the Apply Changes button
|
|
675
|
+
* @param {HTMLElement} button - The button to disable
|
|
676
|
+
*/
|
|
677
|
+
export function disableApplyButton(button) {
|
|
678
|
+
button.disabled = true;
|
|
679
|
+
button.style.backgroundColor = 'rgba(0,0,0,0.2)';
|
|
680
|
+
button.style.color = '#ccc';
|
|
681
|
+
button.style.cursor = 'not-allowed';
|
|
682
|
+
button.style.opacity = '0.5';
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* Enable the Apply Changes button
|
|
687
|
+
* @param {HTMLElement} button - The button to enable
|
|
688
|
+
*/
|
|
689
|
+
export function enableApplyButton(button) {
|
|
690
|
+
if (button) {
|
|
691
|
+
button.removeAttribute('disabled');
|
|
692
|
+
button.classList.remove('disabled');
|
|
693
|
+
|
|
694
|
+
// Restore visual appearance to match enabled state
|
|
695
|
+
button.style.backgroundColor = '#3f51b5'; // Standard blue button color
|
|
696
|
+
button.style.color = '#ffffff'; // White text
|
|
697
|
+
button.style.cursor = 'pointer';
|
|
698
|
+
button.style.opacity = '1.0';
|
|
699
|
+
}
|
|
700
|
+
}
|