minidraco 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/three.js CHANGED
@@ -212,6 +212,14 @@ var DecoderBuffer = class {
212
212
  this._pos += size;
213
213
  return result;
214
214
  }
215
+ // Zero-copy variant of decodeBytes: a view into the stream, only valid until
216
+ // the caller's next chance to mutate the buffer — copy out before keeping it.
217
+ decodeBytesView(size) {
218
+ if (this._pos + size > this._dataSize) return void 0;
219
+ const result = this._data.subarray(this._pos, this._pos + size);
220
+ this._pos += size;
221
+ return result;
222
+ }
215
223
  startBitDecoding(decodeSize) {
216
224
  let outSize = 0;
217
225
  if (decodeSize) {
@@ -269,6 +277,48 @@ var DecoderBuffer = class {
269
277
  }
270
278
  };
271
279
 
280
+ // src/decoder/core/ScratchArena.ts
281
+ var freeInt32 = [];
282
+ var freeUint8 = [];
283
+ var borrowedInt32 = [];
284
+ var borrowedUint8 = [];
285
+ var acquire = (free, borrowed, size) => {
286
+ for (let i = free.length - 1; i >= 0; --i) {
287
+ const buffer = free[i];
288
+ if (buffer.length >= size) {
289
+ free[i] = free[free.length - 1];
290
+ free.pop();
291
+ borrowed.push(buffer);
292
+ return buffer;
293
+ }
294
+ }
295
+ return null;
296
+ };
297
+ var scratchInt32 = (size) => {
298
+ const pooled = acquire(freeInt32, borrowedInt32, size);
299
+ if (pooled !== null) return pooled.subarray(0, size);
300
+ const fresh = new Int32Array(size);
301
+ borrowedInt32.push(fresh);
302
+ return fresh;
303
+ };
304
+ var scratchUint8Zeroed = (size) => {
305
+ const pooled = acquire(freeUint8, borrowedUint8, size);
306
+ if (pooled !== null) {
307
+ const view = pooled.subarray(0, size);
308
+ view.fill(0);
309
+ return view;
310
+ }
311
+ const fresh = new Uint8Array(size);
312
+ borrowedUint8.push(fresh);
313
+ return fresh;
314
+ };
315
+ var releaseScratch = () => {
316
+ for (const buffer of borrowedInt32) freeInt32.push(buffer);
317
+ for (const buffer of borrowedUint8) freeUint8.push(buffer);
318
+ borrowedInt32.length = 0;
319
+ borrowedUint8.length = 0;
320
+ };
321
+
272
322
  // src/decoder/point_cloud/PointCloud.ts
273
323
  var NAMED_ATTRIBUTES_COUNT = 8;
274
324
  var PointCloud = class {
@@ -368,25 +418,17 @@ var Mesh = class extends PointCloud {
368
418
  this.numFaces_ = 0;
369
419
  this.attribute_data_ = [];
370
420
  }
371
- _ensureFaceCapacity(numFaces) {
372
- if (this.faces_.length >= numFaces * 3) {
373
- return;
374
- }
375
- const grown = new Int32Array(numFaces * 3);
376
- grown.set(this.faces_);
377
- this.faces_ = grown;
378
- }
379
- addFace(face) {
380
- const f = this.numFaces_;
381
- this._ensureFaceCapacity(f + 1);
382
- const o = f * 3;
383
- this.faces_[o] = face[0];
384
- this.faces_[o + 1] = face[1];
385
- this.faces_[o + 2] = face[2];
386
- this.numFaces_ = f + 1;
387
- }
421
+ // Sizes faces_ to hold numFaces faces. Called once per decode, before any
422
+ // face index is written (edgebreaker: _assignPointsToCorners; sequential:
423
+ // decodeConnectivity), so the buffer is allocated exactly here — the
424
+ // decoders fill faces_ directly rather than appending face-by-face.
388
425
  setNumFaces(numFaces) {
389
- this._ensureFaceCapacity(numFaces);
426
+ const needed = numFaces * 3;
427
+ if (this.faces_.length < needed) {
428
+ const grown = new Int32Array(needed);
429
+ grown.set(this.faces_);
430
+ this.faces_ = grown;
431
+ }
390
432
  this.numFaces_ = numFaces;
391
433
  }
392
434
  numFaces() {
@@ -1056,15 +1098,6 @@ var MeshAttributeCornerTable = class {
1056
1098
  leftMostCorner(v) {
1057
1099
  return this.vertex_to_left_most_corner_map_[v];
1058
1100
  }
1059
- face(corner) {
1060
- return this.corner_table_.face(corner);
1061
- }
1062
- firstCorner(faceIndex) {
1063
- return this.corner_table_.firstCorner(faceIndex);
1064
- }
1065
- allCorners(faceIndex) {
1066
- return this.corner_table_.allCorners(faceIndex);
1067
- }
1068
1101
  // --- Flat-array accessors: let DepthFirstTraverser avoid per-corner dispatch. ---
1069
1102
  cornerToVertexArray() {
1070
1103
  return this.corner_to_vertex_map_;
@@ -1079,7 +1112,8 @@ var MeshAttributeCornerTable = class {
1079
1112
  if (seamCorners.length === 0) {
1080
1113
  this._effectiveOpposite = base;
1081
1114
  } else {
1082
- const eff = base.slice(0, nc);
1115
+ const eff = scratchInt32(nc);
1116
+ eff.set(base.length === nc ? base : base.subarray(0, nc));
1083
1117
  for (let i = 0, l = seamCorners.length; i < l; ++i) {
1084
1118
  eff[seamCorners[i]] = kInvalidCornerIndex;
1085
1119
  }
@@ -1113,9 +1147,6 @@ var MeshAttributeCornerTable = class {
1113
1147
  this._effectiveOpposite = other._effectiveOpposite;
1114
1148
  this._seamCorners = other._seamCorners;
1115
1149
  }
1116
- isDegenerated(faceIndex) {
1117
- return this.corner_table_.isDegenerated(faceIndex);
1118
- }
1119
1150
  };
1120
1151
 
1121
1152
  // src/decoder/core/DracoTypes.ts
@@ -1863,15 +1894,12 @@ var SequentialAttributeDecoder = class {
1863
1894
  decodeValues(pointIds, buffer) {
1864
1895
  const numValues = pointIds.length;
1865
1896
  const entrySize = this._attribute.byteStride;
1866
- let outBytePos = 0;
1867
- for (let i = 0; i < numValues; i++) {
1868
- const valueData = buffer.decodeBytes(entrySize);
1869
- if (valueData === void 0) {
1870
- return false;
1871
- }
1872
- this._attribute.buffer.write(outBytePos, valueData, entrySize);
1873
- outBytePos += entrySize;
1897
+ const totalSize = numValues * entrySize;
1898
+ const valueData = buffer.decodeBytesView(totalSize);
1899
+ if (valueData === void 0) {
1900
+ return false;
1874
1901
  }
1902
+ this._attribute.buffer.write(0, valueData, totalSize);
1875
1903
  return true;
1876
1904
  }
1877
1905
  setPortableAttribute(att) {
@@ -2009,6 +2037,9 @@ var RAnsDecoder = class {
2009
2037
  this.bufOffset = offset - 3;
2010
2038
  this.state = memGetLe24(buf, offset - 3) & 4194303;
2011
2039
  } else if (x === 3) {
2040
+ if (offset - base < 4) {
2041
+ return 1;
2042
+ }
2012
2043
  this.bufOffset = offset - 4;
2013
2044
  this.state = memGetLe32(buf, offset - 4) & 1073741823;
2014
2045
  } else {
@@ -2053,12 +2084,69 @@ var RAnsDecoder = class {
2053
2084
  }
2054
2085
  // Batch ransRead() into out[0..count): all fields hoisted to locals, state
2055
2086
  // written back once. Removes per-symbol property reads and call indirection.
2087
+ // lutTable's element type varies per decoder (Uint8/16/32 by symbol count),
2088
+ // which would make the hot lutTable[rem] access site polymorphic — dispatch
2089
+ // once here so each loop body stays monomorphic on its concrete type. The
2090
+ // three bodies are intentionally identical copies.
2056
2091
  decodeSymbols(out, count) {
2092
+ const lutTable = this.lutTable;
2093
+ if (lutTable instanceof Uint8Array) {
2094
+ this._decodeSymbolsU8(out, count, lutTable);
2095
+ } else if (lutTable instanceof Uint16Array) {
2096
+ this._decodeSymbolsU16(out, count, lutTable);
2097
+ } else {
2098
+ this._decodeSymbolsU32(out, count, lutTable);
2099
+ }
2100
+ }
2101
+ _decodeSymbolsU8(out, count, lutTable) {
2102
+ const buf = this.buf;
2103
+ const lRansBase = this.lRansBase;
2104
+ const ransPrecisionBits = this.ransPrecisionBits;
2105
+ const ransPrecisionMask = this.ransPrecisionMask;
2106
+ const probTable = this.probTable;
2107
+ const cumProbTable = this.cumProbTable;
2108
+ let state = this.state;
2109
+ let bufOffset = this.bufOffset;
2110
+ const bufStart = this.bufStart;
2111
+ for (let i = 0; i < count; ++i) {
2112
+ while (state < lRansBase && bufOffset > bufStart) {
2113
+ state = state << 8 | buf[--bufOffset];
2114
+ }
2115
+ const rem = state & ransPrecisionMask;
2116
+ const symbol = lutTable[rem];
2117
+ out[i] = symbol;
2118
+ state = (state >>> ransPrecisionBits) * probTable[symbol] + rem - cumProbTable[symbol];
2119
+ }
2120
+ this.state = state;
2121
+ this.bufOffset = bufOffset;
2122
+ }
2123
+ _decodeSymbolsU16(out, count, lutTable) {
2124
+ const buf = this.buf;
2125
+ const lRansBase = this.lRansBase;
2126
+ const ransPrecisionBits = this.ransPrecisionBits;
2127
+ const ransPrecisionMask = this.ransPrecisionMask;
2128
+ const probTable = this.probTable;
2129
+ const cumProbTable = this.cumProbTable;
2130
+ let state = this.state;
2131
+ let bufOffset = this.bufOffset;
2132
+ const bufStart = this.bufStart;
2133
+ for (let i = 0; i < count; ++i) {
2134
+ while (state < lRansBase && bufOffset > bufStart) {
2135
+ state = state << 8 | buf[--bufOffset];
2136
+ }
2137
+ const rem = state & ransPrecisionMask;
2138
+ const symbol = lutTable[rem];
2139
+ out[i] = symbol;
2140
+ state = (state >>> ransPrecisionBits) * probTable[symbol] + rem - cumProbTable[symbol];
2141
+ }
2142
+ this.state = state;
2143
+ this.bufOffset = bufOffset;
2144
+ }
2145
+ _decodeSymbolsU32(out, count, lutTable) {
2057
2146
  const buf = this.buf;
2058
2147
  const lRansBase = this.lRansBase;
2059
2148
  const ransPrecisionBits = this.ransPrecisionBits;
2060
2149
  const ransPrecisionMask = this.ransPrecisionMask;
2061
- const lutTable = this.lutTable;
2062
2150
  const probTable = this.probTable;
2063
2151
  const cumProbTable = this.cumProbTable;
2064
2152
  let state = this.state;
@@ -2119,16 +2207,12 @@ function computeRAnsPrecisionFromUniqueSymbolsBitLength(symbolsBitLength) {
2119
2207
  return unclamped;
2120
2208
  }
2121
2209
  var RAnsSymbolDecoder = class {
2122
- uniqueSymbolsBitLength_;
2123
2210
  ransPrecisionBits_;
2124
- ransPrecision_;
2125
2211
  probabilityTable_;
2126
2212
  numSymbols_;
2127
2213
  ans_;
2128
2214
  constructor(uniqueSymbolsBitLength) {
2129
- this.uniqueSymbolsBitLength_ = uniqueSymbolsBitLength;
2130
2215
  this.ransPrecisionBits_ = computeRAnsPrecisionFromUniqueSymbolsBitLength(uniqueSymbolsBitLength);
2131
- this.ransPrecision_ = 1 << this.ransPrecisionBits_;
2132
2216
  this.probabilityTable_ = null;
2133
2217
  this.numSymbols_ = 0;
2134
2218
  this.ans_ = new RAnsDecoder(this.ransPrecisionBits_);
@@ -3540,8 +3624,7 @@ var MeshPredictionSchemeTexCoordsPortablePredictor = class {
3540
3624
  }
3541
3625
  const xUV0 = nUV0 * pnNorm2Squared + cnDotPn * pnUV0;
3542
3626
  const xUV1 = nUV1 * pnNorm2Squared + cnDotPn * pnUV1;
3543
- const pnAbsMax = Math.max(Math.abs(pn0), Math.abs(pn1), Math.abs(pn2));
3544
- if (pnAbsMax > 0 && Math.abs(cnDotPn) > INT64_MAX / pnAbsMax) {
3627
+ if (pnAbsMaxG > 0 && cnDotPnAbs > INT64_MAX / pnAbsMaxG) {
3545
3628
  return false;
3546
3629
  }
3547
3630
  const xPos0 = next0 + Math.trunc(cnDotPn * pn0 / pnNorm2Squared);
@@ -4586,15 +4669,6 @@ var AttributeQuantizationTransform = class _AttributeQuantizationTransform exten
4586
4669
  }
4587
4670
  return true;
4588
4671
  }
4589
- get quantizationBits() {
4590
- return this._quantizationBits;
4591
- }
4592
- get range() {
4593
- return this._range;
4594
- }
4595
- minValue(axis) {
4596
- return this._minValues[axis];
4597
- }
4598
4672
  static _isQuantizationValid(quantizationBits) {
4599
4673
  return quantizationBits >= 1 && quantizationBits <= 30;
4600
4674
  }
@@ -4673,7 +4747,7 @@ var SequentialAttributeDecodersController = class extends AttributesDecoder {
4673
4747
  if (!this._sequencer) {
4674
4748
  return false;
4675
4749
  }
4676
- if (!this._sequencer.generateSequence(this._pointIds)) {
4750
+ if (!this._sequencer.generateSequence()) {
4677
4751
  return false;
4678
4752
  }
4679
4753
  this._pointIds = this._sequencer.getOutputPointIds();
@@ -4797,19 +4871,29 @@ var DepthFirstTraverser = class {
4797
4871
  init(cornerTable, observer) {
4798
4872
  this._cornerTable = cornerTable;
4799
4873
  this._observer = observer;
4800
- this._isFaceVisited = new Uint8Array(cornerTable.numFaces());
4801
- this._isVertexVisited = new Uint8Array(cornerTable.numVertices());
4874
+ this._isFaceVisited = null;
4875
+ this._isVertexVisited = null;
4802
4876
  this._numVisitedFaces = 0;
4803
4877
  this._cornerToVertex = cornerTable.cornerToVertexArray();
4804
4878
  this._oppositeCorners = cornerTable.oppositeCornerArray();
4805
4879
  this._vertexLeftmost = cornerTable.vertexLeftmostCornerArray();
4806
4880
  this._numCorners = cornerTable.numCorners();
4807
- this._cornerTraversalStack = new Int32Array(this._numCorners);
4808
4881
  }
4809
4882
  cornerTable() {
4810
4883
  return this._cornerTable;
4811
4884
  }
4885
+ // Scratch buffers are set up here rather than in init() so a shared-traversal
4886
+ // -cache hit — where generateSequence returns before any traversal — skips
4887
+ // this entirely, including the visited-flag zero-fill (worth ~0.5% on the
4888
+ // 488-primitive manablade-static bundle, which shares attribute corner tables across
4889
+ // primitives). Uint8Array (0/1) instead of Array(bool): these flags are read
4890
+ // and written on every corner of the hottest decode loop (traverseFromCorner).
4891
+ // Decode-scoped scratch: released in bulk at the end of the decode.
4812
4892
  onTraversalStart() {
4893
+ const cornerTable = this._cornerTable;
4894
+ this._isFaceVisited = scratchUint8Zeroed(cornerTable.numFaces());
4895
+ this._isVertexVisited = scratchUint8Zeroed(cornerTable.numVertices());
4896
+ this._cornerTraversalStack = scratchInt32(this._numCorners);
4813
4897
  }
4814
4898
  onTraversalEnd() {
4815
4899
  }
@@ -4825,6 +4909,14 @@ var DepthFirstTraverser = class {
4825
4909
  const vertexLeftmost = this._vertexLeftmost;
4826
4910
  const stack = this._cornerTraversalStack;
4827
4911
  let numVisitedFaces = this._numVisitedFaces;
4912
+ const sequencer = observer._sequencer;
4913
+ const encodingData = observer._encodingData;
4914
+ const obsFaces = observer._faces;
4915
+ const encodedToCornerMap = observer._encodedToCornerMap;
4916
+ const vertexToEncodedMap = observer._vertexToEncodedMap;
4917
+ const outPointIds = sequencer._outPointIds;
4918
+ let numOutPoints = sequencer._numOutPoints;
4919
+ let numValues = encodingData.numValues;
4828
4920
  let stackSize = 0;
4829
4921
  stack[stackSize++] = cornerId;
4830
4922
  const nextCorner = cornerId % 3 === 2 ? cornerId - 2 : cornerId + 1;
@@ -4836,11 +4928,15 @@ var DepthFirstTraverser = class {
4836
4928
  }
4837
4929
  if (!isVertexVisited[nextVert]) {
4838
4930
  isVertexVisited[nextVert] = 1;
4839
- observer.onNewVertexVisited(nextVert, nextCorner);
4931
+ outPointIds[numOutPoints++] = obsFaces[nextCorner];
4932
+ encodedToCornerMap[numValues] = nextCorner;
4933
+ vertexToEncodedMap[nextVert] = numValues++;
4840
4934
  }
4841
4935
  if (!isVertexVisited[prevVert]) {
4842
4936
  isVertexVisited[prevVert] = 1;
4843
- observer.onNewVertexVisited(prevVert, prevCorner);
4937
+ outPointIds[numOutPoints++] = obsFaces[prevCorner];
4938
+ encodedToCornerMap[numValues] = prevCorner;
4939
+ vertexToEncodedMap[prevVert] = numValues++;
4844
4940
  }
4845
4941
  while (stackSize > 0) {
4846
4942
  cornerId = stack[stackSize - 1];
@@ -4854,6 +4950,8 @@ var DepthFirstTraverser = class {
4854
4950
  numVisitedFaces++;
4855
4951
  const vertId = cornerToVertex[cornerId];
4856
4952
  if (vertId === kInvalidVertexIndex2) {
4953
+ sequencer._numOutPoints = numOutPoints;
4954
+ encodingData.numValues = numValues;
4857
4955
  return false;
4858
4956
  }
4859
4957
  if (!isVertexVisited[vertId]) {
@@ -4864,7 +4962,9 @@ var DepthFirstTraverser = class {
4864
4962
  onBoundary = oppositeCorners[nextLc] < 0;
4865
4963
  }
4866
4964
  isVertexVisited[vertId] = 1;
4867
- observer.onNewVertexVisited(vertId, cornerId);
4965
+ outPointIds[numOutPoints++] = obsFaces[cornerId];
4966
+ encodedToCornerMap[numValues] = cornerId;
4967
+ vertexToEncodedMap[vertId] = numValues++;
4868
4968
  if (!onBoundary) {
4869
4969
  const nextCornerId2 = cornerId % 3 === 2 ? cornerId - 2 : cornerId + 1;
4870
4970
  cornerId = oppositeCorners[nextCornerId2];
@@ -4901,6 +5001,8 @@ var DepthFirstTraverser = class {
4901
5001
  }
4902
5002
  }
4903
5003
  this._numVisitedFaces = numVisitedFaces;
5004
+ sequencer._numOutPoints = numOutPoints;
5005
+ encodingData.numValues = numValues;
4904
5006
  return true;
4905
5007
  }
4906
5008
  };
@@ -5113,7 +5215,7 @@ var MeshTraversalSequencer = class {
5113
5215
  setTraverser(traverser) {
5114
5216
  this._traverser = traverser;
5115
5217
  }
5116
- generateSequence(_outPointIds) {
5218
+ generateSequence() {
5117
5219
  const cornerTable = this._traverser.cornerTable();
5118
5220
  const methodId = this._traverser._traversalMethodId;
5119
5221
  const cacheKey = cornerTable.cornerToVertexArray();
@@ -5451,7 +5553,7 @@ var MeshEdgebreakerDecoderImpl = class {
5451
5553
  return true;
5452
5554
  }
5453
5555
  _decodeConnectivity(numSymbols) {
5454
- const activeCornerStack = new Int32Array(numSymbols + this._topologySplitData.length + 16);
5556
+ const activeCornerStack = scratchInt32(numSymbols + this._topologySplitData.length + 16);
5455
5557
  let activeCornerStackSize = 0;
5456
5558
  const topologySplitActiveCorners = /* @__PURE__ */ new Map();
5457
5559
  const invalidVertices = [];
@@ -6315,7 +6417,7 @@ var MeshEdgebreakerTraversalValenceDecoder = class extends MeshEdgebreakerTraver
6315
6417
  this._activeContext = -1;
6316
6418
  this._minValence = 2;
6317
6419
  this._maxValence = 7;
6318
- this._vertexValences = [];
6420
+ this._vertexValences = new Int32Array(0);
6319
6421
  this._contextSymbols = [];
6320
6422
  this._contextCounters = [];
6321
6423
  }
@@ -6339,7 +6441,7 @@ var MeshEdgebreakerTraversalValenceDecoder = class extends MeshEdgebreakerTraver
6339
6441
  if (this._numVertices < 0) {
6340
6442
  return false;
6341
6443
  }
6342
- this._vertexValences = new Array(this._numVertices).fill(0);
6444
+ this._vertexValences = new Int32Array(this._numVertices);
6343
6445
  const numUniqueValences = this._maxValence - this._minValence + 1;
6344
6446
  this._contextSymbols = new Array(numUniqueValences);
6345
6447
  this._contextCounters = new Array(numUniqueValences);
@@ -6381,34 +6483,37 @@ var MeshEdgebreakerTraversalValenceDecoder = class extends MeshEdgebreakerTraver
6381
6483
  return this._lastSymbol;
6382
6484
  }
6383
6485
  newActiveCornerReached(corner) {
6384
- const ct = this._cornerTable;
6385
- const next = ct.next(corner);
6386
- const prev = ct.previous(corner);
6486
+ const cornerToVertex = this._cornerTable._cornerToVertex;
6487
+ const valences = this._vertexValences;
6488
+ const next = corner % 3 === 2 ? corner - 2 : corner + 1;
6489
+ const prev = corner % 3 === 0 ? corner + 2 : corner - 1;
6490
+ const vertNext = cornerToVertex[next];
6491
+ const vertPrev = cornerToVertex[prev];
6387
6492
  switch (this._lastSymbol) {
6388
6493
  case TOPOLOGY_C:
6389
6494
  case TOPOLOGY_S:
6390
- this._vertexValences[ct.vertex(next)] += 1;
6391
- this._vertexValences[ct.vertex(prev)] += 1;
6495
+ valences[vertNext] += 1;
6496
+ valences[vertPrev] += 1;
6392
6497
  break;
6393
6498
  case TOPOLOGY_R:
6394
- this._vertexValences[ct.vertex(corner)] += 1;
6395
- this._vertexValences[ct.vertex(next)] += 1;
6396
- this._vertexValences[ct.vertex(prev)] += 2;
6499
+ valences[cornerToVertex[corner]] += 1;
6500
+ valences[vertNext] += 1;
6501
+ valences[vertPrev] += 2;
6397
6502
  break;
6398
6503
  case TOPOLOGY_L:
6399
- this._vertexValences[ct.vertex(corner)] += 1;
6400
- this._vertexValences[ct.vertex(next)] += 2;
6401
- this._vertexValences[ct.vertex(prev)] += 1;
6504
+ valences[cornerToVertex[corner]] += 1;
6505
+ valences[vertNext] += 2;
6506
+ valences[vertPrev] += 1;
6402
6507
  break;
6403
6508
  case TOPOLOGY_E:
6404
- this._vertexValences[ct.vertex(corner)] += 2;
6405
- this._vertexValences[ct.vertex(next)] += 2;
6406
- this._vertexValences[ct.vertex(prev)] += 2;
6509
+ valences[cornerToVertex[corner]] += 2;
6510
+ valences[vertNext] += 2;
6511
+ valences[vertPrev] += 2;
6407
6512
  break;
6408
6513
  default:
6409
6514
  break;
6410
6515
  }
6411
- const activeValence = this._vertexValences[ct.vertex(next)];
6516
+ const activeValence = valences[vertNext];
6412
6517
  let clampedValence;
6413
6518
  if (activeValence < this._minValence) {
6414
6519
  clampedValence = this._minValence;
@@ -6527,45 +6632,30 @@ var MeshSequentialDecoder = class extends MeshDecoder {
6527
6632
  return false;
6528
6633
  }
6529
6634
  } else {
6635
+ const mesh = this.mesh();
6636
+ mesh.setNumFaces(numFaces);
6637
+ const faces = mesh.faces_;
6638
+ const numIndices = numFaces * 3;
6639
+ const buffer = this.buffer();
6530
6640
  if (numPoints < 256) {
6531
- for (let i = 0; i < numFaces; ++i) {
6532
- const face = [0, 0, 0];
6533
- for (let j = 0; j < 3; ++j) {
6534
- const val = this.buffer().decodeUint8();
6535
- if (val === void 0) return false;
6536
- face[j] = val;
6537
- }
6538
- this.mesh().addFace(face);
6539
- }
6641
+ const src = buffer.decodeBytesView(numIndices);
6642
+ if (src === void 0) return false;
6643
+ for (let i = 0; i < numIndices; ++i) faces[i] = src[i];
6540
6644
  } else if (numPoints < 1 << 16) {
6541
- for (let i = 0; i < numFaces; ++i) {
6542
- const face = [0, 0, 0];
6543
- for (let j = 0; j < 3; ++j) {
6544
- const val = this.buffer().decodeUint16();
6545
- if (val === void 0) return false;
6546
- face[j] = val;
6547
- }
6548
- this.mesh().addFace(face);
6549
- }
6645
+ const src = buffer.decodeBytesView(numIndices * 2);
6646
+ if (src === void 0) return false;
6647
+ for (let i = 0; i < numIndices; ++i) faces[i] = src[i * 2] | src[i * 2 + 1] << 8;
6550
6648
  } else if (numPoints < 1 << 21) {
6551
- for (let i = 0; i < numFaces; ++i) {
6552
- const face = [0, 0, 0];
6553
- for (let j = 0; j < 3; ++j) {
6554
- const val = decodeVarint(this.buffer());
6555
- if (val === void 0) return false;
6556
- face[j] = val;
6557
- }
6558
- this.mesh().addFace(face);
6649
+ for (let i = 0; i < numIndices; ++i) {
6650
+ const val = decodeVarint(buffer);
6651
+ if (val === void 0) return false;
6652
+ faces[i] = val;
6559
6653
  }
6560
6654
  } else {
6561
- for (let i = 0; i < numFaces; ++i) {
6562
- const face = [0, 0, 0];
6563
- for (let j = 0; j < 3; ++j) {
6564
- const val = this.buffer().decodeUint32();
6565
- if (val === void 0) return false;
6566
- face[j] = val;
6567
- }
6568
- this.mesh().addFace(face);
6655
+ const src = buffer.decodeBytesView(numIndices * 4);
6656
+ if (src === void 0) return false;
6657
+ for (let i = 0; i < numIndices; ++i) {
6658
+ faces[i] = src[i * 4] | src[i * 4 + 1] << 8 | src[i * 4 + 2] << 16 | src[i * 4 + 3] << 24;
6569
6659
  }
6570
6660
  }
6571
6661
  }
@@ -6579,32 +6669,31 @@ var MeshSequentialDecoder = class extends MeshDecoder {
6579
6669
  );
6580
6670
  }
6581
6671
  _decodeAndDecompressIndices(numFaces) {
6582
- const indicesBuffer = new Uint32Array(numFaces * 3);
6583
- if (!decodeSymbols(numFaces * 3, 1, this.buffer(), indicesBuffer)) {
6672
+ const numIndices = numFaces * 3;
6673
+ const indicesBuffer = new Uint32Array(numIndices);
6674
+ if (!decodeSymbols(numIndices, 1, this.buffer(), indicesBuffer)) {
6584
6675
  return false;
6585
6676
  }
6677
+ const mesh = this.mesh();
6678
+ mesh.setNumFaces(numFaces);
6679
+ const faces = mesh.faces_;
6586
6680
  let lastIndexValue = 0;
6587
- let vertexIndex = 0;
6588
- for (let i = 0; i < numFaces; ++i) {
6589
- const face = [0, 0, 0];
6590
- for (let j = 0; j < 3; ++j) {
6591
- const encodedVal = indicesBuffer[vertexIndex++];
6592
- let indexDiff = encodedVal >>> 1;
6593
- if (encodedVal & 1) {
6594
- if (indexDiff > lastIndexValue) {
6595
- return false;
6596
- }
6597
- indexDiff = -indexDiff;
6598
- } else {
6599
- if (indexDiff > 2147483647 - lastIndexValue) {
6600
- return false;
6601
- }
6681
+ for (let i = 0; i < numIndices; ++i) {
6682
+ const encodedVal = indicesBuffer[i];
6683
+ let indexDiff = encodedVal >>> 1;
6684
+ if (encodedVal & 1) {
6685
+ if (indexDiff > lastIndexValue) {
6686
+ return false;
6687
+ }
6688
+ indexDiff = -indexDiff;
6689
+ } else {
6690
+ if (indexDiff > 2147483647 - lastIndexValue) {
6691
+ return false;
6602
6692
  }
6603
- const indexValue = indexDiff + lastIndexValue | 0;
6604
- face[j] = indexValue;
6605
- lastIndexValue = indexValue;
6606
6693
  }
6607
- this.mesh().addFace(face);
6694
+ const indexValue = indexDiff + lastIndexValue | 0;
6695
+ faces[i] = indexValue;
6696
+ lastIndexValue = indexValue;
6608
6697
  }
6609
6698
  return true;
6610
6699
  }
@@ -6662,9 +6751,15 @@ var Decoder = class {
6662
6751
  if (result.header.encoderType !== EncodedGeometryType.TRIANGULAR_MESH) {
6663
6752
  return { ok: false, message: "Input is not a mesh." };
6664
6753
  }
6665
- const decoder = createMeshDecoder(result.header.encoderMethod);
6666
- const status = decoder.decodeMesh(this.options_, inBuffer, outGeometry);
6667
- return { ok: status.ok(), message: status.errorMsg };
6754
+ try {
6755
+ const decoder = createMeshDecoder(result.header.encoderMethod);
6756
+ const status = decoder.decodeMesh(this.options_, inBuffer, outGeometry);
6757
+ return { ok: status.ok(), message: status.errorMsg };
6758
+ } catch (error) {
6759
+ return { ok: false, message: error instanceof Error ? error.message : String(error) };
6760
+ } finally {
6761
+ releaseScratch();
6762
+ }
6668
6763
  }
6669
6764
  options() {
6670
6765
  return this.options_;
@@ -6687,6 +6782,7 @@ var decodeDracoMesh = (data) => {
6687
6782
  };
6688
6783
 
6689
6784
  // src/three/index.ts
6785
+ var isLoadingManager = (value) => typeof value?.itemStart === "function";
6690
6786
  var _taskCache = /* @__PURE__ */ new WeakMap();
6691
6787
  var _attributeTypeMap = {
6692
6788
  POSITION: Type.POSITION,
@@ -6708,14 +6804,31 @@ var MiniDRACOLoader = class extends Loader {
6708
6804
  defaultAttributeIDs;
6709
6805
  defaultAttributeTypes;
6710
6806
  workerLimit;
6807
+ // Opt-in (0 = disabled): buffers at or below this size decode synchronously
6808
+ // on the main thread instead of paying the ~0.5 ms worker message roundtrip.
6809
+ // Worth enabling (e.g. 4096) when the main thread is otherwise idle during
6810
+ // loads — in a full GLTFLoader parse the main thread is already busy
6811
+ // building geometries, and measurements show the pool wins there even for
6812
+ // tiny primitives.
6813
+ syncByteThreshold;
6711
6814
  _workers;
6712
6815
  _taskId;
6713
6816
  _tasks;
6817
+ // Tasks queued during the current microtask; flushed as one batched message
6818
+ // per worker (a 488-primitive scene costs 4 postMessages, not 488).
6819
+ _batch;
6820
+ _batchScheduled;
6714
6821
  // Set when spawning a worker fails (bundler without module-worker support,
6715
6822
  // file:// pages, …): decoding transparently falls back to the main thread.
6716
6823
  _workersBroken;
6717
- constructor(manager) {
6718
- super(manager);
6824
+ _workerUrl;
6825
+ // Same-origin blob bootstrap used when the worker asset lives on a CDN
6826
+ // origin (created lazily, revoked on dispose).
6827
+ _workerBlobUrl;
6828
+ // Accepts either a LoadingManager (three-compatible form) or an options bag.
6829
+ constructor(managerOrOptions) {
6830
+ const options = isLoadingManager(managerOrOptions) ? { manager: managerOrOptions } : managerOrOptions ?? {};
6831
+ super(options.manager);
6719
6832
  this.defaultAttributeIDs = {
6720
6833
  position: "POSITION",
6721
6834
  normal: "NORMAL",
@@ -6728,14 +6841,31 @@ var MiniDRACOLoader = class extends Loader {
6728
6841
  color: "Float32Array",
6729
6842
  uv: "Float32Array"
6730
6843
  };
6731
- this.workerLimit = 4;
6844
+ this.workerLimit = options.workers === false ? 0 : options.workerLimit ?? 4;
6845
+ this.syncByteThreshold = options.syncByteThreshold ?? 0;
6732
6846
  this._workers = [];
6733
6847
  this._taskId = 0;
6734
6848
  this._tasks = /* @__PURE__ */ new Map();
6849
+ this._batch = [];
6850
+ this._batchScheduled = false;
6735
6851
  this._workersBroken = false;
6852
+ this._workerUrl = null;
6853
+ this._workerBlobUrl = null;
6854
+ }
6855
+ // Overrides where the decode worker is loaded from. Normally unnecessary:
6856
+ // the worker resolves through `new URL('./worker.js', import.meta.url)`
6857
+ // (bundlers emit it as a hashed asset), and CDN origins are handled by the
6858
+ // blob bootstrap in _getWorker.
6859
+ setWorkerUrl(url) {
6860
+ this._workerUrl = url;
6861
+ return this;
6736
6862
  }
6737
- // Kept for API compatibility with THREE.DRACOLoader — minidraco has no
6738
- // external decoder files to configure.
6863
+ // No-ops kept for API compatibility with THREE.DRACOLoader — minidraco has
6864
+ // no external decoder files to configure. The params are `unknown` (not
6865
+ // `string`/`object`) so the signatures stay assignable to THREE.DRACOLoader
6866
+ // across three versions, whose setDecoderPath has grown to accept a
6867
+ // `string | DecoderPaths` — letting `new MiniDRACOLoader()` be passed to
6868
+ // GLTFLoader.setDRACOLoader() with no cast.
6739
6869
  setDecoderPath(_path) {
6740
6870
  return this;
6741
6871
  }
@@ -6746,14 +6876,35 @@ var MiniDRACOLoader = class extends Loader {
6746
6876
  this.workerLimit = limit;
6747
6877
  return this;
6748
6878
  }
6879
+ // Toggle the worker pool on/off. false decodes synchronously on the main
6880
+ // thread; true enables the pool, keeping the current size or falling back to
6881
+ // the default 4 if it was disabled. For a specific pool size use
6882
+ // setWorkerLimit(n).
6883
+ setWorkers(enabled) {
6884
+ this.workerLimit = enabled ? this.workerLimit || 4 : 0;
6885
+ return this;
6886
+ }
6749
6887
  preload() {
6750
- if (this._workersAvailable()) this._getWorker();
6888
+ if (this._workersAvailable()) {
6889
+ while (this._workers.length < this.workerLimit && this._getWorker() !== null) {
6890
+ }
6891
+ }
6751
6892
  return this;
6752
6893
  }
6753
6894
  dispose() {
6754
6895
  for (const entry of this._workers) entry.worker.terminate();
6755
6896
  this._workers = [];
6897
+ const disposedError = new Error("MiniDRACOLoader: disposed while decoding");
6898
+ disposedError.isDecodeError = true;
6899
+ for (const task of this._batch) task.reject(disposedError);
6900
+ this._batch = [];
6901
+ this._batchScheduled = false;
6902
+ for (const [, task] of this._tasks) task.reject(disposedError);
6756
6903
  this._tasks.clear();
6904
+ if (this._workerBlobUrl !== null) {
6905
+ URL.revokeObjectURL(this._workerBlobUrl);
6906
+ this._workerBlobUrl = null;
6907
+ }
6757
6908
  return this;
6758
6909
  }
6759
6910
  load(url, onLoad, onProgress, onError) {
@@ -6791,10 +6942,6 @@ var MiniDRACOLoader = class extends Loader {
6791
6942
  const cachedTask = _taskCache.get(buffer);
6792
6943
  if (cachedTask.key === taskKey) {
6793
6944
  return cachedTask.promise;
6794
- } else if (buffer.byteLength === 0) {
6795
- throw new Error(
6796
- "MiniDRACOLoader: Unable to re-decode a buffer with different settings. Buffer has already been transferred."
6797
- );
6798
6945
  }
6799
6946
  }
6800
6947
  const geometryPending = this._runTask(buffer, taskConfig);
@@ -6803,12 +6950,16 @@ var MiniDRACOLoader = class extends Loader {
6803
6950
  }
6804
6951
  async _runTask(buffer, taskConfig) {
6805
6952
  if (this._workersAvailable()) {
6806
- try {
6807
- const raw = await this._decodeInWorker(buffer, taskConfig);
6808
- return this._buildGeometryFromRaw(raw, taskConfig);
6809
- } catch (error) {
6810
- if (error?.isDecodeError) throw error;
6811
- this._workersBroken = true;
6953
+ if (buffer.byteLength > this.syncByteThreshold) {
6954
+ try {
6955
+ const raw = await this._decodeInWorker(buffer, taskConfig);
6956
+ return this._buildGeometryFromRaw(raw, taskConfig);
6957
+ } catch (error) {
6958
+ if (error?.isDecodeError) throw error;
6959
+ this._workersBroken = true;
6960
+ }
6961
+ } else {
6962
+ await Promise.resolve();
6812
6963
  }
6813
6964
  }
6814
6965
  return this._decodeBuffer(buffer, taskConfig);
@@ -6817,21 +6968,38 @@ var MiniDRACOLoader = class extends Loader {
6817
6968
  return this.workerLimit > 0 && typeof Worker !== "undefined" && !this._workersBroken;
6818
6969
  }
6819
6970
  _getWorker() {
6971
+ if (this._workersBroken) return null;
6820
6972
  if (this._workers.length < this.workerLimit) {
6821
- const worker = new Worker(new URL("./worker.js", import.meta.url), { type: "module" });
6973
+ const workerUrl = this._workerUrl ?? new URL("./worker.js", import.meta.url);
6974
+ let worker;
6975
+ try {
6976
+ worker = new Worker(workerUrl, { type: "module" });
6977
+ } catch {
6978
+ try {
6979
+ if (this._workerBlobUrl === null) {
6980
+ const bootstrap = `import ${JSON.stringify(String(workerUrl))};`;
6981
+ this._workerBlobUrl = URL.createObjectURL(new Blob([bootstrap], { type: "text/javascript" }));
6982
+ }
6983
+ worker = new Worker(this._workerBlobUrl, { type: "module" });
6984
+ } catch {
6985
+ this._workersBroken = true;
6986
+ return null;
6987
+ }
6988
+ }
6822
6989
  const entry = { worker, pending: 0 };
6823
6990
  worker.onmessage = (event) => {
6824
- const { id, ok, indices, attributes, error } = event.data;
6825
- const task = this._tasks.get(id);
6826
- if (!task) return;
6827
- this._tasks.delete(id);
6828
- task.entry.pending--;
6829
- if (ok) {
6830
- task.resolve({ indices, attributes });
6831
- } else {
6832
- const decodeError = new Error(error);
6833
- decodeError.isDecodeError = true;
6834
- task.reject(decodeError);
6991
+ for (const { id, ok, indices, attributes, error } of event.data.results) {
6992
+ const task = this._tasks.get(id);
6993
+ if (!task) continue;
6994
+ this._tasks.delete(id);
6995
+ task.entry.pending--;
6996
+ if (ok) {
6997
+ task.resolve({ indices, attributes });
6998
+ } else {
6999
+ const decodeError = new Error(error);
7000
+ decodeError.isDecodeError = true;
7001
+ task.reject(decodeError);
7002
+ }
6835
7003
  }
6836
7004
  };
6837
7005
  worker.onerror = (event) => {
@@ -6851,19 +7019,56 @@ var MiniDRACOLoader = class extends Loader {
6851
7019
  return best;
6852
7020
  }
6853
7021
  _decodeInWorker(buffer, taskConfig) {
6854
- const entry = this._getWorker();
6855
7022
  const id = this._taskId++;
6856
7023
  return new Promise((resolve, reject) => {
6857
- this._tasks.set(id, { resolve, reject, entry });
6858
- entry.pending++;
7024
+ this._batch.push({ id, buffer, taskConfig, resolve, reject });
7025
+ if (!this._batchScheduled) {
7026
+ this._batchScheduled = true;
7027
+ queueMicrotask(() => this._flushBatch());
7028
+ }
7029
+ });
7030
+ }
7031
+ _flushBatch() {
7032
+ this._batchScheduled = false;
7033
+ const batch = this._batch;
7034
+ if (batch.length === 0) return;
7035
+ this._batch = [];
7036
+ while (this._workers.length < Math.min(this.workerLimit, batch.length) && this._getWorker() !== null) {
7037
+ }
7038
+ if (this._workersBroken || this._workers.length === 0) {
7039
+ const error = new Error("MiniDRACOLoader: worker unavailable");
7040
+ for (const task of batch) task.reject(error);
7041
+ return;
7042
+ }
7043
+ batch.sort((a, b) => b.buffer.byteLength - a.buffer.byteLength);
7044
+ let totalBytes = 0;
7045
+ for (const task of batch) totalBytes += task.buffer.byteLength;
7046
+ const averageBytes = totalBytes / batch.length;
7047
+ const buckets = this._workers.map((entry) => ({
7048
+ entry,
7049
+ tasks: [],
7050
+ bytes: entry.pending * averageBytes
7051
+ }));
7052
+ for (const task of batch) {
7053
+ let best = buckets[0];
7054
+ for (const bucket of buckets) if (bucket.bytes < best.bytes) best = bucket;
7055
+ best.tasks.push(task);
7056
+ best.bytes += task.buffer.byteLength;
7057
+ }
7058
+ for (const { entry, tasks } of buckets) {
7059
+ if (tasks.length === 0) continue;
7060
+ entry.pending += tasks.length;
7061
+ for (const task of tasks) this._tasks.set(task.id, { resolve: task.resolve, reject: task.reject, entry });
6859
7062
  entry.worker.postMessage({
6860
- id,
6861
- buffer,
6862
- attributeIDs: taskConfig.attributeIDs,
6863
- attributeTypes: taskConfig.attributeTypes,
6864
- useUniqueIDs: taskConfig.useUniqueIDs
7063
+ tasks: tasks.map((task) => ({
7064
+ id: task.id,
7065
+ buffer: task.buffer,
7066
+ attributeIDs: task.taskConfig.attributeIDs,
7067
+ attributeTypes: task.taskConfig.attributeTypes,
7068
+ useUniqueIDs: task.taskConfig.useUniqueIDs
7069
+ }))
6865
7070
  });
6866
- });
7071
+ }
6867
7072
  }
6868
7073
  _buildGeometryFromRaw(raw, taskConfig) {
6869
7074
  const geometry = new BufferGeometry();