@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,441 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import {
|
|
3
|
+
clearLabels,
|
|
4
|
+
getLabelGroup,
|
|
5
|
+
hideLabels,
|
|
6
|
+
rigOptions,
|
|
7
|
+
setLabelGroup,
|
|
8
|
+
updateLabelPosition
|
|
9
|
+
} from "./rig-controller";
|
|
10
|
+
import { boneVisualsGroup } from './bone-kinematics';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Create joint labels for all joints in the scene
|
|
14
|
+
* @param {Object} scene - The Three.js scene
|
|
15
|
+
*/
|
|
16
|
+
export function createJointLabels(scene) {
|
|
17
|
+
console.log('Creating joint labels...');
|
|
18
|
+
|
|
19
|
+
// Remove any existing labels first
|
|
20
|
+
clearLabels('joint', scene);
|
|
21
|
+
|
|
22
|
+
// Create a group to hold all labels
|
|
23
|
+
setLabelGroup('joint', "JointLabels", scene);
|
|
24
|
+
|
|
25
|
+
// Keep track of the labels created
|
|
26
|
+
const labelCount = {total: 0, added: 0};
|
|
27
|
+
|
|
28
|
+
// Track positions where we've already created labels to prevent duplicates
|
|
29
|
+
const labelPositions = [];
|
|
30
|
+
const positionTolerance = 0.001; // Tolerance for considering positions as identical
|
|
31
|
+
|
|
32
|
+
// Helper function to check if a position already has a label
|
|
33
|
+
const hasLabelAtPosition = (position) => {
|
|
34
|
+
return labelPositions.some(pos => position.distanceTo(pos) < positionTolerance);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Find all bone meshes
|
|
38
|
+
boneVisualsGroup.traverse((object) => {
|
|
39
|
+
if (object.userData && object.userData.bonePart === 'cap') {
|
|
40
|
+
labelCount.total++;
|
|
41
|
+
|
|
42
|
+
// Get world position of this joint
|
|
43
|
+
const worldPos = new THREE.Vector3();
|
|
44
|
+
object.getWorldPosition(worldPos);
|
|
45
|
+
|
|
46
|
+
// Check if we already have a label at this position
|
|
47
|
+
if (hasLabelAtPosition(worldPos)) {
|
|
48
|
+
console.log('Skipping duplicate joint label at position:', worldPos);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Determine which bone name to use
|
|
53
|
+
let boneName = "";
|
|
54
|
+
if (object.parent && object.parent.userData) {
|
|
55
|
+
if (object.position.y > 0 && object.parent.userData.childBone) {
|
|
56
|
+
// Top sphere - use child bone name
|
|
57
|
+
boneName = object.parent.userData.childBone.name;
|
|
58
|
+
} else if (object.position.y === 0 && object.parent.userData.parentBone) {
|
|
59
|
+
// Bottom sphere - use parent bone name
|
|
60
|
+
boneName = object.parent.userData.parentBone.name;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (boneName) {
|
|
65
|
+
// Create a label for this joint
|
|
66
|
+
const label = createSimpleLabel(boneName, object, scene);
|
|
67
|
+
if (label) {
|
|
68
|
+
const jointLabelGroup = getLabelGroup('joint');
|
|
69
|
+
if (jointLabelGroup) {
|
|
70
|
+
jointLabelGroup.add(label);
|
|
71
|
+
labelCount.added++;
|
|
72
|
+
|
|
73
|
+
// Record this position as having a label
|
|
74
|
+
labelPositions.push(worldPos);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
console.log(`Created ${labelCount.added} labels out of ${labelCount.total} joint spheres found`);
|
|
82
|
+
return scene.getObjectByName("JointLabels");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Create bone labels for all bone segments in the scene
|
|
87
|
+
* @param {Object} scene - The Three.js scene
|
|
88
|
+
*/
|
|
89
|
+
export function createBoneLabels(scene) {
|
|
90
|
+
console.log('Creating bone labels...');
|
|
91
|
+
|
|
92
|
+
// Remove any existing bone labels
|
|
93
|
+
clearLabels('bone', scene);
|
|
94
|
+
|
|
95
|
+
// Create a group to hold all bone labels
|
|
96
|
+
setLabelGroup('bone', "BoneLabels", scene);
|
|
97
|
+
|
|
98
|
+
// Keep track of the labels created
|
|
99
|
+
const labelCount = {total: 0, added: 0};
|
|
100
|
+
|
|
101
|
+
// Track bone connections that already have labels to prevent duplicates
|
|
102
|
+
const labeledBoneConnections = new Set();
|
|
103
|
+
|
|
104
|
+
// Find all bone groups
|
|
105
|
+
boneVisualsGroup.children.forEach((boneGroup) => {
|
|
106
|
+
// Skip if not a bone group
|
|
107
|
+
if (!boneGroup.userData || !boneGroup.userData.isVisualBone) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
labelCount.total++;
|
|
112
|
+
|
|
113
|
+
// Get bone name information from the bone group
|
|
114
|
+
let boneName = "";
|
|
115
|
+
if (boneGroup.userData.parentBone && boneGroup.userData.childBone) {
|
|
116
|
+
// Create a connection identifier (both directions to handle differently ordered pairs)
|
|
117
|
+
const parentID = boneGroup.userData.parentBone.id || boneGroup.userData.parentBone.uuid;
|
|
118
|
+
const childID = boneGroup.userData.childBone.id || boneGroup.userData.childBone.uuid;
|
|
119
|
+
const connectionID = `${parentID}_${childID}`;
|
|
120
|
+
const reverseConnectionID = `${childID}_${parentID}`;
|
|
121
|
+
|
|
122
|
+
// Skip if we already created a label for this connection
|
|
123
|
+
if (labeledBoneConnections.has(connectionID) || labeledBoneConnections.has(reverseConnectionID)) {
|
|
124
|
+
console.log('Skipping duplicate bone label for connection:',
|
|
125
|
+
`${boneGroup.userData.parentBone.name} → ${boneGroup.userData.childBone.name}`);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Create a name that indicates the connection
|
|
130
|
+
boneName = `${boneGroup.userData.parentBone.name} → ${boneGroup.userData.childBone.name}`;
|
|
131
|
+
|
|
132
|
+
// Create a label for this bone
|
|
133
|
+
const label = createSimpleLabel(boneName, boneGroup, scene, true); // Pass true to indicate it's a bone label
|
|
134
|
+
if (label) {
|
|
135
|
+
const boneLabelGroup = getLabelGroup('bone');
|
|
136
|
+
if (boneLabelGroup) {
|
|
137
|
+
boneLabelGroup.add(label);
|
|
138
|
+
labelCount.added++;
|
|
139
|
+
|
|
140
|
+
// Mark this connection as labeled
|
|
141
|
+
labeledBoneConnections.add(connectionID);
|
|
142
|
+
|
|
143
|
+
// Calculate the midpoint between parent and child bones immediately
|
|
144
|
+
const parentPos = new THREE.Vector3();
|
|
145
|
+
const childPos = new THREE.Vector3();
|
|
146
|
+
|
|
147
|
+
boneGroup.userData.parentBone.getWorldPosition(parentPos);
|
|
148
|
+
boneGroup.userData.childBone.getWorldPosition(childPos);
|
|
149
|
+
|
|
150
|
+
// Calculate the middle point
|
|
151
|
+
const midPoint = new THREE.Vector3().addVectors(parentPos, childPos).multiplyScalar(0.5);
|
|
152
|
+
|
|
153
|
+
// Position at the middle point immediately
|
|
154
|
+
label.position.copy(midPoint);
|
|
155
|
+
|
|
156
|
+
// Position the label at the middle of the bone
|
|
157
|
+
if (label.userData.updatePosition) {
|
|
158
|
+
label.userData.updatePosition();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
console.log(`Created ${labelCount.added} labels out of ${labelCount.total} bone segments found`);
|
|
166
|
+
return scene.getObjectByName("BoneLabels");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Create both joint and bone labels and set their visibility
|
|
171
|
+
* @param {Object} scene - The Three.js scene
|
|
172
|
+
*/
|
|
173
|
+
export function createLabels(scene) {
|
|
174
|
+
// Create joint labels
|
|
175
|
+
console.log('Setting up joint labels');
|
|
176
|
+
createJointLabels(scene);
|
|
177
|
+
|
|
178
|
+
// Check if joint labels should be visible based on option
|
|
179
|
+
if (!rigOptions.showJointLabels) {
|
|
180
|
+
hideLabels('joint');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Create bone labels
|
|
184
|
+
console.log('Setting up bone labels');
|
|
185
|
+
createBoneLabels(scene);
|
|
186
|
+
|
|
187
|
+
// Check if bone labels should be visible based on option
|
|
188
|
+
if (!rigOptions.showBoneLabels) {
|
|
189
|
+
hideLabels('bone');
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Create a simple text label as a sprite
|
|
195
|
+
* @param {string} text - Text to display
|
|
196
|
+
* @param {Object} joint - Joint object to attach to
|
|
197
|
+
* @param {Object} scene - Three.js scene
|
|
198
|
+
* @param {boolean} isBoneLabel - Whether this is a bone label (true) or joint label (false)
|
|
199
|
+
* @returns {Object} The created label sprite
|
|
200
|
+
*/
|
|
201
|
+
function createSimpleLabel(text, joint, scene, isBoneLabel = false) {
|
|
202
|
+
console.log(`Creating label for ${isBoneLabel ? 'bone' : 'joint'}: ${text}`);
|
|
203
|
+
|
|
204
|
+
// Create a canvas for the label
|
|
205
|
+
const canvas = document.createElement('canvas');
|
|
206
|
+
const ctx = canvas.getContext('2d');
|
|
207
|
+
|
|
208
|
+
// Set canvas size
|
|
209
|
+
canvas.width = 256;
|
|
210
|
+
// Increase height for bone labels to accommodate multiple lines
|
|
211
|
+
canvas.height = isBoneLabel ? 96 : 64;
|
|
212
|
+
|
|
213
|
+
// Different styling for bone vs joint labels - ONLY VISUAL DIFFERENCES HERE
|
|
214
|
+
const labelConfig = {
|
|
215
|
+
bgColor: isBoneLabel ? 'rgba(30, 136, 229, 0.8)' : 'rgba(76, 175, 80, 0.8)',
|
|
216
|
+
gradientStops: isBoneLabel ?
|
|
217
|
+
[{ pos: 0, color: 'rgba(40, 40, 80, 0.4)' }, { pos: 1, color: 'rgba(20, 20, 40, 0.6)' }] :
|
|
218
|
+
[{ pos: 0, color: 'rgba(30, 60, 30, 0.4)' }, { pos: 1, color: 'rgba(10, 30, 10, 0.6)' }],
|
|
219
|
+
borderColor: isBoneLabel ? '#88AAFF' : '#AAFFAA',
|
|
220
|
+
scrollThumbColor: isBoneLabel ? '#88AAFF' : '#AAFFAA',
|
|
221
|
+
headerText: isBoneLabel ? 'BONE' : 'JOINT'
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
// Background fill
|
|
225
|
+
ctx.fillStyle = labelConfig.bgColor;
|
|
226
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
227
|
+
|
|
228
|
+
// Add gradient overlay
|
|
229
|
+
const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height);
|
|
230
|
+
labelConfig.gradientStops.forEach(stop => {
|
|
231
|
+
gradient.addColorStop(stop.pos, stop.color);
|
|
232
|
+
});
|
|
233
|
+
ctx.fillStyle = gradient;
|
|
234
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
235
|
+
|
|
236
|
+
// Border
|
|
237
|
+
ctx.strokeStyle = labelConfig.borderColor;
|
|
238
|
+
ctx.lineWidth = 2;
|
|
239
|
+
ctx.strokeRect(0, 0, canvas.width, canvas.height);
|
|
240
|
+
|
|
241
|
+
// Header line
|
|
242
|
+
ctx.fillStyle = 'rgba(0, 0, 0, 0.3)';
|
|
243
|
+
ctx.fillRect(0, 0, canvas.width, 18);
|
|
244
|
+
|
|
245
|
+
// Header text
|
|
246
|
+
ctx.font = 'bold 12px monospace';
|
|
247
|
+
ctx.fillStyle = '#DDDDDD';
|
|
248
|
+
ctx.textAlign = 'left';
|
|
249
|
+
ctx.textBaseline = 'middle';
|
|
250
|
+
ctx.fillText(labelConfig.headerText, 6, 9);
|
|
251
|
+
|
|
252
|
+
// Main text
|
|
253
|
+
ctx.font = 'bold 16px monospace';
|
|
254
|
+
ctx.fillStyle = 'white';
|
|
255
|
+
ctx.textAlign = 'center';
|
|
256
|
+
ctx.textBaseline = 'middle';
|
|
257
|
+
|
|
258
|
+
// For bone labels, format as multiple lines if it contains an arrow
|
|
259
|
+
if (isBoneLabel && text.includes('→')) {
|
|
260
|
+
const parts = text.split('→');
|
|
261
|
+
const parent = parts[0].trim();
|
|
262
|
+
const child = parts[1].trim();
|
|
263
|
+
|
|
264
|
+
// Calculate max width to check if parts need scrolling
|
|
265
|
+
const parentWidth = ctx.measureText(parent).width;
|
|
266
|
+
const childWidth = ctx.measureText(child).width;
|
|
267
|
+
const maxWidth = canvas.width - 20; // 10px padding on each side
|
|
268
|
+
|
|
269
|
+
// First line: parent
|
|
270
|
+
let parentText = parent;
|
|
271
|
+
if (parentWidth > maxWidth) {
|
|
272
|
+
parentText = parent.length > 25 ? parent.substring(0, 22) + '...' : parent;
|
|
273
|
+
}
|
|
274
|
+
ctx.fillText(parentText, canvas.width / 2, 35);
|
|
275
|
+
|
|
276
|
+
// Arrow line
|
|
277
|
+
ctx.fillText('↓', canvas.width / 2, 55);
|
|
278
|
+
|
|
279
|
+
// Third line: child
|
|
280
|
+
let childText = child;
|
|
281
|
+
if (childWidth > maxWidth) {
|
|
282
|
+
childText = child.length > 25 ? child.substring(0, 22) + '...' : child;
|
|
283
|
+
}
|
|
284
|
+
ctx.fillText(childText, canvas.width / 2, 75);
|
|
285
|
+
|
|
286
|
+
// If either part was truncated, add a scroll indicator
|
|
287
|
+
if (parentWidth > maxWidth || childWidth > maxWidth) {
|
|
288
|
+
// Draw scroll indicator
|
|
289
|
+
ctx.fillStyle = 'rgba(255, 255, 255, 0.4)';
|
|
290
|
+
ctx.fillRect(10, canvas.height - 8, canvas.width - 20, 4);
|
|
291
|
+
|
|
292
|
+
// Draw scrollbar thumb
|
|
293
|
+
const contentWidth = Math.max(parentWidth, childWidth);
|
|
294
|
+
const thumbWidth = Math.max(30, (maxWidth / contentWidth) * (canvas.width - 20));
|
|
295
|
+
ctx.fillStyle = labelConfig.scrollThumbColor;
|
|
296
|
+
ctx.fillRect(10, canvas.height - 8, thumbWidth, 4);
|
|
297
|
+
}
|
|
298
|
+
} else {
|
|
299
|
+
// Regular single-line text handling (for joint labels or non-arrow bone labels)
|
|
300
|
+
// Calculate text width to check if it needs scrolling
|
|
301
|
+
const textMetrics = ctx.measureText(text);
|
|
302
|
+
const textWidth = textMetrics.width;
|
|
303
|
+
const maxWidth = canvas.width - 20; // 10px padding on each side
|
|
304
|
+
|
|
305
|
+
// If text is too long, implement scrolling behavior
|
|
306
|
+
if (textWidth > maxWidth) {
|
|
307
|
+
// Indicate text is scrollable with ellipsis
|
|
308
|
+
const displayText = text.length > 25 ? text.substring(0, 22) + '...' : text;
|
|
309
|
+
ctx.fillText(displayText, canvas.width / 2, canvas.height / 2 + 5);
|
|
310
|
+
|
|
311
|
+
// Draw scroll indicator
|
|
312
|
+
ctx.fillStyle = 'rgba(255, 255, 255, 0.4)';
|
|
313
|
+
ctx.fillRect(10, canvas.height - 8, canvas.width - 20, 4);
|
|
314
|
+
|
|
315
|
+
// Draw scrollbar thumb
|
|
316
|
+
const thumbWidth = Math.max(30, (maxWidth / textWidth) * (canvas.width - 20));
|
|
317
|
+
ctx.fillStyle = labelConfig.scrollThumbColor;
|
|
318
|
+
ctx.fillRect(10, canvas.height - 8, thumbWidth, 4);
|
|
319
|
+
} else {
|
|
320
|
+
// Text fits, just display it
|
|
321
|
+
ctx.fillText(text, canvas.width / 2, canvas.height / 2 + 5);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Create sprite material
|
|
326
|
+
const texture = new THREE.CanvasTexture(canvas);
|
|
327
|
+
const material = new THREE.SpriteMaterial({
|
|
328
|
+
map: texture,
|
|
329
|
+
transparent: true,
|
|
330
|
+
depthTest: false,
|
|
331
|
+
depthWrite: false
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// Create sprite
|
|
335
|
+
const sprite = new THREE.Sprite(material);
|
|
336
|
+
sprite.userData.isBoneLabel = isBoneLabel;
|
|
337
|
+
sprite.userData.isJointLabel = !isBoneLabel;
|
|
338
|
+
sprite.userData.targetJoint = joint;
|
|
339
|
+
sprite.userData.labelText = text; // Store original text
|
|
340
|
+
|
|
341
|
+
// Add hover handling for label headers
|
|
342
|
+
sprite.userData.isInteractive = true;
|
|
343
|
+
sprite.userData.isLabelHeader = true;
|
|
344
|
+
sprite.userData.headerHeight = 18; // Height of the header section in pixels
|
|
345
|
+
sprite.userData.canvasHeight = canvas.height;
|
|
346
|
+
sprite.userData.canvasWidth = canvas.width;
|
|
347
|
+
|
|
348
|
+
// Add mouse event handling to prevent propagation when over the header
|
|
349
|
+
sprite.userData.onMouseMove = (event) => {
|
|
350
|
+
// Check if mouse is over the header area
|
|
351
|
+
if (sprite.userData.isMouseOverHeader) {
|
|
352
|
+
// Prevent event propagation to stop camera controls
|
|
353
|
+
event.stopPropagation();
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
// This function calculates if mouse is over the header or the content area
|
|
358
|
+
sprite.userData.checkHeaderHover = (mousePos) => {
|
|
359
|
+
if (!mousePos) return false;
|
|
360
|
+
|
|
361
|
+
// The sprite's coordinates go from -0.5 to 0.5 in both dimensions
|
|
362
|
+
// So we need to convert from local sprite coordinates to canvas coordinates
|
|
363
|
+
|
|
364
|
+
// Calculate the overall dimensions in world units
|
|
365
|
+
const totalHeight = sprite.scale.y;
|
|
366
|
+
|
|
367
|
+
// Convert mousePos.y from local coordinates (-0.5 to 0.5) to normalized height (0 to 1)
|
|
368
|
+
// In THREE.js, sprite local coords have origin at center, Y+ is up
|
|
369
|
+
// -0.5 is bottom, 0.5 is top, so we need to flip and shift
|
|
370
|
+
const normalizedY = 0.5 - mousePos.y; // Convert to 0 at top, 1 at bottom
|
|
371
|
+
|
|
372
|
+
// Now convert to canvas pixel coordinates
|
|
373
|
+
const canvasY = normalizedY * canvas.height;
|
|
374
|
+
|
|
375
|
+
// Check if we're in the header region (top 18px of the canvas)
|
|
376
|
+
const isOverHeader = canvasY >= 0 && canvasY <= sprite.userData.headerHeight;
|
|
377
|
+
|
|
378
|
+
return isOverHeader;
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
// Set initial position safely - updateLabelPosition now has defensive coding
|
|
382
|
+
updateLabelPosition(sprite, joint);
|
|
383
|
+
|
|
384
|
+
// FIXED SIZE FOR ALL LABELS - no dependency on underlying object geometry
|
|
385
|
+
const fixedScale = 0.3;
|
|
386
|
+
// Adjust height for bone labels to accommodate multiple lines
|
|
387
|
+
const heightFactor = isBoneLabel ? 0.4 : 0.25;
|
|
388
|
+
sprite.scale.set(fixedScale, fixedScale * heightFactor, 1);
|
|
389
|
+
|
|
390
|
+
// Set initial visibility based on appropriate option
|
|
391
|
+
sprite.visible = isBoneLabel ? rigOptions.showBoneLabels : rigOptions.showJointLabels;
|
|
392
|
+
if (!sprite.visible) {
|
|
393
|
+
console.log(`Label for ${text} is initially hidden`);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Set up the update function - customize for bone labels
|
|
397
|
+
sprite.userData.updatePosition = () => {
|
|
398
|
+
// For bone labels, we want to position them in the middle of the bone
|
|
399
|
+
if (isBoneLabel) {
|
|
400
|
+
// Get the bone group which should have parent and child bone references
|
|
401
|
+
const boneGroup = joint.userData && joint.userData.isVisualBone ?
|
|
402
|
+
joint : (joint.parent && joint.parent.userData && joint.parent.userData.isVisualBone ?
|
|
403
|
+
joint.parent : null);
|
|
404
|
+
|
|
405
|
+
if (boneGroup && boneGroup.userData &&
|
|
406
|
+
boneGroup.userData.parentBone && boneGroup.userData.childBone) {
|
|
407
|
+
|
|
408
|
+
// Get world positions of parent and child bones
|
|
409
|
+
const parentPos = new THREE.Vector3();
|
|
410
|
+
const childPos = new THREE.Vector3();
|
|
411
|
+
|
|
412
|
+
boneGroup.userData.parentBone.getWorldPosition(parentPos);
|
|
413
|
+
boneGroup.userData.childBone.getWorldPosition(childPos);
|
|
414
|
+
|
|
415
|
+
// Calculate the middle point
|
|
416
|
+
const midPoint = new THREE.Vector3().addVectors(parentPos, childPos).multiplyScalar(0.5);
|
|
417
|
+
|
|
418
|
+
// Position exactly at the midpoint without any offset
|
|
419
|
+
sprite.position.copy(midPoint);
|
|
420
|
+
} else {
|
|
421
|
+
// Fallback for when we can't find the proper bone references
|
|
422
|
+
const worldPos = new THREE.Vector3();
|
|
423
|
+
joint.getWorldPosition(worldPos);
|
|
424
|
+
sprite.position.copy(worldPos);
|
|
425
|
+
}
|
|
426
|
+
} else {
|
|
427
|
+
// For joint labels, use the standard update function
|
|
428
|
+
updateLabelPosition(sprite, joint);
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
// Initialize the position
|
|
433
|
+
if (sprite.userData.updatePosition) {
|
|
434
|
+
sprite.userData.updatePosition();
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Make sure the sprite renders on top
|
|
438
|
+
sprite.renderOrder = isBoneLabel ? 990 : 1000; // Joint labels show on top of bone labels
|
|
439
|
+
|
|
440
|
+
return sprite;
|
|
441
|
+
}
|