@fonsecabarreto/genesis-gl-core 0.1.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 (62) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +33 -0
  3. package/dist/Camera-DY_8gx3C.d.ts +45 -0
  4. package/dist/Core/classes/Material.d.ts +3 -0
  5. package/dist/Core/classes/Material.js +9 -0
  6. package/dist/Core/classes/Material.js.map +1 -0
  7. package/dist/Core/classes/Model.d.ts +5 -0
  8. package/dist/Core/classes/Model.js +7 -0
  9. package/dist/Core/classes/Model.js.map +1 -0
  10. package/dist/Core/classes/Renderer.d.ts +30 -0
  11. package/dist/Core/classes/Renderer.js +11 -0
  12. package/dist/Core/classes/Renderer.js.map +1 -0
  13. package/dist/Core/classes/Scene.d.ts +37 -0
  14. package/dist/Core/classes/Scene.js +7 -0
  15. package/dist/Core/classes/Scene.js.map +1 -0
  16. package/dist/Core/classes/Viewport.d.ts +37 -0
  17. package/dist/Core/classes/Viewport.js +7 -0
  18. package/dist/Core/classes/Viewport.js.map +1 -0
  19. package/dist/Core/domain/interfaces/Vectors.d.ts +4 -0
  20. package/dist/Core/domain/interfaces/Vectors.js +1 -0
  21. package/dist/Core/domain/interfaces/Vectors.js.map +1 -0
  22. package/dist/Core/index.d.ts +10 -0
  23. package/dist/Core/index.js +51 -0
  24. package/dist/Core/index.js.map +1 -0
  25. package/dist/Core/utils/get-overlap.d.ts +3 -0
  26. package/dist/Core/utils/get-overlap.js +11 -0
  27. package/dist/Core/utils/get-overlap.js.map +1 -0
  28. package/dist/Core/utils/load-glb.d.ts +101 -0
  29. package/dist/Core/utils/load-glb.js +697 -0
  30. package/dist/Core/utils/load-glb.js.map +1 -0
  31. package/dist/Core/utils/parse-obj.d.ts +10 -0
  32. package/dist/Core/utils/parse-obj.js +183 -0
  33. package/dist/Core/utils/parse-obj.js.map +1 -0
  34. package/dist/Editor/index.d.ts +364 -0
  35. package/dist/Editor/index.js +1737 -0
  36. package/dist/Editor/index.js.map +1 -0
  37. package/dist/Game/controls/KeyboardInput.d.ts +8 -0
  38. package/dist/Game/controls/KeyboardInput.js +7 -0
  39. package/dist/Game/controls/KeyboardInput.js.map +1 -0
  40. package/dist/Game/index.d.ts +45 -0
  41. package/dist/Game/index.js +353 -0
  42. package/dist/Game/index.js.map +1 -0
  43. package/dist/KeyboardControl-5w7Vm0J0.d.ts +18 -0
  44. package/dist/KeyboardInput-DTsfj3tE.d.ts +166 -0
  45. package/dist/Material-BGLkldxv.d.ts +74 -0
  46. package/dist/Model-CQvDXd-b.d.ts +302 -0
  47. package/dist/WebGLCore-DR7ZHJB0.d.ts +22 -0
  48. package/dist/chunk-3ULETMWF.js +144 -0
  49. package/dist/chunk-3ULETMWF.js.map +1 -0
  50. package/dist/chunk-5TAAXI6S.js +330 -0
  51. package/dist/chunk-5TAAXI6S.js.map +1 -0
  52. package/dist/chunk-6LS6AO5H.js +296 -0
  53. package/dist/chunk-6LS6AO5H.js.map +1 -0
  54. package/dist/chunk-JK2HEZAT.js +317 -0
  55. package/dist/chunk-JK2HEZAT.js.map +1 -0
  56. package/dist/chunk-P7QOKDLY.js +57 -0
  57. package/dist/chunk-P7QOKDLY.js.map +1 -0
  58. package/dist/chunk-QCQVJCSR.js +968 -0
  59. package/dist/chunk-QCQVJCSR.js.map +1 -0
  60. package/dist/chunk-SUNYSY45.js +81 -0
  61. package/dist/chunk-SUNYSY45.js.map +1 -0
  62. package/package.json +83 -0
