@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,407 @@
|
|
|
1
|
+
import { BINARY_DATA_PROPERTY, MESH_BINARY_EXTENSION, MESH_INDEX_PROPERTY } from "../state/glb-preview-state";
|
|
2
|
+
import { validateGLBHeader, validateJSONChunk, validateBinaryChunk, isRemovalOperation, hasExtension, hasBufferURI, isDataURI, getChunkInfo } from './glb-classifier.js';
|
|
3
|
+
|
|
4
|
+
export function associateBinaryBufferWithMesh(glbArrayBuffer, meshIndex, binaryData) {
|
|
5
|
+
return new Promise((resolve, reject) => {
|
|
6
|
+
try {
|
|
7
|
+
const isRemoval = isRemovalOperation(binaryData);
|
|
8
|
+
console.log(`${isRemoval ? 'Removing' : 'Associating'} binary data for mesh ${meshIndex} (${isRemoval ? 0 : binaryData.byteLength} bytes)`);
|
|
9
|
+
|
|
10
|
+
const dataView = new DataView(glbArrayBuffer);
|
|
11
|
+
validateGLBHeader(dataView);
|
|
12
|
+
|
|
13
|
+
console.log(`GLB buffer size: ${dataView.byteLength} bytes`);
|
|
14
|
+
|
|
15
|
+
const chunkInfo = getChunkInfo(dataView);
|
|
16
|
+
console.log(`JSON chunk length: ${chunkInfo.jsonChunkLength} bytes`);
|
|
17
|
+
|
|
18
|
+
validateJSONChunk(dataView, chunkInfo.jsonChunkLength);
|
|
19
|
+
|
|
20
|
+
const gltf = parseJSONChunk(glbArrayBuffer, chunkInfo);
|
|
21
|
+
ensureExtensions(gltf);
|
|
22
|
+
|
|
23
|
+
const associations = gltf.extensions[MESH_BINARY_EXTENSION].meshBinaryAssociations;
|
|
24
|
+
const existingAssociationIndex = associations.findIndex(assoc => assoc[MESH_INDEX_PROPERTY] === meshIndex);
|
|
25
|
+
const existingAssociation = existingAssociationIndex >= 0 ? associations[existingAssociationIndex] : null;
|
|
26
|
+
|
|
27
|
+
if (isRemoval && existingAssociation) {
|
|
28
|
+
resolve(removeBinaryAssociation(glbArrayBuffer, gltf, associations, existingAssociationIndex, meshIndex));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
resolve(addOrUpdateBinaryAssociation(glbArrayBuffer, gltf, meshIndex, binaryData, existingAssociation, associations, chunkInfo));
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error('Error in associateBinaryBufferWithMesh:', error);
|
|
35
|
+
reject(new Error(`Error associating binary buffer: ${error.message}`));
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function getBinaryBufferForMesh(glbArrayBuffer, meshIndex) {
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
try {
|
|
43
|
+
const dataView = new DataView(glbArrayBuffer);
|
|
44
|
+
validateGLBHeader(dataView);
|
|
45
|
+
|
|
46
|
+
const chunkInfo = getChunkInfo(dataView);
|
|
47
|
+
validateJSONChunk(dataView, chunkInfo.jsonChunkLength);
|
|
48
|
+
|
|
49
|
+
const gltf = parseJSONChunk(glbArrayBuffer, chunkInfo);
|
|
50
|
+
|
|
51
|
+
if (!hasExtension(gltf, MESH_BINARY_EXTENSION)) {
|
|
52
|
+
resolve(null);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const associations = gltf.extensions[MESH_BINARY_EXTENSION].meshBinaryAssociations;
|
|
57
|
+
if (!associations || !Array.isArray(associations)) {
|
|
58
|
+
resolve(null);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const association = associations.find(assoc => assoc[MESH_INDEX_PROPERTY] === meshIndex);
|
|
63
|
+
|
|
64
|
+
if (!association) {
|
|
65
|
+
resolve(null);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const bufferIndex = association[BINARY_DATA_PROPERTY];
|
|
70
|
+
|
|
71
|
+
if (!gltf.buffers || !gltf.buffers[bufferIndex]) {
|
|
72
|
+
console.log(`Buffer ${bufferIndex} not found for mesh ${meshIndex}`);
|
|
73
|
+
resolve(null);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const buffer = gltf.buffers[bufferIndex];
|
|
78
|
+
console.log(`Found buffer ${bufferIndex} for mesh ${meshIndex} with length ${buffer.byteLength}`);
|
|
79
|
+
|
|
80
|
+
resolve(extractBufferData(glbArrayBuffer, gltf, buffer, bufferIndex, chunkInfo));
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.error('Error retrieving binary buffer:', error);
|
|
83
|
+
reject(new Error(`Error retrieving binary buffer: ${error.message}`));
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function parseJSONChunk(glbArrayBuffer, chunkInfo) {
|
|
89
|
+
const jsonData = glbArrayBuffer.slice(chunkInfo.jsonStart, chunkInfo.jsonEnd);
|
|
90
|
+
const decoder = new TextDecoder('utf-8');
|
|
91
|
+
const jsonString = decoder.decode(jsonData);
|
|
92
|
+
return JSON.parse(jsonString);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function ensureExtensions(gltf) {
|
|
96
|
+
if (!gltf.extensions) {
|
|
97
|
+
gltf.extensions = {};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!gltf.extensions[MESH_BINARY_EXTENSION]) {
|
|
101
|
+
gltf.extensions[MESH_BINARY_EXTENSION] = {
|
|
102
|
+
meshBinaryAssociations: []
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function removeBinaryAssociation(glbArrayBuffer, gltf, associations, existingAssociationIndex, meshIndex) {
|
|
108
|
+
console.log(`Removing binary association for mesh ${meshIndex}`);
|
|
109
|
+
associations.splice(existingAssociationIndex, 1);
|
|
110
|
+
|
|
111
|
+
if (associations.length === 0) {
|
|
112
|
+
delete gltf.extensions[MESH_BINARY_EXTENSION];
|
|
113
|
+
|
|
114
|
+
if (Object.keys(gltf.extensions).length === 0) {
|
|
115
|
+
delete gltf.extensions;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const newJsonString = JSON.stringify(gltf);
|
|
120
|
+
const newJsonBytes = new TextEncoder().encode(newJsonString);
|
|
121
|
+
|
|
122
|
+
return updateGlbJsonOnly(glbArrayBuffer, newJsonBytes);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function addOrUpdateBinaryAssociation(glbArrayBuffer, gltf, meshIndex, binaryData, existingAssociation, associations, chunkInfo) {
|
|
126
|
+
let bufferToUpdateIndex = -1;
|
|
127
|
+
|
|
128
|
+
if (existingAssociation) {
|
|
129
|
+
bufferToUpdateIndex = existingAssociation[BINARY_DATA_PROPERTY];
|
|
130
|
+
} else {
|
|
131
|
+
bufferToUpdateIndex = gltf.buffers ? gltf.buffers.length : 0;
|
|
132
|
+
|
|
133
|
+
associations.push({
|
|
134
|
+
[MESH_INDEX_PROPERTY]: meshIndex,
|
|
135
|
+
[BINARY_DATA_PROPERTY]: bufferToUpdateIndex
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (!gltf.buffers) {
|
|
140
|
+
gltf.buffers = [];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const exactByteLength = binaryData.byteLength;
|
|
144
|
+
const bufferLength = Math.ceil(exactByteLength / 4) * 4;
|
|
145
|
+
|
|
146
|
+
if (bufferToUpdateIndex < gltf.buffers.length) {
|
|
147
|
+
gltf.buffers[bufferToUpdateIndex] = {
|
|
148
|
+
byteLength: exactByteLength
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
delete gltf.buffers[bufferToUpdateIndex].uri;
|
|
152
|
+
} else {
|
|
153
|
+
gltf.buffers.push({
|
|
154
|
+
byteLength: exactByteLength
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return rebuildGLBWithBinaryData(glbArrayBuffer, gltf, bufferToUpdateIndex, binaryData, bufferLength, chunkInfo);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function rebuildGLBWithBinaryData(glbArrayBuffer, gltf, bufferToUpdateIndex, binaryData, bufferLength, chunkInfo) {
|
|
162
|
+
const newJsonString = JSON.stringify(gltf);
|
|
163
|
+
const newJsonBytes = new TextEncoder().encode(newJsonString);
|
|
164
|
+
|
|
165
|
+
const paddedJsonLength = Math.ceil(newJsonBytes.length / 4) * 4;
|
|
166
|
+
const jsonPadding = paddedJsonLength - newJsonBytes.length;
|
|
167
|
+
|
|
168
|
+
const existingBuffers = extractExistingBuffers(glbArrayBuffer, gltf, bufferToUpdateIndex, chunkInfo);
|
|
169
|
+
|
|
170
|
+
let totalBinarySize = 0;
|
|
171
|
+
|
|
172
|
+
for (const buf of existingBuffers) {
|
|
173
|
+
totalBinarySize += Math.ceil(buf.data.byteLength / 4) * 4;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
totalBinarySize += bufferLength;
|
|
177
|
+
|
|
178
|
+
const newTotalSize =
|
|
179
|
+
12 +
|
|
180
|
+
8 + paddedJsonLength +
|
|
181
|
+
8 + totalBinarySize;
|
|
182
|
+
|
|
183
|
+
console.log(`New GLB total size: ${newTotalSize} bytes`);
|
|
184
|
+
console.log(`New binary chunk size: ${totalBinarySize} bytes`);
|
|
185
|
+
|
|
186
|
+
return buildNewGLB(newTotalSize, newJsonBytes, paddedJsonLength, jsonPadding, totalBinarySize, existingBuffers, binaryData, bufferLength);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function extractExistingBuffers(glbArrayBuffer, gltf, excludeBufferIndex, chunkInfo) {
|
|
190
|
+
const dataView = new DataView(glbArrayBuffer);
|
|
191
|
+
const existingBuffers = [];
|
|
192
|
+
|
|
193
|
+
if (chunkInfo.binaryChunkOffset + 8 <= dataView.byteLength) {
|
|
194
|
+
try {
|
|
195
|
+
const binaryChunkHeaderLength = dataView.getUint32(chunkInfo.binaryChunkOffset, true);
|
|
196
|
+
const binaryChunkType = dataView.getUint32(chunkInfo.binaryChunkOffset + 4, true);
|
|
197
|
+
|
|
198
|
+
if (binaryChunkType === 0x004E4942) {
|
|
199
|
+
console.log(`Original binary chunk found: ${binaryChunkHeaderLength} bytes`);
|
|
200
|
+
|
|
201
|
+
let currentOffset = 0;
|
|
202
|
+
|
|
203
|
+
for (let i = 0; i < gltf.buffers.length; i++) {
|
|
204
|
+
if (i === excludeBufferIndex) {
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (gltf.buffers[i] && !hasBufferURI(gltf.buffers[i])) {
|
|
209
|
+
const bufLen = gltf.buffers[i].byteLength;
|
|
210
|
+
const paddedLen = Math.ceil(bufLen / 4) * 4;
|
|
211
|
+
|
|
212
|
+
if (chunkInfo.binaryChunkOffset + 8 + currentOffset + bufLen <= dataView.byteLength) {
|
|
213
|
+
const bufData = glbArrayBuffer.slice(
|
|
214
|
+
chunkInfo.binaryChunkOffset + 8 + currentOffset,
|
|
215
|
+
chunkInfo.binaryChunkOffset + 8 + currentOffset + bufLen
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
existingBuffers.push({
|
|
219
|
+
index: i,
|
|
220
|
+
data: bufData
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
currentOffset += paddedLen;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
} catch (e) {
|
|
229
|
+
console.warn('Error extracting existing binary data:', e);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return existingBuffers;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function extractBufferData(glbArrayBuffer, gltf, buffer, bufferIndex, chunkInfo) {
|
|
237
|
+
if (hasBufferURI(buffer)) {
|
|
238
|
+
if (isDataURI(buffer.uri)) {
|
|
239
|
+
const base64Data = buffer.uri.split(',')[1];
|
|
240
|
+
const binaryString = atob(base64Data);
|
|
241
|
+
const bytes = new Uint8Array(binaryString.length);
|
|
242
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
243
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
244
|
+
}
|
|
245
|
+
return bytes.buffer;
|
|
246
|
+
} else {
|
|
247
|
+
throw new Error('External URI buffers not supported');
|
|
248
|
+
}
|
|
249
|
+
} else {
|
|
250
|
+
return extractGLBBuffer(glbArrayBuffer, gltf, buffer, bufferIndex, chunkInfo);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function extractGLBBuffer(glbArrayBuffer, gltf, buffer, bufferIndex, chunkInfo) {
|
|
255
|
+
const dataView = new DataView(glbArrayBuffer);
|
|
256
|
+
|
|
257
|
+
if (!validateBinaryChunk(dataView, chunkInfo.binaryChunkOffset)) {
|
|
258
|
+
console.log(`No binary chunk found at offset ${chunkInfo.binaryChunkOffset}`);
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
let currentOffset = 0;
|
|
263
|
+
let foundBuffer = false;
|
|
264
|
+
|
|
265
|
+
for (let i = 0; i < gltf.buffers.length; i++) {
|
|
266
|
+
if (i === bufferIndex) {
|
|
267
|
+
foundBuffer = true;
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (!hasBufferURI(gltf.buffers[i])) {
|
|
272
|
+
const bufferLength = gltf.buffers[i].byteLength;
|
|
273
|
+
const paddedLength = Math.ceil(bufferLength / 4) * 4;
|
|
274
|
+
currentOffset += paddedLength;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (!foundBuffer) {
|
|
279
|
+
console.warn(`Buffer index ${bufferIndex} exceeds buffer count`);
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const bufferStartOffset = chunkInfo.binaryChunkOffset + 8 + currentOffset;
|
|
284
|
+
const bufferEndOffset = bufferStartOffset + buffer.byteLength;
|
|
285
|
+
|
|
286
|
+
if (bufferEndOffset > dataView.byteLength) {
|
|
287
|
+
console.warn(`Buffer extends beyond GLB file (${bufferEndOffset} > ${dataView.byteLength})`);
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
console.log(`Extracting buffer at offset ${currentOffset} (${bufferStartOffset}-${bufferEndOffset})`);
|
|
292
|
+
return glbArrayBuffer.slice(bufferStartOffset, bufferEndOffset);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function buildNewGLB(newTotalSize, newJsonBytes, paddedJsonLength, jsonPadding, totalBinarySize, existingBuffers, binaryData, bufferLength) {
|
|
296
|
+
const newGlb = new ArrayBuffer(newTotalSize);
|
|
297
|
+
const newDataView = new DataView(newGlb);
|
|
298
|
+
const newUint8Array = new Uint8Array(newGlb);
|
|
299
|
+
|
|
300
|
+
newDataView.setUint32(0, 0x46546C67, true);
|
|
301
|
+
newDataView.setUint32(4, 2, true);
|
|
302
|
+
newDataView.setUint32(8, newTotalSize, true);
|
|
303
|
+
|
|
304
|
+
newDataView.setUint32(12, paddedJsonLength, true);
|
|
305
|
+
newDataView.setUint32(16, 0x4E4F534A, true);
|
|
306
|
+
|
|
307
|
+
newUint8Array.set(newJsonBytes, 20);
|
|
308
|
+
|
|
309
|
+
for (let i = 0; i < jsonPadding; i++) {
|
|
310
|
+
newUint8Array[20 + newJsonBytes.length + i] = 0x20;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const newBinaryChunkOffset = 20 + paddedJsonLength;
|
|
314
|
+
|
|
315
|
+
newDataView.setUint32(newBinaryChunkOffset, totalBinarySize, true);
|
|
316
|
+
newDataView.setUint32(newBinaryChunkOffset + 4, 0x004E4942, true);
|
|
317
|
+
|
|
318
|
+
let currentBinaryOffset = newBinaryChunkOffset + 8;
|
|
319
|
+
|
|
320
|
+
for (let i = 0; i < existingBuffers.length; i++) {
|
|
321
|
+
const buf = existingBuffers[i];
|
|
322
|
+
const bufArray = new Uint8Array(buf.data);
|
|
323
|
+
const paddedLength = Math.ceil(buf.data.byteLength / 4) * 4;
|
|
324
|
+
|
|
325
|
+
newUint8Array.set(bufArray, currentBinaryOffset);
|
|
326
|
+
|
|
327
|
+
const padding = paddedLength - buf.data.byteLength;
|
|
328
|
+
for (let j = 0; j < padding; j++) {
|
|
329
|
+
newUint8Array[currentBinaryOffset + buf.data.byteLength + j] = 0;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
currentBinaryOffset += paddedLength;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (binaryData && binaryData.byteLength > 0) {
|
|
336
|
+
const binaryArray = new Uint8Array(binaryData);
|
|
337
|
+
|
|
338
|
+
newUint8Array.set(binaryArray, currentBinaryOffset);
|
|
339
|
+
|
|
340
|
+
const padding = bufferLength - binaryData.byteLength;
|
|
341
|
+
for (let i = 0; i < padding; i++) {
|
|
342
|
+
newUint8Array[currentBinaryOffset + binaryData.byteLength + i] = 0;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
console.log('Successfully created new GLB with updated binary data');
|
|
347
|
+
return newGlb;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function updateGlbJsonOnly(glbArrayBuffer, newJsonBytes) {
|
|
351
|
+
const dataView = new DataView(glbArrayBuffer);
|
|
352
|
+
|
|
353
|
+
const oldJsonChunkLength = dataView.getUint32(12, true);
|
|
354
|
+
|
|
355
|
+
const jsonStart = 20;
|
|
356
|
+
const jsonEnd = jsonStart + oldJsonChunkLength;
|
|
357
|
+
|
|
358
|
+
const binaryChunkOffset = jsonEnd;
|
|
359
|
+
let binaryChunkData = null;
|
|
360
|
+
|
|
361
|
+
if (binaryChunkOffset + 8 <= dataView.byteLength) {
|
|
362
|
+
const binaryChunkHeaderLength = dataView.getUint32(binaryChunkOffset, true);
|
|
363
|
+
const binaryChunkType = dataView.getUint32(binaryChunkOffset + 4, true);
|
|
364
|
+
|
|
365
|
+
if (binaryChunkType === 0x004E4942) {
|
|
366
|
+
binaryChunkData = glbArrayBuffer.slice(
|
|
367
|
+
binaryChunkOffset,
|
|
368
|
+
binaryChunkOffset + 8 + binaryChunkHeaderLength
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const paddedJsonLength = Math.ceil(newJsonBytes.length / 4) * 4;
|
|
374
|
+
const jsonPadding = paddedJsonLength - newJsonBytes.length;
|
|
375
|
+
|
|
376
|
+
const newTotalSize =
|
|
377
|
+
12 +
|
|
378
|
+
8 + paddedJsonLength;
|
|
379
|
+
|
|
380
|
+
const hasBinaryChunk = binaryChunkData !== null;
|
|
381
|
+
const finalSize = hasBinaryChunk ? newTotalSize + (binaryChunkData.byteLength) : newTotalSize;
|
|
382
|
+
|
|
383
|
+
const newGlb = new ArrayBuffer(finalSize);
|
|
384
|
+
const newDataView = new DataView(newGlb);
|
|
385
|
+
const newUint8Array = new Uint8Array(newGlb);
|
|
386
|
+
|
|
387
|
+
newDataView.setUint32(0, 0x46546C67, true);
|
|
388
|
+
newDataView.setUint32(4, 2, true);
|
|
389
|
+
newDataView.setUint32(8, finalSize, true);
|
|
390
|
+
|
|
391
|
+
newDataView.setUint32(12, paddedJsonLength, true);
|
|
392
|
+
newDataView.setUint32(16, 0x4E4F534A, true);
|
|
393
|
+
|
|
394
|
+
newUint8Array.set(newJsonBytes, 20);
|
|
395
|
+
|
|
396
|
+
for (let i = 0; i < jsonPadding; i++) {
|
|
397
|
+
newUint8Array[20 + newJsonBytes.length + i] = 0x20;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (hasBinaryChunk) {
|
|
401
|
+
const newBinaryOffset = 20 + paddedJsonLength;
|
|
402
|
+
const binaryArray = new Uint8Array(binaryChunkData);
|
|
403
|
+
newUint8Array.set(binaryArray, newBinaryOffset);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return newGlb;
|
|
407
|
+
}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { deduplicateItemsByName } from "./duplicate-handler";
|
|
2
|
+
|
|
3
|
+
export function validateGLBHeader(dataView) {
|
|
4
|
+
if (dataView.byteLength < 12) {
|
|
5
|
+
throw new Error('Invalid GLB: File too small');
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const magic = dataView.getUint32(0, true);
|
|
9
|
+
const expectedMagic = 0x46546C67;
|
|
10
|
+
if (magic !== expectedMagic) {
|
|
11
|
+
throw new Error('Invalid GLB: Incorrect magic bytes');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const version = dataView.getUint32(4, true);
|
|
15
|
+
if (version !== 2) {
|
|
16
|
+
throw new Error(`Unsupported GLB version: ${version}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function validateJSONChunk(dataView, jsonChunkLength) {
|
|
21
|
+
const jsonChunkType = dataView.getUint32(16, true);
|
|
22
|
+
|
|
23
|
+
if (jsonChunkType !== 0x4E4F534A) {
|
|
24
|
+
throw new Error('Invalid GLB: First chunk is not JSON');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const jsonStart = 20;
|
|
28
|
+
const jsonEnd = jsonStart + jsonChunkLength;
|
|
29
|
+
|
|
30
|
+
if (jsonEnd > dataView.byteLength) {
|
|
31
|
+
throw new Error('Invalid GLB: JSON chunk extends beyond file size');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function validateBinaryChunk(dataView, binaryChunkOffset) {
|
|
36
|
+
if (dataView.byteLength <= binaryChunkOffset + 8) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const binaryChunkType = dataView.getUint32(binaryChunkOffset + 4, true);
|
|
41
|
+
if (binaryChunkType !== 0x004E4942) {
|
|
42
|
+
throw new Error('Invalid GLB: Second chunk is not BIN');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function isRemovalOperation(binaryData) {
|
|
49
|
+
return !binaryData || binaryData.byteLength === 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function hasExtension(gltf, extensionName) {
|
|
53
|
+
return gltf.extensions && gltf.extensions[extensionName];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function hasBufferURI(buffer) {
|
|
57
|
+
return buffer.uri;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function isDataURI(uri) {
|
|
61
|
+
return uri.startsWith('data:');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function getChunkInfo(dataView) {
|
|
65
|
+
const jsonChunkLength = dataView.getUint32(12, true);
|
|
66
|
+
const jsonChunkType = dataView.getUint32(16, true);
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
jsonChunkLength,
|
|
70
|
+
jsonChunkType,
|
|
71
|
+
jsonStart: 20,
|
|
72
|
+
jsonEnd: 20 + jsonChunkLength,
|
|
73
|
+
binaryChunkOffset: 20 + jsonChunkLength
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Analyze the rig data in a GLTF model
|
|
79
|
+
* @param {Object} gltf - The loaded GLTF model data
|
|
80
|
+
* @returns {Object} Analyzed rig details
|
|
81
|
+
*/
|
|
82
|
+
export function analyzeGltfModel(gltf) {
|
|
83
|
+
console.log('analyzeGltfModel called with:', gltf);
|
|
84
|
+
|
|
85
|
+
if (!gltf || !gltf.scene) {
|
|
86
|
+
console.error('Invalid GLTF model:', gltf);
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
console.log('Analyzing GLTF model for rig information...');
|
|
91
|
+
console.log('GLTF scene structure:', gltf.scene);
|
|
92
|
+
|
|
93
|
+
const rawDetails = {
|
|
94
|
+
bones: [],
|
|
95
|
+
rigs: [],
|
|
96
|
+
roots: [],
|
|
97
|
+
controls: [],
|
|
98
|
+
joints: [],
|
|
99
|
+
constraints: [] // Add a new array to track constraints
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// Extract scene information
|
|
103
|
+
const scene = gltf.scene;
|
|
104
|
+
|
|
105
|
+
// Helper function to traverse the scene
|
|
106
|
+
const traverseNode = (node, parentType = null) => {
|
|
107
|
+
// Look for joint constraints in this node
|
|
108
|
+
const constraints = parseJointConstraints(node);
|
|
109
|
+
if (constraints) {
|
|
110
|
+
rawDetails.constraints.push({
|
|
111
|
+
nodeName: node.name,
|
|
112
|
+
nodeType: node.type,
|
|
113
|
+
constraintType: constraints.type,
|
|
114
|
+
constraintData: constraints
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Check if the node is a bone
|
|
119
|
+
if (node.isBone || node.name.toLowerCase().includes('bone')) {
|
|
120
|
+
rawDetails.bones.push({
|
|
121
|
+
name: node.name,
|
|
122
|
+
position: node.position ? [node.position.x, node.position.y, node.position.z] : null,
|
|
123
|
+
rotation: node.rotation ? [node.rotation.x, node.rotation.y, node.rotation.z] : null,
|
|
124
|
+
parentName: parentType === 'bone' ? node.parent.name : null,
|
|
125
|
+
constraintType: constraints ? constraints.type : 'none' // Add constraint type
|
|
126
|
+
});
|
|
127
|
+
parentType = 'bone';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Check if the node is a rig
|
|
131
|
+
if (node.name.toLowerCase().includes('rig') || node.name.toLowerCase().includes('armature')) {
|
|
132
|
+
rawDetails.rigs.push({
|
|
133
|
+
name: node.name,
|
|
134
|
+
position: node.position ? [node.position.x, node.position.y, node.position.z] : null,
|
|
135
|
+
childCount: node.children ? node.children.length : 0
|
|
136
|
+
});
|
|
137
|
+
parentType = 'rig';
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Check if the node is a root
|
|
141
|
+
if (node.name.toLowerCase().includes('root')) {
|
|
142
|
+
rawDetails.roots.push({
|
|
143
|
+
name: node.name,
|
|
144
|
+
position: node.position ? [node.position.x, node.position.y, node.position.z] : null
|
|
145
|
+
});
|
|
146
|
+
parentType = 'root';
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Check if the node is a control/handle
|
|
150
|
+
if (node.name.toLowerCase().includes('control') ||
|
|
151
|
+
node.name.toLowerCase().includes('ctrl') ||
|
|
152
|
+
node.name.toLowerCase().includes('handle')) {
|
|
153
|
+
rawDetails.controls.push({
|
|
154
|
+
name: node.name,
|
|
155
|
+
position: node.position ? [node.position.x, node.position.y, node.position.z] : null,
|
|
156
|
+
type: node.name.toLowerCase().includes('control') ? 'control' :
|
|
157
|
+
node.name.toLowerCase().includes('ctrl') ? 'ctrl' : 'handle'
|
|
158
|
+
});
|
|
159
|
+
parentType = 'control';
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Traverse children
|
|
163
|
+
if (node.children) {
|
|
164
|
+
node.children.forEach(child => traverseNode(child, parentType));
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// Start traversal from the scene
|
|
169
|
+
scene.traverse(node => traverseNode(node));
|
|
170
|
+
|
|
171
|
+
// Process raw details to deduplicate items
|
|
172
|
+
const result = {
|
|
173
|
+
bones: deduplicateItemsByName(rawDetails.bones),
|
|
174
|
+
rigs: deduplicateItemsByName(rawDetails.rigs),
|
|
175
|
+
roots: deduplicateItemsByName(rawDetails.roots),
|
|
176
|
+
controls: deduplicateItemsByName(rawDetails.controls),
|
|
177
|
+
joints: deduplicateItemsByName(rawDetails.joints),
|
|
178
|
+
constraints: rawDetails.constraints // Don't deduplicate constraints
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
console.log('Rig analysis results:', result);
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Parse joint constraint data from node extras or userData
|
|
187
|
+
* @param {Object} node - Bone/joint node to examine
|
|
188
|
+
* @returns {Object|null} - Constraint data if found, null otherwise
|
|
189
|
+
*/
|
|
190
|
+
export function parseJointConstraints(node) {
|
|
191
|
+
// Initialize constraint object
|
|
192
|
+
let constraints = null;
|
|
193
|
+
|
|
194
|
+
// Check for constraints in userData
|
|
195
|
+
if (node.userData) {
|
|
196
|
+
// Look for Blender constraint data in userData
|
|
197
|
+
if (node.userData.constraints || node.userData.boneConstraints) {
|
|
198
|
+
constraints = node.userData.constraints || node.userData.boneConstraints;
|
|
199
|
+
return constraints;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Check for limit_rotation type constraints
|
|
203
|
+
if (node.userData.limitRotation || node.userData.rotationLimits) {
|
|
204
|
+
constraints = {
|
|
205
|
+
type: 'limitRotation',
|
|
206
|
+
limits: node.userData.limitRotation || node.userData.rotationLimits
|
|
207
|
+
};
|
|
208
|
+
return constraints;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Look for GLTF extras (where custom data is often stored)
|
|
213
|
+
if (node.extras) {
|
|
214
|
+
if (node.extras.constraints || node.extras.jointType) {
|
|
215
|
+
constraints = node.extras.constraints || { type: node.extras.jointType };
|
|
216
|
+
return constraints;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Infer constraints from bone properties
|
|
221
|
+
// Check if bone has locked rotation axes
|
|
222
|
+
if (node.rotation && node.userData.initialRotation) {
|
|
223
|
+
// Look for zero initial rotations that might indicate locked axes
|
|
224
|
+
const lockedAxes = [];
|
|
225
|
+
const epsilon = 0.0001; // Small threshold for floating point comparison
|
|
226
|
+
|
|
227
|
+
if (Math.abs(node.userData.initialRotation.x) < epsilon) lockedAxes.push('x');
|
|
228
|
+
if (Math.abs(node.userData.initialRotation.y) < epsilon) lockedAxes.push('y');
|
|
229
|
+
if (Math.abs(node.userData.initialRotation.z) < epsilon) lockedAxes.push('z');
|
|
230
|
+
|
|
231
|
+
// If we have 2+ locked axes, this might be a hinge joint
|
|
232
|
+
if (lockedAxes.length >= 2) {
|
|
233
|
+
const freeAxis = ['x', 'y', 'z'].find(axis => !lockedAxes.includes(axis));
|
|
234
|
+
if (freeAxis) {
|
|
235
|
+
constraints = {
|
|
236
|
+
type: 'hinge',
|
|
237
|
+
axis: freeAxis
|
|
238
|
+
};
|
|
239
|
+
return constraints;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// If all axes are locked, this might be a fixed joint
|
|
244
|
+
if (lockedAxes.length === 3) {
|
|
245
|
+
constraints = {
|
|
246
|
+
type: 'fixed'
|
|
247
|
+
};
|
|
248
|
+
return constraints;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Check naming conventions that might indicate constraint types
|
|
253
|
+
const lowerName = node.name.toLowerCase();
|
|
254
|
+
if (lowerName.includes('fixed') || lowerName.includes('rigid')) {
|
|
255
|
+
constraints = {
|
|
256
|
+
type: 'fixed'
|
|
257
|
+
};
|
|
258
|
+
return constraints;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (lowerName.includes('hinge') || lowerName.includes('elbow') || lowerName.includes('knee')) {
|
|
262
|
+
// Default to Y axis for hinge if not specified
|
|
263
|
+
constraints = {
|
|
264
|
+
type: 'hinge',
|
|
265
|
+
axis: lowerName.includes('_x') ? 'x' : (lowerName.includes('_z') ? 'z' : 'y')
|
|
266
|
+
};
|
|
267
|
+
return constraints;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (lowerName.includes('spring') || lowerName.includes('bounce')) {
|
|
271
|
+
constraints = {
|
|
272
|
+
type: 'spring',
|
|
273
|
+
stiffness: 50, // Default stiffness
|
|
274
|
+
damping: 5 // Default damping
|
|
275
|
+
};
|
|
276
|
+
return constraints;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// No constraints found
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// TODO Create a function for determining if a glb has a display mesh
|
|
284
|
+
|
|
285
|
+
// TODO Create a function for getting the mesh index of the display mesh
|
|
286
|
+
|
|
287
|
+
// TODO Create a function that calls the two above and then uses that info to get the binary buffer for the display mesh
|
|
288
|
+
// Then checks to see if Rig Control Node is true in the buffer
|
|
289
|
+
// If it is calls new method to make the display mesh a control node
|
|
290
|
+
// Stub it out for now that will be next task
|