@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.
Files changed (114) hide show
  1. package/bin/cli.js +69 -0
  2. package/package.json +13 -7
  3. package/src/asset_debugger/axis-indicator/axis-indicator.css +6 -0
  4. package/src/asset_debugger/axis-indicator/axis-indicator.html +20 -0
  5. package/src/asset_debugger/axis-indicator/axis-indicator.js +822 -0
  6. package/src/asset_debugger/debugger-scene/debugger-scene.css +142 -0
  7. package/src/asset_debugger/debugger-scene/debugger-scene.html +80 -0
  8. package/src/asset_debugger/debugger-scene/debugger-scene.js +791 -0
  9. package/src/asset_debugger/header/header.css +73 -0
  10. package/src/asset_debugger/header/header.html +24 -0
  11. package/src/asset_debugger/header/header.js +224 -0
  12. package/src/asset_debugger/index.html +76 -0
  13. package/src/asset_debugger/landing-page/landing-page.css +396 -0
  14. package/src/asset_debugger/landing-page/landing-page.html +81 -0
  15. package/src/asset_debugger/landing-page/landing-page.js +611 -0
  16. package/src/asset_debugger/loading-splash/loading-splash.css +195 -0
  17. package/src/asset_debugger/loading-splash/loading-splash.html +22 -0
  18. package/src/asset_debugger/loading-splash/loading-splash.js +59 -0
  19. package/src/asset_debugger/loading-splash/preview-loading-splash.js +66 -0
  20. package/src/asset_debugger/main.css +14 -0
  21. package/src/asset_debugger/modals/examples-modal/examples-modal.css +41 -0
  22. package/src/asset_debugger/modals/examples-modal/examples-modal.html +18 -0
  23. package/src/asset_debugger/modals/examples-modal/examples-modal.js +111 -0
  24. package/src/asset_debugger/modals/examples-modal/examples.js +125 -0
  25. package/src/asset_debugger/modals/html-editor-modal/html-editor-modal.css +452 -0
  26. package/src/asset_debugger/modals/html-editor-modal/html-editor-modal.html +87 -0
  27. package/src/asset_debugger/modals/html-editor-modal/html-editor-modal.js +675 -0
  28. package/src/asset_debugger/modals/mesh-info-modal/mesh-info-modal.css +219 -0
  29. package/src/asset_debugger/modals/mesh-info-modal/mesh-info-modal.html +20 -0
  30. package/src/asset_debugger/modals/mesh-info-modal/mesh-info-modal.js +548 -0
  31. package/src/asset_debugger/modals/settings-modal/settings-modal.css +103 -0
  32. package/src/asset_debugger/modals/settings-modal/settings-modal.html +158 -0
  33. package/src/asset_debugger/modals/settings-modal/settings-modal.js +475 -0
  34. package/src/asset_debugger/panels/asset-panel/asset-panel.css +263 -0
  35. package/src/asset_debugger/panels/asset-panel/asset-panel.html +123 -0
  36. package/src/asset_debugger/panels/asset-panel/asset-panel.js +136 -0
  37. package/src/asset_debugger/panels/asset-panel/atlas-heading/atlas-heading.css +94 -0
  38. package/src/asset_debugger/panels/asset-panel/atlas-heading/atlas-heading.js +312 -0
  39. package/src/asset_debugger/panels/asset-panel/mesh-heading/mesh-heading.css +129 -0
  40. package/src/asset_debugger/panels/asset-panel/mesh-heading/mesh-heading.js +486 -0
  41. package/src/asset_debugger/panels/asset-panel/rig-heading/rig-heading.css +545 -0
  42. package/src/asset_debugger/panels/asset-panel/rig-heading/rig-heading.js +538 -0
  43. package/src/asset_debugger/panels/asset-panel/uv-heading/uv-heading.css +70 -0
  44. package/src/asset_debugger/panels/asset-panel/uv-heading/uv-heading.js +586 -0
  45. package/src/asset_debugger/panels/world-panel/world-panel.css +364 -0
  46. package/src/asset_debugger/panels/world-panel/world-panel.html +173 -0
  47. package/src/asset_debugger/panels/world-panel/world-panel.js +1891 -0
  48. package/src/asset_debugger/router.js +190 -0
  49. package/src/asset_debugger/util/animation/playback/animation-playback-controller.js +150 -0
  50. package/src/asset_debugger/util/animation/playback/animation-preview-controller.js +316 -0
  51. package/src/asset_debugger/util/animation/playback/css3d-bounce-controller.js +400 -0
  52. package/src/asset_debugger/util/animation/playback/css3d-reversal-controller.js +821 -0
  53. package/src/asset_debugger/util/animation/render/css3d-prerender-controller.js +696 -0
  54. package/src/asset_debugger/util/animation/render/debug-texture-factory.js +0 -0
  55. package/src/asset_debugger/util/animation/render/iframe2texture-render-controller.js +199 -0
  56. package/src/asset_debugger/util/animation/render/image2texture-prerender-controller.js +461 -0
  57. package/src/asset_debugger/util/animation/render/pbr-material-factory.js +82 -0
  58. package/src/asset_debugger/util/common.css +280 -0
  59. package/src/asset_debugger/util/data/animation-classifier.js +323 -0
  60. package/src/asset_debugger/util/data/duplicate-handler.js +20 -0
  61. package/src/asset_debugger/util/data/glb-buffer-manager.js +407 -0
  62. package/src/asset_debugger/util/data/glb-classifier.js +290 -0
  63. package/src/asset_debugger/util/data/html-formatter.js +76 -0
  64. package/src/asset_debugger/util/data/html-linter.js +276 -0
  65. package/src/asset_debugger/util/data/localstorage-manager.js +265 -0
  66. package/src/asset_debugger/util/data/mesh-html-manager.js +295 -0
  67. package/src/asset_debugger/util/data/string-serder.js +303 -0
  68. package/src/asset_debugger/util/data/texture-classifier.js +663 -0
  69. package/src/asset_debugger/util/data/upload/background-file-handler.js +292 -0
  70. package/src/asset_debugger/util/data/upload/dropzone-preview-controller.js +396 -0
  71. package/src/asset_debugger/util/data/upload/file-upload-manager.js +495 -0
  72. package/src/asset_debugger/util/data/upload/glb-file-handler.js +36 -0
  73. package/src/asset_debugger/util/data/upload/glb-preview-controller.js +317 -0
  74. package/src/asset_debugger/util/data/upload/lighting-file-handler.js +194 -0
  75. package/src/asset_debugger/util/data/upload/model-file-manager.js +104 -0
  76. package/src/asset_debugger/util/data/upload/texture-file-handler.js +166 -0
  77. package/src/asset_debugger/util/data/upload/zip-handler.js +686 -0
  78. package/src/asset_debugger/util/loaders/html2canvas-loader.js +107 -0
  79. package/src/asset_debugger/util/rig/bone-kinematics.js +403 -0
  80. package/src/asset_debugger/util/rig/rig-constraint-manager.js +618 -0
  81. package/src/asset_debugger/util/rig/rig-controller.js +612 -0
  82. package/src/asset_debugger/util/rig/rig-factory.js +628 -0
  83. package/src/asset_debugger/util/rig/rig-handle-factory.js +46 -0
  84. package/src/asset_debugger/util/rig/rig-label-factory.js +441 -0
  85. package/src/asset_debugger/util/rig/rig-mouse-handler.js +377 -0
  86. package/src/asset_debugger/util/rig/rig-state-manager.js +175 -0
  87. package/src/asset_debugger/util/rig/rig-tooltip-manager.js +267 -0
  88. package/src/asset_debugger/util/rig/rig-ui-factory.js +700 -0
  89. package/src/asset_debugger/util/scene/background-manager.js +284 -0
  90. package/src/asset_debugger/util/scene/camera-controller.js +243 -0
  91. package/src/asset_debugger/util/scene/css3d-debug-controller.js +406 -0
  92. package/src/asset_debugger/util/scene/css3d-frame-factory.js +113 -0
  93. package/src/asset_debugger/util/scene/css3d-scene-manager.js +529 -0
  94. package/src/asset_debugger/util/scene/glb-controller.js +208 -0
  95. package/src/asset_debugger/util/scene/lighting-manager.js +690 -0
  96. package/src/asset_debugger/util/scene/threejs-model-manager.js +437 -0
  97. package/src/asset_debugger/util/scene/threejs-preview-manager.js +207 -0
  98. package/src/asset_debugger/util/scene/threejs-preview-setup.js +478 -0
  99. package/src/asset_debugger/util/scene/threejs-scene-controller.js +286 -0
  100. package/src/asset_debugger/util/scene/ui-manager.js +107 -0
  101. package/src/asset_debugger/util/state/animation-state.js +128 -0
  102. package/src/asset_debugger/util/state/css3d-state.js +83 -0
  103. package/src/asset_debugger/util/state/glb-preview-state.js +31 -0
  104. package/src/asset_debugger/util/state/log-util.js +197 -0
  105. package/src/asset_debugger/util/state/scene-state.js +452 -0
  106. package/src/asset_debugger/util/state/threejs-state.js +54 -0
  107. package/src/asset_debugger/util/workers/lighting-worker.js +61 -0
  108. package/src/asset_debugger/util/workers/model-worker.js +109 -0
  109. package/src/asset_debugger/util/workers/texture-worker.js +54 -0
  110. package/src/asset_debugger/util/workers/worker-manager.js +212 -0
  111. package/src/asset_debugger/widgets/mesh-info-widget.js +280 -0
  112. package/src/index.html +261 -0
  113. package/src/index.js +8 -0
  114. 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