@threepipe/plugin-blend-importer 0.1.0 → 0.3.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 (47) hide show
  1. package/CHANGELOG.md +45 -3
  2. package/README.md +25 -1
  3. package/dist/BlendLoadPlugin.d.ts +63 -3
  4. package/dist/BlendLoadPlugin.d.ts.map +1 -1
  5. package/dist/decompress.d.ts +10 -0
  6. package/dist/decompress.d.ts.map +1 -0
  7. package/dist/dependencies.txt +31 -1
  8. package/dist/index.d.ts +2 -1
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +1351 -89
  11. package/dist/index.js.map +1 -1
  12. package/dist/index.mjs +1349 -87
  13. package/dist/index.mjs.map +1 -1
  14. package/dist/loader/camera.d.ts +1 -1
  15. package/dist/loader/ctx.d.ts +9 -1
  16. package/dist/loader/ctx.d.ts.map +1 -1
  17. package/dist/loader/geometry.d.ts +2 -2
  18. package/dist/loader/geometry.d.ts.map +1 -1
  19. package/dist/loader/index.d.ts +1 -1
  20. package/dist/loader/light.d.ts +1 -1
  21. package/dist/loader/material.d.ts +4 -1
  22. package/dist/loader/material.d.ts.map +1 -1
  23. package/dist/loader/mesh.d.ts +1 -1
  24. package/dist/loader/mesh.d.ts.map +1 -1
  25. package/docs/assets/highlight.css +49 -0
  26. package/docs/assets/icons.js +1 -1
  27. package/docs/assets/icons.svg +1 -1
  28. package/docs/assets/main.js +2 -2
  29. package/docs/assets/navigation.js +1 -1
  30. package/docs/assets/search.js +1 -1
  31. package/docs/assets/style.css +3 -3
  32. package/docs/classes/BlendLoadPlugin.html +6 -3
  33. package/docs/index.html +1 -1
  34. package/docs/interfaces/BlendFile.html +18 -0
  35. package/docs/interfaces/BlendLoadOptions.html +12 -0
  36. package/package.json +1 -1
  37. package/src/BlendLoadPlugin.ts +204 -13
  38. package/src/decompress.ts +59 -0
  39. package/src/index.ts +1 -0
  40. package/src/js-blend/main.js +4 -1
  41. package/src/js-blend/parser/parser.js +192 -52
  42. package/src/loader/ctx.ts +9 -0
  43. package/src/loader/geometry.ts +270 -13
  44. package/src/loader/material.ts +324 -8
  45. package/src/loader/mesh.ts +34 -8
  46. package/tsconfig.json +1 -0
  47. package/docs/.nojekyll +0 -1
package/dist/index.mjs CHANGED
@@ -1,11 +1,4 @@
1
- /**
2
- * @license
3
- * @threepipe/plugin-blend-importer v0.1.0
4
- * Copyright 2022-2025 repalash <palash@shaders.app>
5
- * Apache-2.0 License
6
- * See ./dependencies.txt for any bundled third-party dependencies and licenses.
7
- */
8
- import { Quaternion, Euler, Vector3, BaseImporterPlugin, Importer, FileLoader, Object3D, Object3D2, PerspectiveCamera0, OrthographicCamera0, PointLight2, PhysicalMaterial, Mesh2, BufferGeometry2, BufferAttribute } from "threepipe";
1
+ import { FrontSide, DoubleSide, Texture, SRGBColorSpace, ClampToEdgeWrapping, MirroredRepeatWrapping, RepeatWrapping, Quaternion, Euler, Vector3, gunzipSync, BaseImporterPlugin, Importer, FileLoader, Object3D, Object3D2, Source, BufferAttribute, BufferGeometry2, AmbientLight2, SpotLight2, DirectionalLight2, PointLight2, OrthographicCamera0, PerspectiveCamera0, UnlitMaterial, PhysicalMaterial, Mesh2, DataTexture } from "threepipe";
9
2
  let worker = null;