@@ -0,0 +1,697 @@
1
+ import {
2
+ AnimationClip,
3
+ GL_TRIANGLES,
4
+ Mesh,
5
+ Skeleton
6
+ } from "../../chunk-QCQVJCSR.js";
7
+ import {
8
+ Material
9
+ } from "../../chunk-6LS6AO5H.js";
10
+ import {
11
+ Model
12
+ } from "../../chunk-JK2HEZAT.js";
13
+ import "../../chunk-3ULETMWF.js";
14
+ import "../../chunk-5TAAXI6S.js";
15
+
16
+ // src/Core/utils/load-glb.ts
17
+ import { mat4, quat, vec3 } from "gl-matrix";
18
+ var GLB_MAGIC = 1179937895;
19
+ var GLB_CHUNK_JSON = 1313821514;
20
+ var GLB_CHUNK_BIN = 5130562;
21
+ var GLB_HEADER_SIZE = 12;
22
+ var CHUNK_HEADER_SIZE = 8;
23
+ var COMPONENT_COUNT = {
24
+ SCALAR: 1,
25
+ VEC2: 2,
26
+ VEC3: 3,
27
+ VEC4: 4,
28
+ MAT2: 4,
29
+ MAT3: 9,
30
+ MAT4: 16
31
+ };
32
+ var TYPED_ARRAY_CTOR = {
33
+ [5120 /* BYTE */]: Int8Array,
34
+ [5121 /* UNSIGNED_BYTE */]: Uint8Array,
35
+ [5122 /* SHORT */]: Int16Array,
36
+ [5123 /* UNSIGNED_SHORT */]: Uint16Array,
37
+ [5125 /* UNSIGNED_INT */]: Uint32Array,
38
+ [5126 /* FLOAT */]: Float32Array
39
+ };
40
+ var COMPONENT_SIZES = {
41
+ [5120 /* BYTE */]: 1,
42
+ [5121 /* UNSIGNED_BYTE */]: 1,
43
+ [5122 /* SHORT */]: 2,
44
+ [5123 /* UNSIGNED_SHORT */]: 2,
45
+ [5125 /* UNSIGNED_INT */]: 4,
46
+ [5126 /* FLOAT */]: 4
47
+ };
48
+ var GLBLoader = class {
49
+ gl;
50
+ core;
51
+ constructor(core) {
52
+ this.gl = core.gl;
53
+ this.core = core;
54
+ }
55
+ // ━━━ Public API ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
56
+ /**
57
+ * Fetch and parse a `.glb` file.
58
+ *
59
+ * @param url - URL to the GLB resource.
60
+ * @param name - Logical name assigned to the resulting {@link Model}.
61
+ * @returns Parsed model, optional skeleton, and animation clips.
62
+ */
63
+ async load(url, name = "model") {
64
+ const response = await fetch(url);
65
+ if (!response.ok) {
66
+ throw new Error(
67
+ `[GLBLoader] Failed to fetch "${url}" (HTTP ${response.status})`
68
+ );
69
+ }
70
+ const arrayBuffer = await response.arrayBuffer();
71
+ return this.parse(arrayBuffer, name);
72
+ }
73
+ /**
74
+ * Parse a raw GLB `ArrayBuffer` that was already loaded (e.g. from drag-and-drop).
75
+ */
76
+ async parse(arrayBuffer, name = "model") {
77
+ const { gltf, binChunkData } = this.parseGlbHeader(arrayBuffer);
78
+ const buffers = [binChunkData];
79
+ let skeleton = null;
80
+ if (gltf.skins && gltf.skins.length > 0) {
81
+ skeleton = this.parseSkin(gltf, buffers, 0);
82
+ skeleton.computeJointMatrices();
83
+ }
84
+ const meshes = await this.parseMeshes(gltf, buffers, skeleton);
85
+ const animations = /* @__PURE__ */ new Map();
86
+ if (gltf.animations && skeleton) {
87
+ const nodeToJoint = /* @__PURE__ */ new Map();
88
+ for (let ji = 0; ji < skeleton.joints.length; ji++) {
89
+ nodeToJoint.set(skeleton.joints[ji].nodeIndex, ji);
90
+ }
91
+ for (let ai = 0; ai < gltf.animations.length; ai++) {
92
+ const clip = this.parseAnimation(gltf, buffers, ai, nodeToJoint);
93
+ animations.set(clip.name, clip);
94
+ }
95
+ }
96
+ const model = new Model(meshes, [0, 0, 0], [1, 1, 1], name, skeleton);
97
+ const center = model.getCenter();
98
+ model.setTranslation(
99
+ model.translation[0] - center[0],
100
+ model.translation[1],
101
+ model.translation[2] - center[2]
102
+ );
103
+ return { model, skeleton, animations };
104
+ }
105
+ // ━━━ GLB header parsing ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
106
+ parseGlbHeader(arrayBuffer) {
107
+ const view = new DataView(arrayBuffer);
108
+ const magic = view.getUint32(0, true);
109
+ if (magic !== GLB_MAGIC) {
110
+ throw new Error(
111
+ `[GLBLoader] Invalid GLB magic: 0x${magic.toString(16)} (expected 0x${GLB_MAGIC.toString(16)})`
112
+ );
113
+ }
114
+ const version = view.getUint32(4, true);
115
+ if (version !== 2) {
116
+ throw new Error(
117
+ `[GLBLoader] Unsupported glTF version ${version} (only v2 is supported)`
118
+ );
119
+ }
120
+ const jsonChunkLength = view.getUint32(GLB_HEADER_SIZE, true);
121
+ const jsonChunkType = view.getUint32(GLB_HEADER_SIZE + 4, true);
122
+ if (jsonChunkType !== GLB_CHUNK_JSON) {
123
+ throw new Error("[GLBLoader] First chunk is not JSON");
124
+ }
125
+ const jsonStart = GLB_HEADER_SIZE + CHUNK_HEADER_SIZE;
126
+ const jsonText = new TextDecoder().decode(
127
+ new Uint8Array(arrayBuffer, jsonStart, jsonChunkLength)
128
+ );
129
+ const gltf = JSON.parse(jsonText);
130
+ const binChunkOffset = jsonStart + jsonChunkLength;
131
+ const binChunkLength = view.getUint32(binChunkOffset, true);
132
+ const binChunkType = view.getUint32(binChunkOffset + 4, true);
133
+ if (binChunkType !== GLB_CHUNK_BIN) {
134
+ throw new Error("[GLBLoader] Second chunk is not BIN");
135
+ }
136
+ const binChunkData = arrayBuffer.slice(
137
+ binChunkOffset + CHUNK_HEADER_SIZE,
138
+ binChunkOffset + CHUNK_HEADER_SIZE + binChunkLength
139
+ );
140
+ return { gltf, binChunkData };
141
+ }
142
+ // ━━━ Mesh parsing ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
143
+ /**
144
+ * Walk the active scene's node tree and return all renderable meshes.
145
+ *
146
+ * Each scene node that references a mesh is visited in hierarchy order.
147
+ * Non-skinned mesh nodes that are children of joint nodes (e.g. eyes /
148
+ * hair parented to the Head bone in Blender) are automatically handled:
149
+ * their vertex data is pre-transformed into skin-space and synthetic
150
+ * single-joint weights are generated so they deform with the parent bone.
151
+ *
152
+ * @param skeleton – already-parsed skin for this model (or null).
153
+ */
154
+ async parseMeshes(gltf, buffers, skeleton) {
155
+ if (!gltf.meshes || !gltf.nodes) return [];
156
+ const nodeToJointIndex = /* @__PURE__ */ new Map();
157
+ if (skeleton) {
158
+ for (let ji = 0; ji < skeleton.joints.length; ji++) {
159
+ nodeToJointIndex.set(skeleton.joints[ji].nodeIndex, ji);
160
+ }
161
+ }
162
+ const nodeParentMap = /* @__PURE__ */ new Map();
163
+ for (let ni = 0; ni < gltf.nodes.length; ni++) {
164
+ const children = gltf.nodes[ni].children;
165
+ if (children) {
166
+ for (const ci of children) nodeParentMap.set(ci, ni);
167
+ }
168
+ }
169
+ const findAncestorJoint = (nodeIdx) => {
170
+ let cur = nodeParentMap.get(nodeIdx);
171
+ while (cur !== void 0) {
172
+ const ji = nodeToJointIndex.get(cur);
173
+ if (ji !== void 0) return ji;
174
+ cur = nodeParentMap.get(cur);
175
+ }
176
+ return null;
177
+ };
178
+ const entries = [];
179
+ const sceneNodes = gltf.scenes?.[gltf.scene ?? 0]?.nodes ?? [];
180
+ const walk = (nodeIdx) => {
181
+ const node = gltf.nodes[nodeIdx];
182
+ if (node.mesh !== void 0) {
183
+ entries.push({ meshIdx: node.mesh, skinIdx: node.skin, nodeIdx });
184
+ }
185
+ node.children?.forEach((c) => walk(c));
186
+ };
187
+ for (const r of sceneNodes) walk(r);
188
+ const meshes = [];
189
+ for (const { meshIdx, skinIdx, nodeIdx } of entries) {
190
+ const meshDef = gltf.meshes[meshIdx];
191
+ for (let pi = 0; pi < meshDef.primitives.length; pi++) {
192
+ const prim = meshDef.primitives[pi];
193
+ const mesh = await this.parsePrimitive(
194
+ gltf,
195
+ buffers,
196
+ prim,
197
+ meshDef.name ?? `mesh_${meshIdx}_${pi}`
198
+ );
199
+ if (!mesh.isSkinned && skeleton && skinIdx === void 0) {
200
+ const ancestorJointIdx = findAncestorJoint(nodeIdx);
201
+ if (ancestorJointIdx !== null) {
202
+ this.applyBoneParenting(
203
+ mesh,
204
+ gltf.nodes[nodeIdx],
205
+ ancestorJointIdx,
206
+ skeleton.joints[ancestorJointIdx].inverseBindMatrix
207
+ );
208
+ }
209
+ }
210
+ meshes.push(mesh);
211
+ }
212
+ }
213
+ return meshes;
214
+ }
215
+ /**
216
+ * Pre-transform a bone-parented non-skinned mesh into skin coordinate space
217
+ * and synthesise single-joint weights so it deforms with its parent bone.
218
+ *
219
+ * The transform applied to each vertex is:
220
+ * T = inv(IBM_parentJoint) × nodeLocalTransform
221
+ *
222
+ * After this call `mesh.isSkinned` returns `true`.
223
+ */
224
+ applyBoneParenting(mesh, node, jointIdx, ibm) {
225
+ const bindGlobal = mat4.create();
226
+ mat4.invert(bindGlobal, ibm);
227
+ const nodeLocal = this.getNodeLocalMatrix(node);
228
+ const preT = mat4.create();
229
+ mat4.multiply(preT, bindGlobal, nodeLocal);
230
+ const normMat = mat4.create();
231
+ mat4.invert(normMat, preT);
232
+ mat4.transpose(normMat, normMat);
233
+ const srcV = mesh.vertices;
234
+ const v = new Float32Array(srcV.length);
235
+ for (let i = 0; i < srcV.length; i += 3) {
236
+ const x = srcV[i], y = srcV[i + 1], z = srcV[i + 2];
237
+ v[i] = preT[0] * x + preT[4] * y + preT[8] * z + preT[12];
238
+ v[i + 1] = preT[1] * x + preT[5] * y + preT[9] * z + preT[13];
239
+ v[i + 2] = preT[2] * x + preT[6] * y + preT[10] * z + preT[14];
240
+ }
241
+ mesh.vertices = v;
242
+ const srcN = mesh.normals;
243
+ if (srcN && srcN.length > 0) {
244
+ const n = new Float32Array(srcN.length);
245
+ for (let i = 0; i < srcN.length; i += 3) {
246
+ const x = srcN[i], y = srcN[i + 1], z = srcN[i + 2];
247
+ n[i] = normMat[0] * x + normMat[4] * y + normMat[8] * z;
248
+ n[i + 1] = normMat[1] * x + normMat[5] * y + normMat[9] * z;
249
+ n[i + 2] = normMat[2] * x + normMat[6] * y + normMat[10] * z;
250
+ }
251
+ mesh.normals = n;
252
+ }
253
+ const vertexCount = mesh.vertices.length / 3;
254
+ const ji = new Float32Array(vertexCount * 4);
255
+ const jw = new Float32Array(vertexCount * 4);
256
+ for (let i = 0; i < vertexCount; i++) {
257
+ ji[i * 4] = jointIdx;
258
+ jw[i * 4] = 1;
259
+ }
260
+ mesh.jointIndices = ji;
261
+ mesh.jointWeights = jw;
262
+ }
263
+ async parsePrimitive(gltf, buffers, prim, name) {
264
+ const accessors = gltf.accessors;
265
+ const bufferViews = gltf.bufferViews;
266
+ const vertices = this.getAccessorData(
267
+ accessors[prim.attributes.POSITION],
268
+ bufferViews,
269
+ buffers
270
+ );
271
+ const normals = prim.attributes.NORMAL !== void 0 ? this.getAccessorData(
272
+ accessors[prim.attributes.NORMAL],
273
+ bufferViews,
274
+ buffers
275
+ ) : this.generateFlatNormals(vertices);
276
+ const texCoords = prim.attributes.TEXCOORD_0 !== void 0 ? this.getAccessorData(
277
+ accessors[prim.attributes.TEXCOORD_0],
278
+ bufferViews,
279
+ buffers
280
+ ) : new Float32Array();
281
+ const indices = prim.indices !== void 0 ? this.getAccessorData(
282
+ accessors[prim.indices],
283
+ bufferViews,
284
+ buffers
285
+ ) : null;
286
+ const material = await this.parseMaterial(gltf, buffers, prim.material);
287
+ const mesh = new Mesh(
288
+ name,
289
+ vertices,
290
+ normals,
291
+ material,
292
+ texCoords,
293
+ indices
294
+ );
295
+ mesh.setMode(prim.mode ?? GL_TRIANGLES);
296
+ if (prim.attributes.JOINTS_0 !== void 0 && prim.attributes.WEIGHTS_0 !== void 0) {
297
+ const rawJoints = this.getAccessorData(
298
+ accessors[prim.attributes.JOINTS_0],
299
+ bufferViews,
300
+ buffers
301
+ );
302
+ mesh.jointIndices = new Float32Array(rawJoints);
303
+ mesh.jointWeights = this.getAccessorData(
304
+ accessors[prim.attributes.WEIGHTS_0],
305
+ bufferViews,
306
+ buffers
307
+ );
308
+ }
309
+ return mesh;
310
+ }
311
+ // ━━━ Material parsing ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
312
+ async parseMaterial(gltf, buffers, materialIndex) {
313
+ const material = new Material(this.core, {});
314
+ if (materialIndex === void 0 || !gltf.materials) return material;
315
+ const matDef = gltf.materials[materialIndex];
316
+ if (!matDef) return material;
317
+ const pbr = matDef.pbrMetallicRoughness;
318
+ if (pbr) {
319
+ if (pbr.baseColorFactor) {
320
+ const [r, g, b, a] = pbr.baseColorFactor;
321
+ material.albedoColor = [r, g, b, a];
322
+ material.diffuse = [r, g, b];
323
+ material.dissolve = a;
324
+ }
325
+ if (pbr.baseColorTexture !== void 0) {
326
+ const tex = await this.loadEmbeddedTexture(
327
+ gltf,
328
+ buffers,
329
+ pbr.baseColorTexture.index
330
+ );
331
+ if (tex) material.texture = tex;
332
+ }
333
+ const roughness = pbr.roughnessFactor ?? 1;
334
+ material.shininess = Math.max(2, (1 - roughness) * 128);
335
+ }
336
+ const specGloss = matDef.extensions;
337
+ const sg = specGloss?.["KHR_materials_pbrSpecularGlossiness"];
338
+ if (sg) {
339
+ if (sg.diffuseFactor && !material.texture) {
340
+ const [r, g, b, a] = sg.diffuseFactor;
341
+ material.albedoColor = [r, g, b, a];
342
+ material.diffuse = [r, g, b];
343
+ material.dissolve = a;
344
+ }
345
+ if (sg.diffuseTexture && !material.texture) {
346
+ const diffTex = sg.diffuseTexture;
347
+ const tex = await this.loadEmbeddedTexture(
348
+ gltf,
349
+ buffers,
350
+ diffTex.index
351
+ );
352
+ if (tex) material.texture = tex;
353
+ }
354
+ if (sg.glossinessFactor !== void 0) {
355
+ material.shininess = Math.max(2, sg.glossinessFactor * 128);
356
+ }
357
+ }
358
+ if (matDef.emissiveFactor) {
359
+ const [er, eg, eb] = matDef.emissiveFactor;
360
+ material.ambientColor = [
361
+ material.ambientColor[0] + er,
362
+ material.ambientColor[1] + eg,
363
+ material.ambientColor[2] + eb
364
+ ];
365
+ }
366
+ if (matDef.alphaMode === "BLEND") {
367
+ material.dissolve = material.albedoColor[3];
368
+ } else if (matDef.alphaMode === "MASK") {
369
+ material.dissolve = 1;
370
+ } else {
371
+ material.dissolve = 1;
372
+ }
373
+ if (matDef.doubleSided) {
374
+ material.doubleSided = true;
375
+ }
376
+ return material;
377
+ }
378
+ // ━━━ Texture loading ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
379
+ /**
380
+ * Decode a texture embedded in the GLB binary chunk and upload to GPU.
381
+ */
382
+ async loadEmbeddedTexture(gltf, buffers, textureIndex) {
383
+ const texDef = gltf.textures?.[textureIndex];
384
+ if (!texDef || texDef.source === void 0) return null;
385
+ const imageDef = gltf.images?.[texDef.source];
386
+ if (!imageDef) return null;
387
+ if (imageDef.bufferView !== void 0) {
388
+ const bv = gltf.bufferViews[imageDef.bufferView];
389
+ const buffer = buffers[bv.buffer];
390
+ const offset = bv.byteOffset ?? 0;
391
+ const data = new Uint8Array(buffer, offset, bv.byteLength);
392
+ const mimeType = imageDef.mimeType ?? "image/png";
393
+ const blob = new Blob([data], { type: mimeType });
394
+ const url = URL.createObjectURL(blob);
395
+ try {
396
+ return await this.createTextureFromUrl(url);
397
+ } finally {
398
+ URL.revokeObjectURL(url);
399
+ }
400
+ }
401
+ if (imageDef.uri) {
402
+ return this.createTextureFromUrl(imageDef.uri);
403
+ }
404
+ return null;
405
+ }
406
+ createTextureFromUrl(url) {
407
+ return new Promise((resolve) => {
408
+ const img = new Image();
409
+ img.crossOrigin = "anonymous";
410
+ img.onload = () => {
411
+ const gl = this.gl;
412
+ const tex = gl.createTexture();
413
+ if (!tex) {
414
+ resolve(null);
415
+ return;
416
+ }
417
+ gl.bindTexture(gl.TEXTURE_2D, tex);
418
+ gl.texImage2D(
419
+ gl.TEXTURE_2D,
420
+ 0,
421
+ gl.RGBA,
422
+ gl.RGBA,
423
+ gl.UNSIGNED_BYTE,
424
+ img
425
+ );
426
+ if (isPowerOfTwo(img.width) && isPowerOfTwo(img.height)) {
427
+ gl.generateMipmap(gl.TEXTURE_2D);
428
+ } else {
429
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
430
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
431
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
432
+ }
433
+ resolve(tex);
434
+ };
435
+ img.onerror = () => {
436
+ console.warn(`[GLBLoader] Failed to load texture from "${url}"`);
437
+ resolve(null);
438
+ };
439
+ img.src = url;
440
+ });
441
+ }
442
+ // ━━━ Skin / Skeleton parsing ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
443
+ parseSkin(gltf, buffers, skinIndex) {
444
+ const skinDef = gltf.skins[skinIndex];
445
+ const nodes = gltf.nodes;
446
+ const accessors = gltf.accessors;
447
+ const bufferViews = gltf.bufferViews;
448
+ let inverseBindMatrices = null;
449
+ if (skinDef.inverseBindMatrices !== void 0) {
450
+ inverseBindMatrices = this.getAccessorData(
451
+ accessors[skinDef.inverseBindMatrices],
452
+ bufferViews,
453
+ buffers
454
+ );
455
+ }
456
+ const nodeToJointIndex = /* @__PURE__ */ new Map();
457
+ for (let i = 0; i < skinDef.joints.length; i++) {
458
+ nodeToJointIndex.set(skinDef.joints[i], i);
459
+ }
460
+ const joints = [];
461
+ for (let ji = 0; ji < skinDef.joints.length; ji++) {
462
+ const nodeIdx = skinDef.joints[ji];
463
+ const node = nodes[nodeIdx];
464
+ let parentIndex = -1;
465
+ if (node) {
466
+ for (let pji = 0; pji < skinDef.joints.length; pji++) {
467
+ if (pji === ji) continue;
468
+ const pNode = nodes[skinDef.joints[pji]];
469
+ if (pNode.children?.includes(nodeIdx)) {
470
+ parentIndex = pji;
471
+ break;
472
+ }
473
+ }
474
+ }
475
+ const ibm = mat4.create();
476
+ if (inverseBindMatrices) {
477
+ for (let k = 0; k < 16; k++) {
478
+ ibm[k] = inverseBindMatrices[ji * 16 + k];
479
+ }
480
+ }
481
+ const t = node.translation ? vec3.fromValues(
482
+ node.translation[0],
483
+ node.translation[1],
484
+ node.translation[2]
485
+ ) : vec3.create();
486
+ const r = node.rotation ? quat.fromValues(
487
+ node.rotation[0],
488
+ node.rotation[1],
489
+ node.rotation[2],
490
+ node.rotation[3]
491
+ ) : quat.create();
492
+ const s = node.scale ? vec3.fromValues(node.scale[0], node.scale[1], node.scale[2]) : vec3.fromValues(1, 1, 1);
493
+ joints.push({
494
+ name: node.name ?? `joint_${ji}`,
495
+ nodeIndex: nodeIdx,
496
+ parentIndex,
497
+ localTranslation: t,
498
+ localRotation: r,
499
+ localScale: s,
500
+ inverseBindMatrix: ibm
501
+ });
502
+ }
503
+ const skinRootTransform = this.computeSkinRootTransform(
504
+ gltf,
505
+ skinDef.joints,
506
+ joints
507
+ );
508
+ return new Skeleton(joints, skinRootTransform);
509
+ }
510
+ /**
511
+ * Compute the accumulated global transform of all non-joint ancestor
512
+ * nodes above the skeleton's root joint(s).
513
+ */
514
+ computeSkinRootTransform(gltf, jointNodeIndices, joints) {
515
+ const nodes = gltf.nodes;
516
+ const nodeParentMap = /* @__PURE__ */ new Map();
517
+ for (let ni = 0; ni < nodes.length; ni++) {
518
+ const children = nodes[ni].children;
519
+ if (children) {
520
+ for (const ci of children) {
521
+ nodeParentMap.set(ci, ni);
522
+ }
523
+ }
524
+ }
525
+ const rootJoint = joints.find((j) => j.parentIndex === -1);
526
+ if (!rootJoint) return mat4.create();
527
+ const skinRoot = mat4.create();
528
+ let ancestor = nodeParentMap.get(rootJoint.nodeIndex);
529
+ while (ancestor !== void 0) {
530
+ const localMat = this.getNodeLocalMatrix(nodes[ancestor]);
531
+ mat4.multiply(skinRoot, localMat, skinRoot);
532
+ ancestor = nodeParentMap.get(ancestor);
533
+ }
534
+ return skinRoot;
535
+ }
536
+ // ━━━ Animation parsing ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
537
+ parseAnimation(gltf, buffers, animIndex, nodeToJoint) {
538
+ const animDef = gltf.animations[animIndex];
539
+ const accessors = gltf.accessors;
540
+ const bufferViews = gltf.bufferViews;
541
+ const channels = [];
542
+ let maxTime = 0;
543
+ for (const chDef of animDef.channels) {
544
+ const targetNode = chDef.target.node;
545
+ if (targetNode === void 0) continue;
546
+ const jointIndex = nodeToJoint.get(targetNode);
547
+ if (jointIndex === void 0) continue;
548
+ const path = chDef.target.path;
549
+ if (path === "weights") continue;
550
+ const samplerDef = animDef.samplers[chDef.sampler];
551
+ const times = this.getAccessorData(
552
+ accessors[samplerDef.input],
553
+ bufferViews,
554
+ buffers
555
+ );
556
+ const values = this.getAccessorData(
557
+ accessors[samplerDef.output],
558
+ bufferViews,
559
+ buffers
560
+ );
561
+ if (times.length > 0) {
562
+ maxTime = Math.max(maxTime, times[times.length - 1]);
563
+ }
564
+ const sampler = {
565
+ times,
566
+ values,
567
+ interpolation: samplerDef.interpolation ?? "LINEAR"
568
+ };
569
+ channels.push({
570
+ jointIndex,
571
+ path,
572
+ sampler
573
+ });
574
+ }
575
+ return new AnimationClip(
576
+ animDef.name ?? `animation_${animIndex}`,
577
+ channels,
578
+ maxTime
579
+ );
580
+ }
581
+ // ━━━ Accessor data extraction ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
582
+ /**
583
+ * Extract a typed array from a glTF accessor + bufferView.
584
+ * Handles byte-stride (interleaved buffers) and all glTF component types.
585
+ */
586
+ getAccessorData(accessor, bufferViews, buffers) {
587
+ if (accessor.bufferView === void 0) {
588
+ const count2 = accessor.count * COMPONENT_COUNT[accessor.type];
589
+ const Ctor2 = TYPED_ARRAY_CTOR[accessor.componentType] ?? Float32Array;
590
+ return new Ctor2(count2);
591
+ }
592
+ const bv = bufferViews[accessor.bufferView];
593
+ const buffer = buffers[bv.buffer];
594
+ const numComponents = COMPONENT_COUNT[accessor.type];
595
+ const componentSize = COMPONENT_SIZES[accessor.componentType] ?? 4;
596
+ const byteOffset = (bv.byteOffset ?? 0) + (accessor.byteOffset ?? 0);
597
+ const count = accessor.count;
598
+ const Ctor = TYPED_ARRAY_CTOR[accessor.componentType];
599
+ if (!Ctor) {
600
+ throw new Error(
601
+ `[GLBLoader] Unsupported componentType: ${accessor.componentType}`
602
+ );
603
+ }
604
+ const stride = bv.byteStride ?? 0;
605
+ const tightStride = numComponents * componentSize;
606
+ if (stride === 0 || stride === tightStride) {
607
+ return new Ctor(buffer, byteOffset, count * numComponents);
608
+ }
609
+ const out = new Ctor(count * numComponents);
610
+ const src = new DataView(buffer);
611
+ for (let i = 0; i < count; i++) {
612
+ const base = byteOffset + i * stride;
613
+ for (let c = 0; c < numComponents; c++) {
614
+ const off = base + c * componentSize;
615
+ out[i * numComponents + c] = readComponent(
616
+ src,
617
+ off,
618
+ accessor.componentType
619
+ );
620
+ }
621
+ }
622
+ return out;
623
+ }
624
+ // ━━━ Node-tree helpers ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
625
+ /** Compute the local transform matrix from a glTF node's TRS or matrix. */
626
+ getNodeLocalMatrix(node) {
627
+ const m = mat4.create();
628
+ if (node.matrix) {
629
+ for (let i = 0; i < 16; i++) {
630
+ m[i] = node.matrix[i];
631
+ }
632
+ return m;
633
+ }
634
+ const t = node.translation ?? [0, 0, 0];
635
+ const r = node.rotation ?? [0, 0, 0, 1];
636
+ const s = node.scale ?? [1, 1, 1];
637
+ mat4.fromRotationTranslationScale(
638
+ m,
639
+ quat.fromValues(r[0], r[1], r[2], r[3]),
640
+ vec3.fromValues(t[0], t[1], t[2]),
641
+ vec3.fromValues(s[0], s[1], s[2])
642
+ );
643
+ return m;
644
+ }
645
+ // ━━━ Flat-normal generation ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
646
+ /** Generate flat face-normals when the mesh doesn't include them. */
647
+ generateFlatNormals(vertices) {
648
+ const normals = new Float32Array(vertices.length);
649
+ for (let i = 0; i < vertices.length; i += 9) {
650
+ const ax = vertices[i], ay = vertices[i + 1], az = vertices[i + 2];
651
+ const bx = vertices[i + 3], by = vertices[i + 4], bz = vertices[i + 5];
652
+ const cx = vertices[i + 6], cy = vertices[i + 7], cz = vertices[i + 8];
653
+ const ux = bx - ax, uy = by - ay, uz = bz - az;
654
+ const vx = cx - ax, vy = cy - ay, vz = cz - az;
655
+ let nx = uy * vz - uz * vy;
656
+ let ny = uz * vx - ux * vz;
657
+ let nz = ux * vy - uy * vx;
658
+ const len = Math.sqrt(nx * nx + ny * ny + nz * nz);
659
+ if (len > 0) {
660
+ nx /= len;
661
+ ny /= len;
662
+ nz /= len;
663
+ }
664
+ for (let v = 0; v < 3; v++) {
665
+ normals[i + v * 3] = nx;
666
+ normals[i + v * 3 + 1] = ny;
667
+ normals[i + v * 3 + 2] = nz;
668
+ }
669
+ }
670
+ return normals;
671
+ }
672
+ };
673
+ function isPowerOfTwo(value) {
674
+ return (value & value - 1) === 0 && value > 0;
675
+ }
676
+ function readComponent(view, offset, componentType) {
677
+ switch (componentType) {
678
+ case 5120 /* BYTE */:
679
+ return view.getInt8(offset);
680
+ case 5121 /* UNSIGNED_BYTE */:
681
+ return view.getUint8(offset);
682
+ case 5122 /* SHORT */:
683
+ return view.getInt16(offset, true);
684
+ case 5123 /* UNSIGNED_SHORT */:
685
+ return view.getUint16(offset, true);
686
+ case 5125 /* UNSIGNED_INT */:
687
+ return view.getUint32(offset, true);
688
+ case 5126 /* FLOAT */:
689
+ return view.getFloat32(offset, true);
690
+ default:
691
+ return 0;
692
+ }
693
+ }
694
+ export {
695
+ GLBLoader
696
+ };
697
+ //# sourceMappingURL=load-glb.js.map