10
3
  const return_object = {
11
4
  loadBlendFromArrayBuffer: function(array_buffer) {
@@ -38,6 +31,7 @@ function worker_code() {
38
31
  struct_names = [];
39
32
  offset = 0;
40
33
  current_SDNA_template = null;
34
+ ERROR = null;
41
35
  _data = msg.data;
42
36
  AB = _data.slice();
43
37
  data = new DataView(_data);
@@ -72,13 +66,30 @@ function worker_code() {
72
66
  } else {
73
67
  return pointerLow;
74
68
  }
69
+ },
70
+ // Alignment-safe TypedArray read over the file buffer. Consumers (e.g. loader/geometry.ts)
71
+ // that reach into `__blender_file__.AB` at a datablock's `__data_address__` MUST use this
72
+ // instead of `new Ctor(AB, byteOffset, n)` — Blender packs blocks with no padding, so on
73
+ // v1 (17-byte header) and some v0 files the payload lands at an offset that isn't a multiple
74
+ // of the element size, where a direct view throws RangeError. All supported files are
75
+ // little-endian (big-endian is rejected at header parse), so a raw byte copy is correct.
76
+ readTypedArray: function(Ctor, byteOffset, length) {
77
+ return alignedTypedArray(Ctor, this.AB, byteOffset, length);
75
78
  }
76
79
  };
77
80
  self.onmessage = parseFile;
81
+ function alignedTypedArray(Ctor, buffer, byteOffset, length) {
82
+ if (byteOffset % Ctor.BYTES_PER_ELEMENT === 0) {
83
+ return new Ctor(buffer, byteOffset, length);
84
+ }
85
+ const out = new Ctor(length);
86
+ new Uint8Array(out.buffer).set(new Uint8Array(buffer, byteOffset, length * Ctor.BYTES_PER_ELEMENT));
87
+ return out;
88
+ }
78
89
  function float64Prop(offset2, Blender_Array_Length, length) {
79
90
  return {
80
91
  get: function() {
81
- return Blender_Array_Length > 1 ? new Float64Array(this.__blender_file__.AB, this.__data_address__ + offset2, length) : this.__blender_file__.dv.getFloat64(this.__data_address__ + offset2, this.__blender_file__.template.endianess);
92
+ return Blender_Array_Length > 1 ? alignedTypedArray(Float64Array, this.__blender_file__.AB, this.__data_address__ + offset2, length) : this.__blender_file__.dv.getFloat64(this.__data_address__ + offset2, this.__blender_file__.template.endianess);
82
93
  },
83
94
  set: function(float) {
84
95
  if (Blender_Array_Length > 1) ;
@@ -93,7 +104,7 @@ function worker_code() {
93
104
  function floatProp(offset2, Blender_Array_Length, length) {
94
105
  return {
95
106
  get: function() {
96
- return Blender_Array_Length > 1 ? new Float32Array(this.__blender_file__.AB, this.__data_address__ + offset2, length) : this.__blender_file__.dv.getFloat32(this.__data_address__ + offset2, this.__blender_file__.template.endianess);
107
+ return Blender_Array_Length > 1 ? alignedTypedArray(Float32Array, this.__blender_file__.AB, this.__data_address__ + offset2, length) : this.__blender_file__.dv.getFloat32(this.__data_address__ + offset2, this.__blender_file__.template.endianess);
97
108
  },
98
109
  set: function(float) {
99
110
  if (Blender_Array_Length > 1) ;
@@ -108,7 +119,7 @@ function worker_code() {
108
119
  function intProp(offset2, Blender_Array_Length, length) {
109
120
  return {
110
121
  get: function() {
111
- return Blender_Array_Length > 1 ? new Int32Array(this.__blender_file__.AB, this.__data_address__ + offset2, length) : this.__blender_file__.dv.getInt32(this.__data_address__ + offset2, this.__blender_file__.template.endianess);
122
+ return Blender_Array_Length > 1 ? alignedTypedArray(Int32Array, this.__blender_file__.AB, this.__data_address__ + offset2, length) : this.__blender_file__.dv.getInt32(this.__data_address__ + offset2, this.__blender_file__.template.endianess);
112
123
  },
113
124
  set: function(float) {
114
125
  if (Blender_Array_Length > 1) ;
@@ -120,10 +131,25 @@ function worker_code() {
120
131
  configurable: true
121
132
  };
122
133
  }
134
+ function uIntProp(offset2, Blender_Array_Length, length) {
135
+ return {
136
+ get: function() {
137
+ return Blender_Array_Length > 1 ? alignedTypedArray(Uint32Array, this.__blender_file__.AB, this.__data_address__ + offset2, length) : this.__blender_file__.dv.getUint32(this.__data_address__ + offset2, this.__blender_file__.template.endianess);
138
+ },
139
+ set: function(float) {
140
+ if (Blender_Array_Length > 1) ;
141
+ else {
142
+ this.__blender_file__.dv.setUint32(this.__data_address__ + offset2, float, this.__blender_file__.template.endianess);
143
+ }
144
+ },
145
+ enumerable: true,
146
+ configurable: true
147
+ };
148
+ }
123
149
  function shortProp(offset2, Blender_Array_Length, length) {
124
150
  return {
125
151
  get: function() {
126
- return Blender_Array_Length > 1 ? new Int16Array(this.__blender_file__.AB, this.__data_address__ + offset2, length) : this.__blender_file__.dv.getInt16(this.__data_address__ + offset2, this.__blender_file__.template.endianess);
152
+ return Blender_Array_Length > 1 ? alignedTypedArray(Int16Array, this.__blender_file__.AB, this.__data_address__ + offset2, length) : this.__blender_file__.dv.getInt16(this.__data_address__ + offset2, this.__blender_file__.template.endianess);
127
153
  },
128
154
  set: function(float) {
129
155
  if (Blender_Array_Length > 1) ;
@@ -138,7 +164,7 @@ function worker_code() {
138
164
  const uShortProp = (offset2, Blender_Array_Length, length) => {
139
165
  return {
140
166
  get: function() {
141
- return Blender_Array_Length > 1 ? new Uint16Array(this.__blender_file__.AB, this.__data_address__ + offset2, length) : this.__blender_file__.dv.getUint16(this.__data_address__ + offset2, this.__blender_file__.template.endianess);
167
+ return Blender_Array_Length > 1 ? alignedTypedArray(Uint16Array, this.__blender_file__.AB, this.__data_address__ + offset2, length) : this.__blender_file__.dv.getUint16(this.__data_address__ + offset2, this.__blender_file__.template.endianess);
142
168
  },
143
169
  set: function(float) {
144
170
  if (Blender_Array_Length > 1) ;
@@ -186,18 +212,28 @@ function worker_code() {
186
212
  function pointerProp2(offset2) {
187
213
  return {
188
214
  get: function() {
189
- let pointer = this.__blender_file__.getPointer(this.__data_address__ + offset2, this.__blender_file__);
190
- const link = this.__blender_file__.memory_lookup[pointer];
215
+ const bf = this.__blender_file__;
216
+ let pointer = bf.getPointer(this.__data_address__ + offset2, bf);
217
+ const link = bf.memory_lookup[pointer];
191
218
  const results = [];
192
219
  if (link) {
193
220
  const address = link.__data_address__;
194
- let j = 0;
195
- while (true) {
196
- pointer = this.__blender_file__.getPointer(address + j * 8, this.__blender_file__);
197
- let obj = this.__blender_file__.memory_lookup[pointer];
198
- if (!obj) break;
199
- results.push(obj);
200
- j++;
221
+ const ps = bf.template && bf.template.pointer_size || 8;
222
+ const count = typeof link.__byte_length__ === "number" && link.__byte_length__ > 0 ? Math.floor(link.__byte_length__ / ps) : 0;
223
+ if (count > 0) {
224
+ for (let j = 0; j < count; j++) {
225
+ pointer = bf.getPointer(address + j * ps, bf);
226
+ results.push(bf.memory_lookup[pointer] || null);
227
+ }
228
+ } else {
229
+ let j = 0;
230
+ while (true) {
231
+ pointer = bf.getPointer(address + j * ps, bf);
232
+ const obj = bf.memory_lookup[pointer];
233
+ if (!obj) break;
234
+ results.push(obj);
235
+ j++;
236
+ }
201
237
  }
202
238
  }
203
239
  return results;
@@ -233,6 +269,33 @@ function worker_code() {
233
269
  configurable: true
234
270
  };
235
271
  }
272
+ function int8Prop(offset2, Blender_Array_Length, length) {
273
+ return {
274
+ get: function() {
275
+ return Blender_Array_Length > 1 ? alignedTypedArray(Int8Array, this.__blender_file__.AB, this.__data_address__ + offset2, length) : this.__blender_file__.dv.getInt8(this.__data_address__ + offset2);
276
+ },
277
+ set: function(v) {
278
+ if (Blender_Array_Length <= 1) this.__blender_file__.dv.setInt8(this.__data_address__ + offset2, v);
279
+ },
280
+ enumerable: true,
281
+ configurable: true
282
+ };
283
+ }
284
+ function int64Prop(offset2, Blender_Array_Length, length, unsigned) {
285
+ return {
286
+ get: function() {
287
+ if (Blender_Array_Length > 1) {
288
+ return alignedTypedArray(unsigned ? BigUint64Array : BigInt64Array, this.__blender_file__.AB, this.__data_address__ + offset2, length);
289
+ }
290
+ const dv = this.__blender_file__.dv, le = this.__blender_file__.template.endianess;
291
+ return Number(unsigned ? dv.getBigUint64(this.__data_address__ + offset2, le) : dv.getBigInt64(this.__data_address__ + offset2, le));
292
+ },
293
+ set: function() {
294
+ },
295
+ enumerable: true,
296
+ configurable: true
297
+ };
298
+ }
236
299
  function compileProp(obj, name, type, offset2, array_size, IS_POINTER, pointer_size2, length) {
237
300
  if (!IS_POINTER) {
238
301
  switch (type) {
@@ -243,18 +306,34 @@ function worker_code() {
243
306
  Object.defineProperty(obj, name, floatProp(offset2, array_size, length >> 2));
244
307
  break;
245
308
  case "int":
309
+ case "int32_t":
246
310
  Object.defineProperty(obj, name, intProp(offset2, array_size, length >> 2));
247
311
  break;
312
+ case "uint32_t":
313
+ Object.defineProperty(obj, name, uIntProp(offset2, array_size, length >> 2));
314
+ break;
248
315
  case "short":
316
+ case "int16_t":
249
317
  Object.defineProperty(obj, name, shortProp(offset2, array_size, length >> 1));
250
318
  break;
251
319
  case "ushort":
320
+ case "uint16_t":
252
321
  Object.defineProperty(obj, name, uShortProp(offset2, array_size, length >> 1));
253
322
  break;
323
+ case "int8_t":
324
+ Object.defineProperty(obj, name, int8Prop(offset2, array_size, length));
325
+ break;
254
326
  case "char":
255
327
  case "uchar":
328
+ case "uint8_t":
256
329
  Object.defineProperty(obj, name, charProp(offset2, array_size, length));
257
330
  break;
331
+ case "int64_t":
332
+ Object.defineProperty(obj, name, int64Prop(offset2, array_size, length >> 3, false));
333
+ break;
334
+ case "uint64_t":
335
+ Object.defineProperty(obj, name, int64Prop(offset2, array_size, length >> 3, true));
336
+ break;
258
337
  default:
259
338
  obj[name] = {};
260
339
  obj.__list__.push(name, type, length, offset2, array_size, IS_POINTER);
@@ -330,7 +409,7 @@ function worker_code() {
330
409
  offset2 = compileProp(blen_struct.prototype, array_name_, type, offset2, Blender_Array_Length, Pointer_Match, pointer_size, array_length);
331
410
  }
332
411
  Object.defineProperty(blen_struct.prototype, _name, {
333
- get: /* @__PURE__ */ function(array_names2) {
412
+ get: /* @__PURE__ */ (function(array_names2) {
334
413
  return function() {
335
414
  const array = [];
336
415
  for (let i2 = 0; i2 < array_names2.length; i2++) {
@@ -338,7 +417,7 @@ function worker_code() {
338
417
  }
339
418
  return array;
340
419
  };
341
- }(array_names),
420
+ })(array_names),
342
421
  enumerable: true,
343
422
  configurable: true
344
423
  });
@@ -372,6 +451,7 @@ function worker_code() {
372
451
  if (this.__list__ === null) return this;
373
452
  BLENDER_FILE2.addObject(this);
374
453
  this.__blender_file__ = BLENDER_FILE2;
454
+ this.__byte_length__ = data_block_length - _data_offset;
375
455
  const struct = this.__list__;
376
456
  let j = 0, i = 0, name = "", type, length, Blender_Array_Length, offset2, constructor;
377
457
  this.__data_address__ = _data_offset;
@@ -434,25 +514,53 @@ function worker_code() {
434
514
  offset = 0;
435
515
  const magic = toString(_data, offset, 7);
436
516
  if (magic !== "BLENDER") return ERROR = "File supplied is not a .blend compatible Blender file.";
437
- offset += 7;
438
- pointer_size = toString(_data, offset++, offset) == "_" ? 4 : 8;
439
- BIG_ENDIAN = toString(_data, offset++, offset) !== "V";
440
- const version = toString(_data, offset, offset + 3);
441
- if (!templates[version]) {
442
- templates[version] = new MASTER_SDNA_SCHEMA(version);
517
+ let version, large_bhead = false;
518
+ const fmtByte = toString(_data, 7, 8);
519
+ if (fmtByte === "_" || fmtByte === "-") {
520
+ pointer_size = fmtByte === "_" ? 4 : 8;
521
+ BIG_ENDIAN = toString(_data, 8, 9) !== "V";
522
+ version = toString(_data, 9, 12);
523
+ offset = 12;
524
+ } else {
525
+ const headerSize = parseInt(toString(_data, 7, 9), 10);
526
+ if (headerSize !== 17) return ERROR = "Unsupported new-style blend header size: " + headerSize;
527
+ pointer_size = 8;
528
+ BIG_ENDIAN = true;
529
+ large_bhead = true;
530
+ version = toString(_data, 13, 17);
531
+ offset = headerSize;
532
+ }
533
+ if (!BIG_ENDIAN) {
534
+ return ERROR = "Big-endian .blend files are not supported";
443
535
  }
444
- current_SDNA_template = templates[version];
536
+ const BLOCK_HDR = large_bhead ? 32 : 16 + pointer_size;
537
+ const SDNA_OFF = large_bhead ? 4 : 8 + pointer_size;
538
+ const LEN_OFF = large_bhead ? 16 : 4;
539
+ const NR_OFF = large_bhead ? 24 : 12 + pointer_size;
540
+ const LEN_I64 = large_bhead;
541
+ const NR_I64 = large_bhead;
542
+ const readLen = (o) => LEN_I64 ? Number(data.getBigInt64(o + LEN_OFF, true)) : data.getInt32(o + LEN_OFF, true);
543
+ const readSdna = (o) => data.getInt32(o + SDNA_OFF, BIG_ENDIAN);
544
+ const readNr = (o) => NR_I64 ? Number(data.getBigInt64(o + NR_OFF, true)) : data.getInt32(o + NR_OFF, BIG_ENDIAN);
545
+ const templateKey = (large_bhead ? "v1:" : "v0:") + version;
546
+ if (!templates[templateKey]) {
547
+ templates[templateKey] = new MASTER_SDNA_SCHEMA(version);
548
+ }
549
+ current_SDNA_template = templates[templateKey];
445
550
  FILE.template = current_SDNA_template;
446
- offset += 3;
447
551
  if (!current_SDNA_template.SDNA_SET) {
448
552
  current_SDNA_template.endianess = BIG_ENDIAN;
449
553
  current_SDNA_template.pointer_size = pointer_size;
450
554
  offset2 = offset;
451
555
  while (true) {
452
- sdna_index = data.getInt32(offset2 + pointer_size + 8, BIG_ENDIAN);
556
+ if (offset2 + BLOCK_HDR > data.byteLength) {
557
+ ERROR = "Unexpected end of file while searching for DNA1";
558
+ break;
559
+ }
560
+ sdna_index = readSdna(offset2);
453
561
  code = toString(_data, offset2, offset2 + 4).replace(/\u0000/g, "");
454
- block_length = data.getInt32(offset2 + 4, true);
455
- offset2 += 16 + pointer_size;
562
+ block_length = readLen(offset2);
563
+ offset2 += BLOCK_HDR;
456
564
  if (code === "DNA1") {
457
565
  count = 0;
458
566
  let types = [], names = [], lengths = [], curr_name = "";
@@ -550,27 +658,24 @@ function worker_code() {
550
658
  }
551
659
  break;
552
660
  }
553
- if (data.getInt8(offset) === 0) {
554
- debugger;
555
- }
556
661
  data_offset = offset;
557
- if (offset + pointer_size + 12 >= data.byteLength) {
662
+ if (offset + BLOCK_HDR > data.byteLength) {
558
663
  ERROR = "Unexpected end of file while parsing";
559
664
  break;
560
665
  }
561
- sdna_index = data.getInt32(offset + pointer_size + 8, BIG_ENDIAN);
666
+ sdna_index = readSdna(offset);
562
667
  const code_str = toString(_data, offset, offset + 4);
563
- offset2 = offset + 16 + pointer_size;
564
- const blockLength = data.getInt32(offset + 4, true);
565
- if (blockLength < 0 || offset + blockLength + 16 + pointer_size > data.byteLength) {
668
+ offset2 = offset + BLOCK_HDR;
669
+ const blockLength = readLen(offset);
670
+ if (blockLength < 0 || offset + blockLength + BLOCK_HDR > data.byteLength) {
566
671
  ERROR = "Invalid block length detected";
567
672
  break;
568
673
  }
569
- offset += blockLength + 16 + pointer_size;
674
+ offset += blockLength + BLOCK_HDR;
570
675
  if (code_str === "DNA1") ;
571
676
  else if (code_str === "ENDB") break;
572
677
  else if (code_str === "TEST") {
573
- const data_start = data_offset + pointer_size + 16;
678
+ const data_start = data_offset + BLOCK_HDR;
574
679
  const width = data.getInt32(data_start, BIG_ENDIAN);
575
680
  const height = data.getInt32(data_start + 4, BIG_ENDIAN);
576
681
  if (width * height > 0) {
@@ -579,7 +684,7 @@ function worker_code() {
579
684
  ERROR = "Invalid TEST block length detected";
580
685
  break;
581
686
  }
582
- const image_data = new Uint32Array(_data, data_start + 8, data_len >> 2);
687
+ const image_data = alignedTypedArray(Uint32Array, _data, data_start + 8, data_len >> 2);
583
688
  const image = {
584
689
  width,
585
690
  height,
@@ -588,10 +693,10 @@ function worker_code() {
588
693
  FILE.thumbnail = image;
589
694
  }
590
695
  } else {
591
- const data_start = data_offset + pointer_size + 16;
696
+ const data_start = data_offset + BLOCK_HDR;
592
697
  const constructor = current_SDNA_template.getSDNAStructureConstructor(current_SDNA_template.SDNA_NAMES[sdna_index]);
593
- const size = data.getInt32(data_offset + 4, BIG_ENDIAN);
594
- count = data.getInt32(data_offset + 12 + pointer_size, BIG_ENDIAN);
698
+ const size = readLen(data_offset);
699
+ count = readNr(data_offset);
595
700
  if (count > 0 && constructor) {
596
701
  let obj = new constructor();
597
702
  const length = constructor.prototype._length;
@@ -616,18 +721,9 @@ function worker_code() {
616
721
  }
617
722
  }
618
723
  worker = new worker_code();
619
- worker.postMessage = function(message, err) {
620
- return_object.onParseReady(message, err);
724
+ worker.postMessage = function(message, err2) {
725
+ return_object.onParseReady(message, err2);
621
726
  };
622
- /**
623
- * JS.Blend
624
- * Original Repo: https://github.com/acweathersby/js.blend
625
- * Slightly modified for three.js and js updates, minor refactor.
626
- * Object-creation part re-written for latest three.js and typescript
627
- * MIT License
628
- * Copyright (c) 2020 Anthony C, Weathersby
629
- * @license
630
- */
631
727
  async function parseBlend(buffer, name = "") {
632
728
  return new Promise((res, rej) => {
633
729
  return_object.onParseReady = (file, error) => {
@@ -716,14 +812,147 @@ function getLayer(layers, i) {
716
812
  if (!Array.isArray(layers)) return layers;
717
813
  return layers[i];
718
814
  }
815
+ function readCString(block) {
816
+ if (!block || block.__data_address__ === void 0 || !block.__blender_file__) return null;
817
+ const u82 = block.__blender_file__.byte;
818
+ let s = "", o = block.__data_address__;
819
+ while (o < u82.length && u82[o] !== 0 && s.length < 256) s += String.fromCharCode(u82[o++]);
820
+ return s;
821
+ }
822
+ const ATTR_TYPE_INT32 = 3, ATTR_TYPE_FLOAT2 = 6, ATTR_TYPE_FLOAT3 = 7;
823
+ const ATTR_DOMAIN_CORNER = 3;
824
+ function getAttributes(meshData) {
825
+ const as = meshData.attribute_storage;
826
+ if (!as) return [];
827
+ let attrs = as.dna_attributes;
828
+ if (!attrs) return [];
829
+ attrs = Array.isArray(attrs) || attrs.length !== void 0 ? Array.from(attrs) : [attrs];
830
+ return attrs;
831
+ }
832
+ function findAttribute(attrs, name, type) {
833
+ for (const a of attrs) {
834
+ if (readCString(a.name) === name && (type === void 0 || a.data_type === type)) return a;
835
+ }
836
+ return null;
837
+ }
838
+ function attrSize(attr) {
839
+ const arr = attr && attr.data;
840
+ const n = arr && arr.size !== void 0 ? Number(arr.size) : 0;
841
+ return Number.isFinite(n) && n >= 0 ? n : 0;
842
+ }
843
+ function readAttrArray(attr, Ctor, count) {
844
+ const arr = attr && attr.data;
845
+ const block = arr && arr.data;
846
+ if (!block || block.__data_address__ === void 0 || !block.__blender_file__) return null;
847
+ return block.__blender_file__.readTypedArray(Ctor, block.__data_address__, count);
848
+ }
849
+ function mergeGroupRun(runs, start, count, mat) {
850
+ if (count <= 0) return;
851
+ const last = runs[runs.length - 1];
852
+ if (last && last.mat === mat && last.start + last.count === start) last.count += count;
853
+ else runs.push({ start, count, mat });
854
+ }
855
+ function readFaceMaterialIndex(meshData, faceCount) {
856
+ const pd = meshData.pdata;
857
+ if (!pd || !pd.layers || !pd.totlayer) return null;
858
+ for (let i = 0; i < pd.totlayer; i++) {
859
+ const l = getLayer(pd.layers, i);
860
+ if (l && l.name === "material_index" && l.data) {
861
+ const data = Array.isArray(l.data) ? l.data : l.data.length !== void 0 ? Array.from(l.data) : null;
862
+ if (!data) return null;
863
+ const out = new Array(faceCount);
864
+ for (let f = 0; f < faceCount; f++) {
865
+ const e = data[f];
866
+ out[f] = e && typeof e === "object" ? e.i ?? e.value ?? 0 : e || 0;
867
+ }
868
+ return out;
869
+ }
870
+ }
871
+ return null;
872
+ }
873
+ function createBufferGeometryFromAttributes(meshData, ctx) {
874
+ const attrs = getAttributes(meshData);
875
+ if (!attrs.length) return null;
876
+ const posAttr = findAttribute(attrs, "position", ATTR_TYPE_FLOAT3);
877
+ const vertCount = attrSize(posAttr);
878
+ const posRaw = vertCount > 0 && readAttrArray(posAttr, Float32Array, vertCount * 3);
879
+ if (!posRaw) return null;
880
+ const geometry = new ctx.BufferGeometry();
881
+ geometry.name = meshData.aname || "";
882
+ const positions = new Float32Array(vertCount * 3);
883
+ for (let j = 0; j < vertCount; j++) {
884
+ const x = posRaw[j * 3], y = posRaw[j * 3 + 1], z = posRaw[j * 3 + 2];
885
+ positions[j * 3] = Number.isFinite(x) ? x : 0;
886
+ positions[j * 3 + 1] = Number.isFinite(z) ? z : 0;
887
+ positions[j * 3 + 2] = Number.isFinite(y) ? -y : 0;
888
+ }
889
+ geometry.setAttribute("position", new ctx.BufferAttribute(positions, 3));
890
+ const cvAttr = findAttribute(attrs, ".corner_vert", ATTR_TYPE_INT32);
891
+ const loopCount = attrSize(cvAttr);
892
+ const cornerVerts = loopCount > 0 && readAttrArray(cvAttr, Int32Array, loopCount);
893
+ const poi = meshData.poly_offset_indices;
894
+ const faceOffsetCount = poi && poi.__byte_length__ ? poi.__byte_length__ >> 2 : 0;
895
+ if (cornerVerts && faceOffsetCount > 1 && poi) {
896
+ const faceIndices = poi.__blender_file__.readTypedArray(Int32Array, poi.__data_address__, faceOffsetCount);
897
+ const faceCount = faceIndices.length - 1;
898
+ let totalTriangles = 0;
899
+ for (let i = 0; i < faceCount; i++) totalTriangles += Math.max(0, faceIndices[i + 1] - faceIndices[i] - 2);
900
+ const indexes = new Uint32Array(totalTriangles * 3);
901
+ const inRange = (v) => v >= 0 && v < vertCount;
902
+ const matAttr = (meshData.totcol || 0) > 1 ? findAttribute(attrs, "material_index", ATTR_TYPE_INT32) : null;
903
+ const faceMat = matAttr && attrSize(matAttr) >= faceCount ? readAttrArray(matAttr, Int32Array, faceCount) : null;
904
+ const groupRuns = [];
905
+ let t = 0;
906
+ for (let i = 0; i < faceCount; i++) {
907
+ const faceStart = faceIndices[i], faceVertCount = faceIndices[i + 1] - faceStart;
908
+ const triStart = t;
909
+ if (faceVertCount >= 3 && faceStart >= 0 && faceStart + faceVertCount <= loopCount) {
910
+ const firstVert = cornerVerts[faceStart];
911
+ for (let k = 1; k < faceVertCount - 1; k++) {
912
+ const b = cornerVerts[faceStart + k], c = cornerVerts[faceStart + k + 1];
913
+ if (inRange(firstVert) && inRange(b) && inRange(c)) {
914
+ indexes[t++] = firstVert;
915
+ indexes[t++] = b;
916
+ indexes[t++] = c;
917
+ }
918
+ }
919
+ }
920
+ if (faceMat) mergeGroupRun(groupRuns, triStart, t - triStart, faceMat[i] || 0);
921
+ }
922
+ geometry.setIndex(new ctx.BufferAttribute(t === indexes.length ? indexes : indexes.slice(0, t), 1));
923
+ if (faceMat) for (const g of groupRuns) geometry.addGroup(g.start, g.count, g.mat);
924
+ const uvAttr = attrs.find((a) => a.data_type === ATTR_TYPE_FLOAT2 && a.domain === ATTR_DOMAIN_CORNER && !(readCString(a.name) || ".").startsWith("."));
925
+ const uvRaw = uvAttr && attrSize(uvAttr) >= loopCount && readAttrArray(uvAttr, Float32Array, loopCount * 2);
926
+ if (uvRaw) {
927
+ const uvs = new Float32Array(vertCount * 2);
928
+ for (let i = 0; i < loopCount; i++) {
929
+ const v = cornerVerts[i];
930
+ if (v >= 0 && v < vertCount) {
931
+ const u = uvRaw[i * 2], vv = uvRaw[i * 2 + 1];
932
+ uvs[v * 2] = Number.isFinite(u) ? u : 0;
933
+ uvs[v * 2 + 1] = Number.isFinite(vv) ? vv : 0;
934
+ }
935
+ }
936
+ geometry.setAttribute("uv", new ctx.BufferAttribute(uvs, 2));
937
+ }
938
+ }
939
+ if (geometry.attributes.position && !geometry.attributes.normal)
940
+ geometry.computeVertexNormals();
941
+ return geometry;
942
+ }
719
943
  function createBufferGeometry(meshData, ctx) {
720
944
  if (meshData.mpoly) return createBufferGeometryOld(meshData, ctx);
945
+ if (meshData.attribute_storage) {
946
+ const g = createBufferGeometryFromAttributes(meshData, ctx);
947
+ if (g) return g;
948
+ }
721
949
  const geometry = new ctx.BufferGeometry();
722
950
  geometry.name = meshData.aname || "";
723
951
  let vertices;
724
952
  let verticesData;
725
953
  let indices;
726
954
  let indicesData;
955
+ let uvLayerData = null;
727
956
  if (meshData.vdata && meshData.vdata.layers && meshData.vdata.totlayer > 0) {
728
957
  for (let i = 0; i < meshData.vdata.totlayer; i++) {
729
958
  const layer = getLayer(meshData.vdata.layers, i);
@@ -742,7 +971,12 @@ function createBufferGeometry(meshData, ctx) {
742
971
  const layer = getLayer(meshData.ldata.layers, i);
743
972
  const data = layer.data || [];
744
973
  if (data.length === meshData.totloop) {
745
- if (indices && (layer.name !== ".corner_vert" || indices.name === ".corner_vert")) {
974
+ const lname = layer.name || "";
975
+ if (!uvLayerData && (layer.type === 49 || lname[0] !== "." && /uv/i.test(lname))) {
976
+ uvLayerData = data;
977
+ continue;
978
+ }
979
+ if (indices && (lname !== ".corner_vert" || indices.name === ".corner_vert")) {
746
980
  continue;
747
981
  }
748
982
  indices = layer;
@@ -752,9 +986,44 @@ function createBufferGeometry(meshData, ctx) {
752
986
  }
753
987
  let faceIndices = [];
754
988
  if (meshData.poly_offset_indices && meshData.totpoly > 0) {
755
- faceIndices = [...new Int32Array(meshData.poly_offset_indices.__blender_file__.AB, meshData.poly_offset_indices.__data_address__, meshData.totpoly + 1)];
989
+ faceIndices = [...meshData.poly_offset_indices.__blender_file__.readTypedArray(Int32Array, meshData.poly_offset_indices.__data_address__, meshData.totpoly + 1)];
990
+ }
991
+ const vco = (vd) => vd?.x !== void 0 ? [vd.x, vd.y, vd.z] : vd?.co ? [vd.co[0], vd.co[1], vd.co[2]] : [0, 0, 0];
992
+ let cornerToVert = null;
993
+ let expandedPos = null;
994
+ let expandedUv = null;
995
+ if (uvLayerData && indicesData && verticesData && verticesData.length) {
996
+ cornerToVert = new Int32Array(indicesData.length);
997
+ const seen = /* @__PURE__ */ new Map();
998
+ const ps = [], us = [];
999
+ const uvN = uvLayerData.length;
1000
+ for (let i = 0; i < indicesData.length; i++) {
1001
+ const bv = indicesData[i]?.i;
1002
+ if (!(bv >= 0 && bv < verticesData.length)) {
1003
+ cornerToVert[i] = 0;
1004
+ continue;
1005
+ }
1006
+ const e = i < uvN ? uvLayerData[i] : null;
1007
+ const u = e && Number.isFinite(e.x) ? e.x : 0;
1008
+ const w = e && Number.isFinite(e.y) ? e.y : 0;
1009
+ const key = bv + "|" + Math.round(u * 4096) + "|" + Math.round(w * 4096);
1010
+ let ev = seen.get(key);
1011
+ if (ev === void 0) {
1012
+ ev = us.length / 2;
1013
+ seen.set(key, ev);
1014
+ const [x, y, z] = vco(verticesData[bv]);
1015
+ ps.push(x, z, -y);
1016
+ us.push(u, w);
1017
+ }
1018
+ cornerToVert[i] = ev;
1019
+ }
1020
+ expandedPos = new Float32Array(ps);
1021
+ expandedUv = new Float32Array(us);
756
1022
  }
757
- if (verticesData && verticesData.length > 0) {
1023
+ const vAt = (corner) => cornerToVert ? cornerToVert[corner] : indicesData[corner].i;
1024
+ if (expandedPos) {
1025
+ geometry.setAttribute("position", new ctx.BufferAttribute(expandedPos, 3));
1026
+ } else if (verticesData && verticesData.length > 0) {
758
1027
  const positions = new Float32Array(verticesData.length * 3);
759
1028
  for (let j = 0; j < verticesData.length; j++) {
760
1029
  const { x, y, z, co } = verticesData[j] || {};
@@ -773,44 +1042,50 @@ function createBufferGeometry(meshData, ctx) {
773
1042
  }
774
1043
  geometry.setAttribute("position", new ctx.BufferAttribute(positions, 3));
775
1044
  }
776
- if (indicesData && indicesData.length > 0 && (verticesData == null ? void 0 : verticesData.length)) {
1045
+ if (indicesData && indicesData.length > 0 && verticesData?.length) {
777
1046
  const faceSize = meshData.totloop / meshData.totpoly;
778
1047
  if (faceIndices.length > 0) {
1048
+ const faceCount = faceIndices.length - 1;
779
1049
  let totalTriangles = 0;
780
- for (let i = 0; i < faceIndices.length - 1; i++) {
1050
+ for (let i = 0; i < faceCount; i++) {
781
1051
  const faceVertCount = faceIndices[i + 1] - faceIndices[i];
782
1052
  totalTriangles += Math.max(0, faceVertCount - 2);
783
1053
  }
784
1054
  const indexes = new Uint32Array(totalTriangles * 3);
785
1055
  let t = 0;
786
- for (let i = 0; i < faceIndices.length - 1; i++) {
1056
+ const faceMat = (meshData.totcol || 0) > 1 ? readFaceMaterialIndex(meshData, faceCount) : null;
1057
+ const groupRuns = [];
1058
+ for (let i = 0; i < faceCount; i++) {
787
1059
  const faceStart = faceIndices[i];
788
1060
  const faceEnd = faceIndices[i + 1];
789
1061
  const faceVertCount = faceEnd - faceStart;
1062
+ const triStart = t;
790
1063
  if (faceVertCount >= 3) {
791
- const firstVert = indicesData[faceStart].i;
1064
+ const firstVert = vAt(faceStart);
792
1065
  for (let k = 1; k < faceVertCount - 1; k++) {
793
1066
  indexes[t++] = firstVert;
794
- indexes[t++] = indicesData[faceStart + k].i;
795
- indexes[t++] = indicesData[faceStart + k + 1].i;
1067
+ indexes[t++] = vAt(faceStart + k);
1068
+ indexes[t++] = vAt(faceStart + k + 1);
796
1069
  }
797
1070
  }
1071
+ if (faceMat) mergeGroupRun(groupRuns, triStart, t - triStart, faceMat[i] || 0);
798
1072
  }
799
1073
  geometry.setIndex(new ctx.BufferAttribute(indexes, 1));
1074
+ if (faceMat) for (const g of groupRuns) geometry.addGroup(g.start, g.count, g.mat);
800
1075
  } else if (faceSize === 3 || faceSize === 4) {
801
1076
  const isQuad = faceSize === 4;
802
1077
  const faceCount = indices.length / faceSize;
803
1078
  const indexes = new Uint32Array(faceCount * 3 * (isQuad ? 2 : 1));
804
1079
  if (faceSize !== 3 && faceSize !== 4) return geometry;
805
1080
  for (let j = 0, t = 0; j < indices.length; j += faceSize) {
806
- const a = indices[j].i;
807
- const b = indices[j + 1].i;
808
- const c = indices[j + 2].i;
1081
+ const a = vAt(j);
1082
+ const b = vAt(j + 1);
1083
+ const c = vAt(j + 2);
809
1084
  indexes[t++] = a;
810
1085
  indexes[t++] = b;
811
1086
  indexes[t++] = c;
812
1087
  if (isQuad) {
813
- const d = indices[j + 3].i;
1088
+ const d = vAt(j + 3);
814
1089
  indexes[t++] = a;
815
1090
  indexes[t++] = c;
816
1091
  indexes[t++] = d;
@@ -821,6 +1096,9 @@ function createBufferGeometry(meshData, ctx) {
821
1096
  } else if (faceIndices) {
822
1097
  console.error("BlendLoader - no indices data found, but face indices are present", faceIndices);
823
1098
  }
1099
+ if (expandedUv && !geometry.attributes.uv) {
1100
+ geometry.setAttribute("uv", new ctx.BufferAttribute(expandedUv, 2));
1101
+ }
824
1102
  if (geometry.attributes.position && !geometry.attributes.normal)
825
1103
  geometry.computeVertexNormals();
826
1104
  return geometry;
@@ -836,9 +1114,12 @@ function createBufferGeometryOld(mesh, ctx) {
836
1114
  const positions = new Float32Array(size * 3);
837
1115
  let currentIndex = 0;
838
1116
  let computeNormals = false;
1117
+ const useGroups = (mesh.totcol || 0) > 1;
1118
+ const groupRuns = [];
839
1119
  for (const face of faces) {
840
1120
  const len = face.totloop;
841
1121
  const start = face.loopstart;
1122
+ const faceStartIndex = currentIndex;
842
1123
  let indexi = 1;
843
1124
  while (indexi < len) {
844
1125
  let index = 0;
@@ -870,34 +1151,294 @@ function createBufferGeometryOld(mesh, ctx) {
870
1151
  }
871
1152
  indexi += 2;
872
1153
  }
1154
+ if (useGroups) mergeGroupRun(groupRuns, faceStartIndex, currentIndex - faceStartIndex, face.mat_nr || 0);
873
1155
  }
874
1156
  geometry.setAttribute("position", new ctx.BufferAttribute(positions, 3));
875
1157
  geometry.setIndex(new ctx.BufferAttribute(indices, 1));
876
1158
  geometry.setAttribute("normal", new ctx.BufferAttribute(normals, 3));
877
1159
  geometry.setAttribute("uv", new ctx.BufferAttribute(uvs, 2));
1160
+ if (useGroups && groupRuns.length)
1161
+ for (const g of groupRuns) geometry.addGroup(g.start, g.count, g.mat);
878
1162
  if (computeNormals) {
879
1163
  geometry.computeVertexNormals();
880
1164
  }
881
1165
  return geometry;
882
1166
  }
1167
+ const MA_BL_CULL_BACKFACE = 1 << 2;
1168
+ const MA_BM_CLIP = 3;
1169
+ function listBaseToArray(lb) {
1170
+ const out = [];
1171
+ if (!lb) return out;
1172
+ let n = lb.first, guard = 0;
1173
+ while (n && guard++ < 8192) {
1174
+ out.push(n);
1175
+ n = n.next;
1176
+ }
1177
+ return out;
1178
+ }
1179
+ const idnameOf = (n) => n && typeof n.idname === "string" ? n.idname : "";
1180
+ const nameOf = (s) => s && typeof s.name === "string" ? s.name : "";
1181
+ function socketByName(sockets, ...names) {
1182
+ for (const name of names) {
1183
+ const s = sockets.find((x) => nameOf(x) === name);
1184
+ if (s) return s;
1185
+ }
1186
+ return null;
1187
+ }
1188
+ function inputsOf(node) {
1189
+ return node ? listBaseToArray(node.inputs) : [];
1190
+ }
1191
+ function socketConst(sock) {
1192
+ const dv = sock && sock.default_value;
1193
+ return dv ? dv.value : void 0;
1194
+ }
1195
+ const num = (v) => typeof v === "number" ? v : void 0;
1196
+ const clamp01 = (c) => Math.max(0, Math.min(1, c));
1197
+ function nodeFeeding(links, socket) {
1198
+ if (!socket) return null;
1199
+ let link = links.find((l) => l.tosock === socket);
1200
+ let guard = 0;
1201
+ while (link && idnameOf(link.fromnode).includes("Reroute") && guard++ < 32) {
1202
+ const re = inputsOf(link.fromnode)[0];
1203
+ link = links.find((l) => l.tosock === re);
1204
+ }
1205
+ return link ? link.fromnode : null;
1206
+ }
1207
+ function imageMime(b) {
1208
+ if (b[0] === 137 && b[1] === 80 && b[2] === 78 && b[3] === 71) return "image/png";
1209
+ if (b[0] === 255 && b[1] === 216 && b[2] === 255) return "image/jpeg";
1210
+ if (b[0] === 66 && b[1] === 77) return "image/bmp";
1211
+ if (b[0] === 82 && b[1] === 73 && b[2] === 70 && b[3] === 70 && b[8] === 87 && b[9] === 69) return "image/webp";
1212
+ return null;
1213
+ }
1214
+ function packedTexture(imageNode, srgb) {
1215
+ const img = imageNode && imageNode.id;
1216
+ if (!img) return null;
1217
+ const pf = img.packedfile || img.packedfiles && img.packedfiles.first && img.packedfiles.first.packedfile;
1218
+ const block = pf && pf.data;
1219
+ const size = pf && typeof pf.size === "number" ? pf.size : 0;
1220
+ if (!block || block.__data_address__ === void 0 || size <= 0) return null;
1221
+ const buf = block.__blender_file__.byte.buffer;
1222
+ if (block.__data_address__ + size > buf.byteLength) return null;
1223
+ const bytes = new Uint8Array(buf, block.__data_address__, size).slice();
1224
+ const mime = imageMime(bytes);
1225
+ if (!mime || typeof Blob === "undefined" || typeof URL === "undefined" || typeof Image === "undefined") return null;
1226
+ const url = URL.createObjectURL(new Blob([bytes], { type: mime }));
1227
+ const el = new Image();
1228
+ const texture = new Texture(el);
1229
+ if (srgb) texture.colorSpace = SRGBColorSpace;
1230
+ texture.name = (typeof img.aname === "string" ? img.aname : "") || "blendTexture";
1231
+ el.onload = () => {
1232
+ texture.needsUpdate = true;
1233
+ URL.revokeObjectURL(url);
1234
+ };
1235
+ el.onerror = () => {
1236
+ URL.revokeObjectURL(url);
1237
+ };
1238
+ el.src = url;
1239
+ return texture;
1240
+ }
1241
+ const IMA_SRC_GENERATED = 4, IMA_SRC_VIEWER = 5;
1242
+ function applyWrap(tex, imageNode) {
1243
+ if (!tex) return tex;
1244
+ const ext = imageNode?.storage?.extension;
1245
+ tex.wrapS = tex.wrapT = ext === 1 || ext === 2 ? ClampToEdgeWrapping : ext === 3 ? MirroredRepeatWrapping : RepeatWrapping;
1246
+ tex.needsUpdate = true;
1247
+ return tex;
1248
+ }
1249
+ function imageNodeTexture(imageNode, srgb, ctx) {
1250
+ const packed = packedTexture(imageNode, srgb);
1251
+ if (packed) return applyWrap(packed, imageNode);
1252
+ const img = imageNode && imageNode.id;
1253
+ if (img && img.source !== IMA_SRC_GENERATED && img.source !== IMA_SRC_VIEWER && typeof img.name === "string" && img.name && ctx.loadExternalTexture) {
1254
+ return applyWrap(ctx.loadExternalTexture(img.name, srgb), imageNode);
1255
+ }
1256
+ return null;
1257
+ }
1258
+ function imageNodeFeeding(links, socket) {
1259
+ let src = nodeFeeding(links, socket);
1260
+ if (src && idnameOf(src).includes("NormalMap")) src = nodeFeeding(links, socketByName(inputsOf(src), "Color"));
1261
+ return src && idnameOf(src).includes("TexImage") ? src : null;
1262
+ }
1263
+ function textureFromSocket(links, socket, srgb, ctx) {
1264
+ const src = imageNodeFeeding(links, socket);
1265
+ return src ? imageNodeTexture(src, srgb, ctx) : null;
1266
+ }
1267
+ function resolveShaders(nodes, links) {
1268
+ const outputs = nodes.filter((n) => idnameOf(n).includes("OutputMaterial"));
1269
+ const activeOut = outputs.find((n) => n.is_active_output) || outputs[0];
1270
+ let principled = null, emission = null;
1271
+ if (activeOut) {
1272
+ let surf = nodeFeeding(links, socketByName(inputsOf(activeOut), "Surface"));
1273
+ let guard = 0;
1274
+ while (surf && !idnameOf(surf).includes("BsdfPrincipled") && guard++ < 4) {
1275
+ if (idnameOf(surf).includes("Emission") && !emission) emission = surf;
1276
+ const next = inputsOf(surf).map((s) => nodeFeeding(links, s)).find((n) => n && idnameOf(n).includes("BsdfPrincipled"));
1277
+ if (!next) break;
1278
+ surf = next;
1279
+ }
1280
+ if (surf && idnameOf(surf).includes("BsdfPrincipled")) principled = surf;
1281
+ }
1282
+ if (!principled) principled = nodes.find((n) => idnameOf(n).includes("BsdfPrincipled"));
1283
+ if (!emission) emission = nodes.find((n) => idnameOf(n) === "ShaderNodeEmission");
1284
+ return { principled, emission };
1285
+ }
883
1286
  function createMaterial(mat, ctx) {
884
1287
  const material = new ctx.MeshPhysicalMaterial();
885
- material.color.setRGB(mat.r, mat.g, mat.b);
886
- material.roughness = mat.roughness !== void 0 ? mat.roughness : 0.4;
887
- material.metalness = mat.metallic !== void 0 ? mat.metallic : 0;
888
- material.opacity = 1;
889
- material.transparent = material.opacity < 1;
1288
+ material.side = typeof mat.blend_flag === "number" && mat.blend_flag & MA_BL_CULL_BACKFACE ? FrontSide : DoubleSide;
1289
+ const nodes = mat.nodetree ? listBaseToArray(mat.nodetree.nodes) : [];
1290
+ const links = mat.nodetree ? listBaseToArray(mat.nodetree.links) : [];
1291
+ const { principled, emission } = resolveShaders(nodes, links);
1292
+ let applied = false;
1293
+ if (principled) {
1294
+ const inp = inputsOf(principled);
1295
+ applied = true;
1296
+ const baseSock = socketByName(inp, "Base Color", "BaseColor");
1297
+ const baseImgNode = imageNodeFeeding(links, baseSock);
1298
+ const baseTex = baseImgNode ? imageNodeTexture(baseImgNode, true, ctx) : null;
1299
+ if (baseTex) {
1300
+ material.map = baseTex;
1301
+ material.color.setRGB(1, 1, 1);
1302
+ } else {
1303
+ const base = socketConst(baseSock);
1304
+ if (base && base.length >= 3) material.color.setRGB(clamp01(base[0]), clamp01(base[1]), clamp01(base[2]));
1305
+ }
1306
+ const roughSock = socketByName(inp, "Roughness");
1307
+ const roughTex = textureFromSocket(links, roughSock, false, ctx);
1308
+ if (roughTex) material.roughnessMap = roughTex;
1309
+ else {
1310
+ const r = num(socketConst(roughSock));
1311
+ if (r !== void 0) material.roughness = r;
1312
+ }
1313
+ const metalSock = socketByName(inp, "Metallic");
1314
+ const metalTex = textureFromSocket(links, metalSock, false, ctx);
1315
+ if (metalTex) material.metalnessMap = metalTex;
1316
+ else {
1317
+ const m = num(socketConst(metalSock));
1318
+ if (m !== void 0) material.metalness = m;
1319
+ }
1320
+ const normSrc = nodeFeeding(links, socketByName(inp, "Normal"));
1321
+ if (normSrc) {
1322
+ const normTex = textureFromSocket(links, socketByName(inp, "Normal"), false, ctx);
1323
+ if (normTex) {
1324
+ material.normalMap = normTex;
1325
+ const strength = num(socketConst(socketByName(inputsOf(normSrc), "Strength")));
1326
+ if (strength !== void 0 && material.normalScale) material.normalScale.set(strength, strength);
1327
+ }
1328
+ }
1329
+ const alphaSock = socketByName(inp, "Alpha");
1330
+ const alphaImgNode = imageNodeFeeding(links, alphaSock);
1331
+ const alphaFromBase = !!(alphaImgNode && baseImgNode && alphaImgNode === baseImgNode);
1332
+ const alphaTex = alphaImgNode && !alphaFromBase ? imageNodeTexture(alphaImgNode, false, ctx) : null;
1333
+ const alphaC = num(socketConst(alphaSock));
1334
+ const hasAlpha = !!alphaTex || alphaFromBase || alphaC !== void 0 && alphaC < 1;
1335
+ if (alphaTex) material.alphaMap = alphaTex;
1336
+ else if (!alphaFromBase && alphaC !== void 0 && alphaC < 1) material.opacity = alphaC;
1337
+ if (hasAlpha) {
1338
+ if (mat.blend_method === MA_BM_CLIP) {
1339
+ const thr = num(mat.alpha_threshold);
1340
+ material.alphaTest = thr !== void 0 && thr > 0 ? thr : 0.5;
1341
+ } else material.transparent = true;
1342
+ }
1343
+ const ior = num(socketConst(socketByName(inp, "IOR")));
1344
+ if (ior !== void 0 && material.ior !== void 0) material.ior = ior;
1345
+ const trans = num(socketConst(socketByName(inp, "Transmission Weight", "Transmission")));
1346
+ if (trans !== void 0 && trans > 0 && material.transmission !== void 0) {
1347
+ material.transmission = trans;
1348
+ }
1349
+ const coat = num(socketConst(socketByName(inp, "Coat Weight", "Clearcoat")));
1350
+ if (coat !== void 0 && coat > 0 && material.clearcoat !== void 0) {
1351
+ material.clearcoat = coat;
1352
+ const cr = num(socketConst(socketByName(inp, "Coat Roughness", "Clearcoat Roughness")));
1353
+ if (cr !== void 0) material.clearcoatRoughness = cr;
1354
+ }
1355
+ const sheen = num(socketConst(socketByName(inp, "Sheen Weight", "Sheen")));
1356
+ if (sheen !== void 0 && sheen > 0 && material.sheen !== void 0) {
1357
+ material.sheen = sheen;
1358
+ const stint = socketConst(socketByName(inp, "Sheen Tint"));
1359
+ if (stint && stint.length >= 3 && material.sheenColor) material.sheenColor.setRGB(stint[0], stint[1], stint[2]);
1360
+ const sr = num(socketConst(socketByName(inp, "Sheen Roughness")));
1361
+ if (sr !== void 0) material.sheenRoughness = sr;
1362
+ }
1363
+ const spec = num(socketConst(socketByName(inp, "Specular IOR Level", "Specular")));
1364
+ if (spec !== void 0 && material.specularIntensity !== void 0) {
1365
+ material.specularIntensity = Math.min(1, spec * 2);
1366
+ }
1367
+ }
1368
+ const matOutput = nodes.find((n) => idnameOf(n).includes("OutputMaterial") && n.is_active_output) || nodes.find((n) => idnameOf(n).includes("OutputMaterial"));
1369
+ const dispNode = matOutput ? nodeFeeding(links, socketByName(inputsOf(matOutput), "Displacement")) : null;
1370
+ if (dispNode && idnameOf(dispNode).includes("Displacement")) {
1371
+ const heightTex = textureFromSocket(links, socketByName(inputsOf(dispNode), "Height"), false, ctx);
1372
+ if (heightTex) {
1373
+ const scale = num(socketConst(socketByName(inputsOf(dispNode), "Scale")));
1374
+ const s = scale !== void 0 && scale > 0 ? scale : 1;
1375
+ const mid = num(socketConst(socketByName(inputsOf(dispNode), "Midlevel")));
1376
+ if (material.displacementMap !== void 0) {
1377
+ material.displacementMap = heightTex;
1378
+ material.displacementScale = s;
1379
+ material.displacementBias = -(mid ?? 0.5) * s;
1380
+ }
1381
+ if (!material.normalMap && material.bumpMap !== void 0) {
1382
+ material.bumpMap = heightTex;
1383
+ material.bumpScale = s;
1384
+ }
1385
+ }
1386
+ }
1387
+ const emSock = emission ? socketByName(inputsOf(emission), "Color") : principled ? socketByName(inputsOf(principled), "Emission Color", "Emission") : null;
1388
+ const emStrSock = emission ? socketByName(inputsOf(emission), "Strength") : principled ? socketByName(inputsOf(principled), "Emission Strength") : null;
1389
+ const emTex = textureFromSocket(links, emSock, true, ctx);
1390
+ let emCol = socketConst(emSock);
1391
+ if (emTex && (!emCol || emCol.length < 3 || !emCol[0] && !emCol[1] && !emCol[2])) emCol = [1, 1, 1];
1392
+ if (emCol && emCol.length >= 3 && (emCol[0] || emCol[1] || emCol[2] || emTex)) {
1393
+ const f = [emCol[0], emCol[1], emCol[2]];
1394
+ const strength = num(socketConst(emStrSock));
1395
+ if (typeof strength === "number") for (let i = 0; i < 3; i++) f[i] *= strength;
1396
+ const peak = Math.max(f[0], f[1], f[2]);
1397
+ if (peak > 1) {
1398
+ material.emissiveIntensity = peak;
1399
+ material.emissive.setRGB(f[0] / peak, f[1] / peak, f[2] / peak);
1400
+ } else material.emissive.setRGB(f[0], f[1], f[2]);
1401
+ }
1402
+ if (!applied) {
1403
+ if (mat.r !== void 0) material.color.setRGB(mat.r, mat.g, mat.b);
1404
+ material.roughness = mat.roughness !== void 0 ? mat.roughness : 0.4;
1405
+ material.metalness = mat.metallic !== void 0 ? mat.metallic : 0;
1406
+ }
890
1407
  return material;
891
1408
  }
892
1409
  function createMesh(object, loaded, ctx) {
893
1410
  if (!object.data) {
894
1411
  return void 0;
895
1412
  }
896
- const geometry = loaded.get(object.data) ?? createBufferGeometry(object.data, ctx);
897
- loaded.set(object.data, geometry);
898
- const mat = object.data.mat[0];
899
- const material = mat ? loaded.get(mat) ?? createMaterial(mat, ctx) : new ctx.MeshPhysicalMaterial();
900
- if (mat) loaded.set(mat, material);
1413
+ let geometry = loaded.get(object.data);
1414
+ if (!geometry) {
1415
+ geometry = createBufferGeometry(object.data, ctx);
1416
+ loaded.set(object.data, geometry);
1417
+ }
1418
+ const matData = object.data.mat;
1419
+ const slots = Array.isArray(matData) ? matData : matData ? [matData] : [];
1420
+ const buildMaterial = (m) => {
1421
+ if (!m) {
1422
+ const d = new ctx.MeshPhysicalMaterial();
1423
+ d.side = DoubleSide;
1424
+ return d;
1425
+ }
1426
+ const cached = loaded.get(m);
1427
+ if (cached) return cached;
1428
+ const created = createMaterial(m, ctx);
1429
+ loaded.set(m, created);
1430
+ return created;
1431
+ };
1432
+ const hasGroups = !!(geometry.groups && geometry.groups.length);
1433
+ let material;
1434
+ if (hasGroups) {
1435
+ material = slots.map(buildMaterial);
1436
+ let maxIdx = 0;
1437
+ for (const g of geometry.groups) if (g.materialIndex > maxIdx) maxIdx = g.materialIndex;
1438
+ while (material.length <= maxIdx) material.push(buildMaterial(null));
1439
+ } else {
1440
+ material = buildMaterial(slots[0]);
1441
+ }
901
1442
  const mesh = new ctx.Mesh(geometry, material);
902
1443
  mesh.castShadow = true;
903
1444
  mesh.receiveShadow = true;
@@ -997,25 +1538,743 @@ function setTransform(object, obj) {
997
1538
  obj.updateMatrix();
998
1539
  if (obj.setDirty) obj.setDirty();
999
1540
  }
1541
+ var ab = ArrayBuffer, u8 = Uint8Array, u16 = Uint16Array, i16 = Int16Array, i32 = Int32Array;
1542
+ var slc = function(v, s, e) {
1543
+ if (u8.prototype.slice)
1544
+ return u8.prototype.slice.call(v, s, e);
1545
+ if (s == null || s < 0)
1546
+ s = 0;
1547
+ if (e == null || e > v.length)
1548
+ e = v.length;
1549
+ var n = new u8(e - s);
1550
+ n.set(v.subarray(s, e));
1551
+ return n;
1552
+ };
1553
+ var fill = function(v, n, s, e) {
1554
+ if (u8.prototype.fill)
1555
+ return u8.prototype.fill.call(v, n, s, e);
1556
+ if (s == null || s < 0)
1557
+ s = 0;
1558
+ if (e == null || e > v.length)
1559
+ e = v.length;
1560
+ for (; s < e; ++s)
1561
+ v[s] = n;
1562
+ return v;
1563
+ };
1564
+ var cpw = function(v, t, s, e) {
1565
+ if (u8.prototype.copyWithin)
1566
+ return u8.prototype.copyWithin.call(v, t, s, e);
1567
+ if (s == null || s < 0)
1568
+ s = 0;
1569
+ if (e == null || e > v.length)
1570
+ e = v.length;
1571
+ while (s < e) {
1572
+ v[t++] = v[s++];
1573
+ }
1574
+ };
1575
+ var ec = [
1576
+ "invalid zstd data",
1577
+ "window size too large (>2046MB)",
1578
+ "invalid block type",
1579
+ "FSE accuracy too high",
1580
+ "match distance too far back",
1581
+ "unexpected EOF"
1582
+ ];
1583
+ var err = function(ind, msg, nt) {
1584
+ var e = new Error(msg || ec[ind]);
1585
+ e.code = ind;
1586
+ if (Error.captureStackTrace)
1587
+ Error.captureStackTrace(e, err);
1588
+ if (!nt)
1589
+ throw e;
1590
+ return e;
1591
+ };
1592
+ var rb = function(d, b, n) {
1593
+ var i = 0, o = 0;
1594
+ for (; i < n; ++i)
1595
+ o |= d[b++] << (i << 3);
1596
+ return o;
1597
+ };
1598
+ var b4 = function(d, b) {
1599
+ return (d[b] | d[b + 1] << 8 | d[b + 2] << 16 | d[b + 3] << 24) >>> 0;
1600
+ };
1601
+ var rzfh = function(dat, w) {
1602
+ var n3 = dat[0] | dat[1] << 8 | dat[2] << 16;
1603
+ if (n3 == 3126568 && dat[3] == 253) {
1604
+ var flg = dat[4];
1605
+ var ss = flg >> 5 & 1, cc = flg >> 2 & 1, df = flg & 3, fcf = flg >> 6;
1606
+ if (flg & 8)
1607
+ err(0);
1608
+ var bt = 6 - ss;
1609
+ var db = df == 3 ? 4 : df;
1610
+ var di = rb(dat, bt, db);
1611
+ bt += db;
1612
+ var fsb = fcf ? 1 << fcf : ss;
1613
+ var fss = rb(dat, bt, fsb) + (fcf == 1 && 256);
1614
+ var ws = fss;
1615
+ if (!ss) {
1616
+ var wb = 1 << 10 + (dat[5] >> 3);
1617
+ ws = wb + (wb >> 3) * (dat[5] & 7);
1618
+ }
1619
+ if (ws > 2145386496)
1620
+ err(1);
1621
+ var buf = new u8((w == 1 ? fss || ws : w ? 0 : ws) + 12);
1622
+ buf[0] = 1, buf[4] = 4, buf[8] = 8;
1623
+ return {
1624
+ b: bt + fsb,
1625
+ y: 0,
1626
+ l: 0,
1627
+ d: di,
1628
+ w: w && w != 1 ? w : buf.subarray(12),
1629
+ e: ws,
1630
+ o: new i32(buf.buffer, 0, 3),
1631
+ u: fss,
1632
+ c: cc,
1633
+ m: Math.min(131072, ws)
1634
+ };
1635
+ } else if ((n3 >> 4 | dat[3] << 20) == 25481893) {
1636
+ return b4(dat, 4) + 8;
1637
+ }
1638
+ err(0);
1639
+ };
1640
+ var msb = function(val) {
1641
+ var bits = 0;
1642
+ for (; 1 << bits <= val; ++bits)
1643
+ ;
1644
+ return bits - 1;
1645
+ };
1646
+ var rfse = function(dat, bt, mal) {
1647
+ var tpos = (bt << 3) + 4;
1648
+ var al = (dat[bt] & 15) + 5;
1649
+ if (al > mal)
1650
+ err(3);
1651
+ var sz = 1 << al;
1652
+ var probs = sz, sym = -1, re = -1, i = -1, ht = sz;
1653
+ var buf = new ab(512 + (sz << 2));
1654
+ var freq = new i16(buf, 0, 256);
1655
+ var dstate = new u16(buf, 0, 256);
1656
+ var nstate = new u16(buf, 512, sz);
1657
+ var bb1 = 512 + (sz << 1);
1658
+ var syms = new u8(buf, bb1, sz);
1659
+ var nbits = new u8(buf, bb1 + sz);
1660
+ while (sym < 255 && probs > 0) {
1661
+ var bits = msb(probs + 1);
1662
+ var cbt = tpos >> 3;
1663
+ var msk = (1 << bits + 1) - 1;
1664
+ var val = (dat[cbt] | dat[cbt + 1] << 8 | dat[cbt + 2] << 16) >> (tpos & 7) & msk;
1665
+ var msk1fb = (1 << bits) - 1;
1666
+ var msv = msk - probs - 1;
1667
+ var sval = val & msk1fb;
1668
+ if (sval < msv)
1669
+ tpos += bits, val = sval;
1670
+ else {
1671
+ tpos += bits + 1;
1672
+ if (val > msk1fb)
1673
+ val -= msv;
1674
+ }
1675
+ freq[++sym] = --val;
1676
+ if (val == -1) {
1677
+ probs += val;
1678
+ syms[--ht] = sym;
1679
+ } else
1680
+ probs -= val;
1681
+ if (!val) {
1682
+ do {
1683
+ var rbt = tpos >> 3;
1684
+ re = (dat[rbt] | dat[rbt + 1] << 8) >> (tpos & 7) & 3;
1685
+ tpos += 2;
1686
+ sym += re;
1687
+ } while (re == 3);
1688
+ }
1689
+ }
1690
+ if (sym > 255 || probs)
1691
+ err(0);
1692
+ var sympos = 0;
1693
+ var sstep = (sz >> 1) + (sz >> 3) + 3;
1694
+ var smask = sz - 1;
1695
+ for (var s = 0; s <= sym; ++s) {
1696
+ var sf = freq[s];
1697
+ if (sf < 1) {
1698
+ dstate[s] = -sf;
1699
+ continue;
1700
+ }
1701
+ for (i = 0; i < sf; ++i) {
1702
+ syms[sympos] = s;
1703
+ do {
1704
+ sympos = sympos + sstep & smask;
1705
+ } while (sympos >= ht);
1706
+ }
1707
+ }
1708
+ if (sympos)
1709
+ err(0);
1710
+ for (i = 0; i < sz; ++i) {
1711
+ var ns = dstate[syms[i]]++;
1712
+ var nb = nbits[i] = al - msb(ns);
1713
+ nstate[i] = (ns << nb) - sz;
1714
+ }
1715
+ return [tpos + 7 >> 3, {
1716
+ b: al,
1717
+ s: syms,
1718
+ n: nbits,
1719
+ t: nstate
1720
+ }];
1721
+ };
1722
+ var rhu = function(dat, bt) {
1723
+ var i = 0, wc = -1;
1724
+ var buf = new u8(292), hb = dat[bt];
1725
+ var hw = buf.subarray(0, 256);
1726
+ var rc = buf.subarray(256, 268);
1727
+ var ri = new u16(buf.buffer, 268);
1728
+ if (hb < 128) {
1729
+ var _a = rfse(dat, bt + 1, 6), ebt = _a[0], fdt = _a[1];
1730
+ bt += hb;
1731
+ var epos = ebt << 3;
1732
+ var lb = dat[bt];
1733
+ if (!lb)
1734
+ err(0);
1735
+ var st1 = 0, st2 = 0, btr1 = fdt.b, btr2 = btr1;
1736
+ var fpos = (++bt << 3) - 8 + msb(lb);
1737
+ for (; ; ) {
1738
+ fpos -= btr1;
1739
+ if (fpos < epos)
1740
+ break;
1741
+ var cbt = fpos >> 3;
1742
+ st1 += (dat[cbt] | dat[cbt + 1] << 8) >> (fpos & 7) & (1 << btr1) - 1;
1743
+ hw[++wc] = fdt.s[st1];
1744
+ fpos -= btr2;
1745
+ if (fpos < epos)
1746
+ break;
1747
+ cbt = fpos >> 3;
1748
+ st2 += (dat[cbt] | dat[cbt + 1] << 8) >> (fpos & 7) & (1 << btr2) - 1;
1749
+ hw[++wc] = fdt.s[st2];
1750
+ btr1 = fdt.n[st1];
1751
+ st1 = fdt.t[st1];
1752
+ btr2 = fdt.n[st2];
1753
+ st2 = fdt.t[st2];
1754
+ }
1755
+ if (++wc > 255)
1756
+ err(0);
1757
+ } else {
1758
+ wc = hb - 127;
1759
+ for (; i < wc; i += 2) {
1760
+ var byte = dat[++bt];
1761
+ hw[i] = byte >> 4;
1762
+ hw[i + 1] = byte & 15;
1763
+ }
1764
+ ++bt;
1765
+ }
1766
+ var wes = 0;
1767
+ for (i = 0; i < wc; ++i) {
1768
+ var wt = hw[i];
1769
+ if (wt > 11)
1770
+ err(0);
1771
+ wes += wt && 1 << wt - 1;
1772
+ }
1773
+ var mb = msb(wes) + 1;
1774
+ var ts = 1 << mb;
1775
+ var rem = ts - wes;
1776
+ if (rem & rem - 1)
1777
+ err(0);
1778
+ hw[wc++] = msb(rem) + 1;
1779
+ for (i = 0; i < wc; ++i) {
1780
+ var wt = hw[i];
1781
+ ++rc[hw[i] = wt && mb + 1 - wt];
1782
+ }
1783
+ var hbuf = new u8(ts << 1);
1784
+ var syms = hbuf.subarray(0, ts), nb = hbuf.subarray(ts);
1785
+ ri[mb] = 0;
1786
+ for (i = mb; i > 0; --i) {
1787
+ var pv = ri[i];
1788
+ fill(nb, i, pv, ri[i - 1] = pv + rc[i] * (1 << mb - i));
1789
+ }
1790
+ if (ri[0] != ts)
1791
+ err(0);
1792
+ for (i = 0; i < wc; ++i) {
1793
+ var bits = hw[i];
1794
+ if (bits) {
1795
+ var code = ri[bits];
1796
+ fill(syms, i, code, ri[bits] = code + (1 << mb - bits));
1797
+ }
1798
+ }
1799
+ return [bt, {
1800
+ n: nb,
1801
+ b: mb,
1802
+ s: syms
1803
+ }];
1804
+ };
1805
+ var dllt = rfse(/* @__PURE__ */ new u8([
1806
+ 81,
1807
+ 16,
1808
+ 99,
1809
+ 140,
1810
+ 49,
1811
+ 198,
1812
+ 24,
1813
+ 99,
1814
+ 12,
1815
+ 33,
1816
+ 196,
1817
+ 24,
1818
+ 99,
1819
+ 102,
1820
+ 102,
1821
+ 134,
1822
+ 70,
1823
+ 146,
1824
+ 4
1825
+ ]), 0, 6)[1];
1826
+ var dmlt = rfse(/* @__PURE__ */ new u8([
1827
+ 33,
1828
+ 20,
1829
+ 196,
1830
+ 24,
1831
+ 99,
1832
+ 140,
1833
+ 33,
1834
+ 132,
1835
+ 16,
1836
+ 66,
1837
+ 8,
1838
+ 33,
1839
+ 132,
1840
+ 16,
1841
+ 66,
1842
+ 8,
1843
+ 33,
1844
+ 68,
1845
+ 68,
1846
+ 68,
1847
+ 68,
1848
+ 68,
1849
+ 68,
1850
+ 68,
1851
+ 68,
1852
+ 36,
1853
+ 9
1854
+ ]), 0, 6)[1];
1855
+ var doct = rfse(/* @__PURE__ */ new u8([
1856
+ 32,
1857
+ 132,
1858
+ 16,
1859
+ 66,
1860
+ 102,
1861
+ 70,
1862
+ 68,
1863
+ 68,
1864
+ 68,
1865
+ 68,
1866
+ 36,
1867
+ 73,
1868
+ 2
1869
+ ]), 0, 5)[1];
1870
+ var b2bl = function(b, s) {
1871
+ var len = b.length, bl = new i32(len);
1872
+ for (var i = 0; i < len; ++i) {
1873
+ bl[i] = s;
1874
+ s += 1 << b[i];
1875
+ }
1876
+ return bl;
1877
+ };
1878
+ var llb = /* @__PURE__ */ new u8((/* @__PURE__ */ new i32([
1879
+ 0,
1880
+ 0,
1881
+ 0,
1882
+ 0,
1883
+ 16843009,
1884
+ 50528770,
1885
+ 134678020,
1886
+ 202050057,
1887
+ 269422093
1888
+ ])).buffer, 0, 36);
1889
+ var llbl = /* @__PURE__ */ b2bl(llb, 0);
1890
+ var mlb = /* @__PURE__ */ new u8((/* @__PURE__ */ new i32([
1891
+ 0,
1892
+ 0,
1893
+ 0,
1894
+ 0,
1895
+ 0,
1896
+ 0,
1897
+ 0,
1898
+ 0,
1899
+ 16843009,
1900
+ 50528770,
1901
+ 117769220,
1902
+ 185207048,
1903
+ 252579084,
1904
+ 16
1905
+ ])).buffer, 0, 53);
1906
+ var mlbl = /* @__PURE__ */ b2bl(mlb, 3);
1907
+ var dhu = function(dat, out, hu) {
1908
+ var len = dat.length, ss = out.length, lb = dat[len - 1], msk = (1 << hu.b) - 1, eb = -hu.b;
1909
+ if (!lb)
1910
+ err(0);
1911
+ var st = 0, btr = hu.b, pos = (len << 3) - 8 + msb(lb) - btr, i = -1;
1912
+ for (; pos > eb && i < ss; ) {
1913
+ var cbt = pos >> 3;
1914
+ var val = (dat[cbt] | dat[cbt + 1] << 8 | dat[cbt + 2] << 16) >> (pos & 7);
1915
+ st = (st << btr | val) & msk;
1916
+ out[++i] = hu.s[st];
1917
+ pos -= btr = hu.n[st];
1918
+ }
1919
+ if (pos != eb || i + 1 != ss)
1920
+ err(0);
1921
+ };
1922
+ var dhu4 = function(dat, out, hu) {
1923
+ var bt = 6;
1924
+ var ss = out.length, sz1 = ss + 3 >> 2, sz2 = sz1 << 1, sz3 = sz1 + sz2;
1925
+ dhu(dat.subarray(bt, bt += dat[0] | dat[1] << 8), out.subarray(0, sz1), hu);
1926
+ dhu(dat.subarray(bt, bt += dat[2] | dat[3] << 8), out.subarray(sz1, sz2), hu);
1927
+ dhu(dat.subarray(bt, bt += dat[4] | dat[5] << 8), out.subarray(sz2, sz3), hu);
1928
+ dhu(dat.subarray(bt), out.subarray(sz3), hu);
1929
+ };
1930
+ var rzb = function(dat, st, out) {
1931
+ var _a;
1932
+ var bt = st.b;
1933
+ var b0 = dat[bt], btype = b0 >> 1 & 3;
1934
+ st.l = b0 & 1;
1935
+ var sz = b0 >> 3 | dat[bt + 1] << 5 | dat[bt + 2] << 13;
1936
+ var ebt = (bt += 3) + sz;
1937
+ if (btype == 1) {
1938
+ if (bt >= dat.length)
1939
+ return;
1940
+ st.b = bt + 1;
1941
+ if (out) {
1942
+ fill(out, dat[bt], st.y, st.y += sz);
1943
+ return out;
1944
+ }
1945
+ return fill(new u8(sz), dat[bt]);
1946
+ }
1947
+ if (ebt > dat.length)
1948
+ return;
1949
+ if (btype == 0) {
1950
+ st.b = ebt;
1951
+ if (out) {
1952
+ out.set(dat.subarray(bt, ebt), st.y);
1953
+ st.y += sz;
1954
+ return out;
1955
+ }
1956
+ return slc(dat, bt, ebt);
1957
+ }
1958
+ if (btype == 2) {
1959
+ var b3 = dat[bt], lbt = b3 & 3, sf = b3 >> 2 & 3;
1960
+ var lss = b3 >> 4, lcs = 0, s4 = 0;
1961
+ if (lbt < 2) {
1962
+ if (sf & 1)
1963
+ lss |= dat[++bt] << 4 | (sf & 2 && dat[++bt] << 12);
1964
+ else
1965
+ lss = b3 >> 3;
1966
+ } else {
1967
+ s4 = sf;
1968
+ if (sf < 2)
1969
+ lss |= (dat[++bt] & 63) << 4, lcs = dat[bt] >> 6 | dat[++bt] << 2;
1970
+ else if (sf == 2)
1971
+ lss |= dat[++bt] << 4 | (dat[++bt] & 3) << 12, lcs = dat[bt] >> 2 | dat[++bt] << 6;
1972
+ else
1973
+ lss |= dat[++bt] << 4 | (dat[++bt] & 63) << 12, lcs = dat[bt] >> 6 | dat[++bt] << 2 | dat[++bt] << 10;
1974
+ }
1975
+ ++bt;
1976
+ var buf = out ? out.subarray(st.y, st.y + st.m) : new u8(st.m);
1977
+ var spl = buf.length - lss;
1978
+ if (lbt == 0)
1979
+ buf.set(dat.subarray(bt, bt += lss), spl);
1980
+ else if (lbt == 1)
1981
+ fill(buf, dat[bt++], spl);
1982
+ else {
1983
+ var hu = st.h;
1984
+ if (lbt == 2) {
1985
+ var hud = rhu(dat, bt);
1986
+ lcs += bt - (bt = hud[0]);
1987
+ st.h = hu = hud[1];
1988
+ } else if (!hu)
1989
+ err(0);
1990
+ (s4 ? dhu4 : dhu)(dat.subarray(bt, bt += lcs), buf.subarray(spl), hu);
1991
+ }
1992
+ var ns = dat[bt++];
1993
+ if (ns) {
1994
+ if (ns == 255)
1995
+ ns = (dat[bt++] | dat[bt++] << 8) + 32512;
1996
+ else if (ns > 127)
1997
+ ns = ns - 128 << 8 | dat[bt++];
1998
+ var scm = dat[bt++];
1999
+ if (scm & 3)
2000
+ err(0);
2001
+ var dts = [dmlt, doct, dllt];
2002
+ for (var i = 2; i > -1; --i) {
2003
+ var md = scm >> (i << 1) + 2 & 3;
2004
+ if (md == 1) {
2005
+ var rbuf = new u8([0, 0, dat[bt++]]);
2006
+ dts[i] = {
2007
+ s: rbuf.subarray(2, 3),
2008
+ n: rbuf.subarray(0, 1),
2009
+ t: new u16(rbuf.buffer, 0, 1),
2010
+ b: 0
2011
+ };
2012
+ } else if (md == 2) {
2013
+ _a = rfse(dat, bt, 9 - (i & 1)), bt = _a[0], dts[i] = _a[1];
2014
+ } else if (md == 3) {
2015
+ if (!st.t)
2016
+ err(0);
2017
+ dts[i] = st.t[i];
2018
+ }
2019
+ }
2020
+ var _b = st.t = dts, mlt = _b[0], oct = _b[1], llt = _b[2];
2021
+ var lb = dat[ebt - 1];
2022
+ if (!lb)
2023
+ err(0);
2024
+ var spos = (ebt << 3) - 8 + msb(lb) - llt.b, cbt = spos >> 3, oubt = 0;
2025
+ var lst = (dat[cbt] | dat[cbt + 1] << 8) >> (spos & 7) & (1 << llt.b) - 1;
2026
+ cbt = (spos -= oct.b) >> 3;
2027
+ var ost = (dat[cbt] | dat[cbt + 1] << 8) >> (spos & 7) & (1 << oct.b) - 1;
2028
+ cbt = (spos -= mlt.b) >> 3;
2029
+ var mst = (dat[cbt] | dat[cbt + 1] << 8) >> (spos & 7) & (1 << mlt.b) - 1;
2030
+ for (++ns; --ns; ) {
2031
+ var llc = llt.s[lst];
2032
+ var lbtr = llt.n[lst];
2033
+ var mlc = mlt.s[mst];
2034
+ var mbtr = mlt.n[mst];
2035
+ var ofc = oct.s[ost];
2036
+ var obtr = oct.n[ost];
2037
+ cbt = (spos -= ofc) >> 3;
2038
+ var ofp = 1 << ofc;
2039
+ var off = ofp + ((dat[cbt] | dat[cbt + 1] << 8 | dat[cbt + 2] << 16 | dat[cbt + 3] << 24) >>> (spos & 7) & ofp - 1);
2040
+ cbt = (spos -= mlb[mlc]) >> 3;
2041
+ var ml = mlbl[mlc] + ((dat[cbt] | dat[cbt + 1] << 8 | dat[cbt + 2] << 16) >> (spos & 7) & (1 << mlb[mlc]) - 1);
2042
+ cbt = (spos -= llb[llc]) >> 3;
2043
+ var ll = llbl[llc] + ((dat[cbt] | dat[cbt + 1] << 8 | dat[cbt + 2] << 16) >> (spos & 7) & (1 << llb[llc]) - 1);
2044
+ cbt = (spos -= lbtr) >> 3;
2045
+ lst = llt.t[lst] + ((dat[cbt] | dat[cbt + 1] << 8) >> (spos & 7) & (1 << lbtr) - 1);
2046
+ cbt = (spos -= mbtr) >> 3;
2047
+ mst = mlt.t[mst] + ((dat[cbt] | dat[cbt + 1] << 8) >> (spos & 7) & (1 << mbtr) - 1);
2048
+ cbt = (spos -= obtr) >> 3;
2049
+ ost = oct.t[ost] + ((dat[cbt] | dat[cbt + 1] << 8) >> (spos & 7) & (1 << obtr) - 1);
2050
+ if (off > 3) {
2051
+ st.o[2] = st.o[1];
2052
+ st.o[1] = st.o[0];
2053
+ st.o[0] = off -= 3;
2054
+ } else {
2055
+ var idx = off - (ll != 0);
2056
+ if (idx) {
2057
+ off = idx == 3 ? st.o[0] - 1 : st.o[idx];
2058
+ if (idx > 1)
2059
+ st.o[2] = st.o[1];
2060
+ st.o[1] = st.o[0];
2061
+ st.o[0] = off;
2062
+ } else
2063
+ off = st.o[0];
2064
+ }
2065
+ for (var i = 0; i < ll; ++i) {
2066
+ buf[oubt + i] = buf[spl + i];
2067
+ }
2068
+ oubt += ll, spl += ll;
2069
+ var stin = oubt - off;
2070
+ if (stin < 0) {
2071
+ var len = -stin;
2072
+ var bs = st.e + stin;
2073
+ if (len > ml)
2074
+ len = ml;
2075
+ for (var i = 0; i < len; ++i) {
2076
+ buf[oubt + i] = st.w[bs + i];
2077
+ }
2078
+ oubt += len, ml -= len, stin = 0;
2079
+ }
2080
+ for (var i = 0; i < ml; ++i) {
2081
+ buf[oubt + i] = buf[stin + i];
2082
+ }
2083
+ oubt += ml;
2084
+ }
2085
+ if (oubt != spl) {
2086
+ while (spl < buf.length) {
2087
+ buf[oubt++] = buf[spl++];
2088
+ }
2089
+ } else
2090
+ oubt = buf.length;
2091
+ if (out)
2092
+ st.y += oubt;
2093
+ else
2094
+ buf = slc(buf, 0, oubt);
2095
+ } else if (out) {
2096
+ st.y += lss;
2097
+ if (spl) {
2098
+ for (var i = 0; i < lss; ++i) {
2099
+ buf[i] = buf[spl + i];
2100
+ }
2101
+ }
2102
+ } else if (spl)
2103
+ buf = slc(buf, spl);
2104
+ st.b = ebt;
2105
+ return buf;
2106
+ }
2107
+ err(2);
2108
+ };
2109
+ var cct = function(bufs, ol) {
2110
+ if (bufs.length == 1)
2111
+ return bufs[0];
2112
+ var buf = new u8(ol);
2113
+ for (var i = 0, b = 0; i < bufs.length; ++i) {
2114
+ var chk = bufs[i];
2115
+ buf.set(chk, b);
2116
+ b += chk.length;
2117
+ }
2118
+ return buf;
2119
+ };
2120
+ function decompress(dat, buf) {
2121
+ var bufs = [], nb = +!buf;
2122
+ var bt = 0, ol = 0;
2123
+ for (; dat.length; ) {
2124
+ var st = rzfh(dat, nb || buf);
2125
+ if (typeof st == "object") {
2126
+ if (nb) {
2127
+ buf = null;
2128
+ if (st.w.length == st.u) {
2129
+ bufs.push(buf = st.w);
2130
+ ol += st.u;
2131
+ }
2132
+ } else {
2133
+ bufs.push(buf);
2134
+ st.e = 0;
2135
+ }
2136
+ for (; !st.l; ) {
2137
+ var blk = rzb(dat, st, buf);
2138
+ if (!blk)
2139
+ err(5);
2140
+ if (buf)
2141
+ st.e = st.y;
2142
+ else {
2143
+ bufs.push(blk);
2144
+ ol += blk.length;
2145
+ cpw(st.w, 0, blk.length);
2146
+ st.w.set(blk, st.w.length - blk.length);
2147
+ }
2148
+ }
2149
+ bt = st.b + st.c * 4;
2150
+ } else
2151
+ bt = st;
2152
+ dat = dat.subarray(bt);
2153
+ }
2154
+ return cct(bufs, ol);
2155
+ }
2156
+ function detectBlendCompression(u82) {
2157
+ if (u82.length >= 3 && u82[0] === 31 && u82[1] === 139 && u82[2] === 8) return "gzip";
2158
+ if (u82.length >= 4) {
2159
+ if (u82[0] === 40 && u82[1] === 181 && u82[2] === 47 && u82[3] === 253) return "zstd";
2160
+ if ((u82[0] & 240) === 80 && u82[1] === 42 && u82[2] === 77 && u82[3] === 24) return "zstd";
2161
+ }
2162
+ if (u82.length >= 7 && u82[0] === 66 && u82[1] === 76 && u82[2] === 69 && u82[3] === 78 && u82[4] === 68 && u82[5] === 69 && u82[6] === 82) return "none";
2163
+ return "unknown";
2164
+ }
2165
+ function toArrayBuffer(u82) {
2166
+ if (u82.byteOffset === 0 && u82.byteLength === u82.buffer.byteLength) return u82.buffer;
2167
+ return u82.buffer.slice(u82.byteOffset, u82.byteOffset + u82.byteLength);
2168
+ }
2169
+ function decompressBlend(buf) {
2170
+ const u82 = new Uint8Array(buf);
2171
+ const kind = detectBlendCompression(u82);
2172
+ if (kind === "none") return buf;
2173
+ let out;
2174
+ if (kind === "gzip") out = toArrayBuffer(gunzipSync(u82));
2175
+ else if (kind === "zstd") out = toArrayBuffer(decompress(u82));
2176
+ else {
2177
+ const hex = [u82[0], u82[1], u82[2], u82[3]].map((b) => (b ?? 0).toString(16).padStart(2, "0")).join(" ");
2178
+ throw new Error(`BlendLoadPlugin: unrecognised file magic (first 4 bytes: ${hex}). Expected 'BLENDER' literal, gzip, or zstd.`);
2179
+ }
2180
+ if (out.byteLength < 12) {
2181
+ throw new Error(`BlendLoadPlugin: ${kind} decompression produced ${out.byteLength} bytes, too short to be a valid .blend file.`);
2182
+ }
2183
+ return out;
2184
+ }
2185
+ function resolveExternalTexture(blenderPath, srgb, importer, blendUrl) {
2186
+ if (typeof document === "undefined") return null;
2187
+ const path = blenderPath.replace(/^\/\//, "").replace(/\\/g, "/").replace(/^\.\//, "");
2188
+ if (!path) return null;
2189
+ let url = importer.resolveURL(path);
2190
+ if (url === path && !/^([a-z][a-z0-9+.-]*:|\/|blob:|data:)/i.test(url)) {
2191
+ url = blendUrl.replace(/[?#].*$/, "").replace(/[^/]*$/, "") + path;
2192
+ }
2193
+ const isData = /\.(exr|hdr|rgbe)$/i.test(path.split(/[?#]/)[0]);
2194
+ let texture;
2195
+ if (isData) {
2196
+ texture = new DataTexture(new Uint8Array([255, 255, 255, 255]), 1, 1);
2197
+ } else {
2198
+ const canvas = document.createElement("canvas");
2199
+ canvas.width = canvas.height = 1;
2200
+ const c2d = canvas.getContext("2d");
2201
+ if (c2d) {
2202
+ c2d.fillStyle = "#ffffff";
2203
+ c2d.fillRect(0, 0, 1, 1);
2204
+ }
2205
+ texture = new Texture(canvas);
2206
+ }
2207
+ texture.needsUpdate = true;
2208
+ if (srgb) texture.colorSpace = SRGBColorSpace;
2209
+ return { texture, url, srgb, path };
2210
+ }
2211
+ function flipDataTextureRowsY(tex) {
2212
+ const img = tex.image;
2213
+ const data = img?.data;
2214
+ if (!data?.length || !img.width || !img.height) return;
2215
+ const { width, height } = img;
2216
+ const elemsPerRow = data.length / height;
2217
+ if (!Number.isInteger(elemsPerRow)) return;
2218
+ const flipped = new data.constructor(data.length);
2219
+ for (let y = 0; y < height; y++) {
2220
+ flipped.set(data.subarray(y * elemsPerRow, (y + 1) * elemsPerRow), (height - 1 - y) * elemsPerRow);
2221
+ }
2222
+ const newImage = { data: flipped, width, height };
2223
+ tex.image = newImage;
2224
+ tex.source = new Source(newImage);
2225
+ tex.needsUpdate = true;
2226
+ }
2227
+ async function importExternalTexture(req, importer) {
2228
+ try {
2229
+ const imported = await importer.importSingle(req.url);
2230
+ if (imported && imported.isTexture && imported.image) {
2231
+ const wrapS = req.texture.wrapS, wrapT = req.texture.wrapT;
2232
+ req.texture.copy(imported);
2233
+ req.texture.wrapS = wrapS;
2234
+ req.texture.wrapT = wrapT;
2235
+ req.texture.colorSpace = req.srgb ? SRGBColorSpace : imported.colorSpace;
2236
+ if (imported.isDataTexture && imported.flipY === false) flipDataTextureRowsY(req.texture);
2237
+ req.texture.needsUpdate = true;
2238
+ } else {
2239
+ console.warn("BlendLoadPlugin - external texture not found/unsupported:", req.path);
2240
+ }
2241
+ } catch (e) {
2242
+ console.warn("BlendLoadPlugin - external texture failed:", req.path, e);
2243
+ }
2244
+ }
1000
2245
  const _BlendLoadPlugin = class _BlendLoadPlugin extends BaseImporterPlugin {
1001
2246
  constructor() {
1002
2247
  super();
1003
2248
  this._importer = new Importer(class extends FileLoader {
1004
2249
  async loadAsync(url, onProgress) {
1005
2250
  this.setResponseType("arraybuffer");
1006
- const res = await super.loadAsync(url, onProgress);
1007
- const blend = await parseBlend(res);
2251
+ let res = await super.loadAsync(url, onProgress);
2252
+ const decompressed = decompressBlend(res);
2253
+ res = null;
2254
+ const blend = await parseBlend(decompressed);
2255
+ const importer = this.assetImporter;
2256
+ const texReqs = [];
1008
2257
  const ctx = {
1009
2258
  Object3D: Object3D2,
1010
2259
  Mesh: Mesh2,
1011
2260
  MeshPhysicalMaterial: PhysicalMaterial,
2261
+ MeshBasicMaterial: UnlitMaterial,
1012
2262
  PerspectiveCamera: PerspectiveCamera0,
1013
2263
  OrthographicCamera: OrthographicCamera0,
1014
2264
  PointLight: PointLight2,
2265
+ DirectionalLight: DirectionalLight2,
2266
+ SpotLight: SpotLight2,
2267
+ AmbientLight: AmbientLight2,
1015
2268
  BufferGeometry: BufferGeometry2,
1016
- BufferAttribute
2269
+ BufferAttribute,
2270
+ loadExternalTexture: importer ? (p, srgb) => {
2271
+ const req = resolveExternalTexture(p, srgb, importer, url);
2272
+ if (req) texReqs.push(req);
2273
+ return req ? req.texture : null;
2274
+ } : void 0
1017
2275
  };
1018
2276
  const objects = await createObjects(blend, ctx);
2277
+ if (importer && texReqs.length) await Promise.all(texReqs.map((r) => importExternalTexture(r, importer)));
1019
2278
  const root = new Object3D();
1020
2279
  root.add(...objects);
1021
2280
  blend.scene = root;
@@ -1027,7 +2286,10 @@ const _BlendLoadPlugin = class _BlendLoadPlugin extends BaseImporterPlugin {
1027
2286
  }
1028
2287
  return res.scene;
1029
2288
  }
1030
- }, ["blend"], ["application/x-blender"], true);
2289
+ }, ["blend"], ["application/x-blender"], true, (loader, assetImporter) => {
2290
+ if (loader) loader.assetImporter = assetImporter;
2291
+ return loader;
2292
+ });
1031
2293
  }
1032
2294
  };
1033
2295
  _BlendLoadPlugin.PluginType = "BlendLoadPlugin";