ldr 0.23.0 → 0.24.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/README.md CHANGED
@@ -117,7 +117,7 @@ MPD-embedded parts, such as Studio/OMR unofficial parts or stickers, become part
117
117
  | `createLdrObject(instances, pack, options?) → LdrObject` | High-level three.js object (see below). |
118
118
  | `createInstancedEdges(pos, instancedMesh, material?)` | Welded, indexed edge lines that share an `InstancedMesh`'s instance matrices. |
119
119
  | `conditionalLines(pack, entry, which?)` / `createInstancedConditionalEdges(cond, instancedMesh, material?)` / `conditionalLineVisible` | Type-5 conditional lines from a `cond` pack. A vertex shader keeps a segment only where both control points project to the same side of the line. |
120
- | `makeTexturedMaterial(side, texture, wrapU?)` / `packTexture(pack, i)` | Printed and sticker parts from a `textures` pack: a UV material and the decoded embedded PNG. |
120
+ | `makeTexturedMaterial(side, texture, wrapU?, base?, gloss?)` / `packTexture(pack, i, data?)` / `pngHasAlpha(bytes)` | Printed and sticker parts from a `textures` pack: a UV material and the decoded embedded PNG. `gloss` (`{ map, alpha }`, a `GLOSSMAP` PNG decoded with `data = true`) adds specularity where its alpha is set (its grey value if the PNG has no alpha channel). |
121
121
  | `buildLdrScene`, `computeCulling`, `cullCore`, `makeMaterial` | Low-level renderer used by the benchmarks. `cullCore(instances, header, steps?)` is the typed-array culling core (with `steps`, also the step at which each stud and underside becomes hidden). |
122
122
 
123
123
  `LdrObjectOptions`:
@@ -131,7 +131,7 @@ MPD-embedded parts, such as Studio/OMR unofficial parts or stickers, become part
131
131
  | `material` | `"plastic"` | `"plastic"` is `MeshPhysicalMaterial` with clearcoat; `"standard"` is `MeshStandardMaterial`, the cheapest option. |
132
132
  | `conditionalLines` | `true` | Conditional (silhouette) lines. Only takes effect with `cond` packs, and follows `edges`. |
133
133
  | `conditionalStudLines` | `true` | Conditional lines on studs. `false` roughly halves the conditional-line cost on large scenes. |
134
- | `textures` | `true` | Renders `!TEXMAP` images from `textures` packs; `false` draws the FALLBACK geometry. `object.texturesReady` resolves once the images are decoded. |
134
+ | `textures` | `true` | Renders `!TEXMAP` images from `textures` packs, with `GLOSSMAP` images as extra gloss (lower roughness, more specular) on the printed area; `false` draws the FALLBACK geometry. `object.texturesReady` resolves once the images are decoded. |
135
135
  | `colors` | pack table | Colour override: a `Map`, a record, or `(code) => color`. |
136
136
  | `fallbackColor` | `#A0A5A9` | Colour used for unknown codes. |
137
137
  | `ldrawSpace` | `false` | `true` keeps native LDraw space (-Y up). |
@@ -143,7 +143,7 @@ MPD-embedded parts, such as Studio/OMR unofficial parts or stickers, become part
143
143
  | `lodEdgeMinPx` | `0.15` | LOD: draw only edge segments at least this many pixels long on screen. `0` = every segment. |
144
144
  | `lodUnder` | `true` | LOD: skip undersides that can't be seen (camera behind every bottom face of a cluster's parts, or the body drawn as its closed bounding box). Lossless in the fidelity checks. |
145
145
 
146
- `LdrObject`: `object`, `stats` (`instances`, `drawn`, `missing[]`, `studsHidden/Total`, `undersHidden/Total`, `buildMs`, `condBatches`, `texBatches`, `textures`, `clusters`), `stepCount`, `stepViews`, `texturesReady`, `setStep(n | null)`, `setEdges(on)`, `setConditionalLines(on)`, `setCulling(studs, undersides)`, `setSide(side)`, `setLod(on)`, `getBox(target?)`, `dispose()`.
146
+ `LdrObject`: `object`, `stats` (`instances`, `drawn`, `missing[]`, `studsHidden/Total`, `undersHidden/Total`, `buildMs`, `condBatches`, `texBatches`, `textures`, `glossmaps`, `clusters`), `stepCount`, `stepViews`, `texturesReady`, `setStep(n | null)`, `setEdges(on)`, `setConditionalLines(on)`, `setCulling(studs, undersides)`, `setSide(side)`, `setLod(on)`, `getBox(target?)`, `dispose()`.
147
147
 
148
148
  `setStep` doesn't rebuild anything: it compacts instance buffers, and culling is step-aware (a stud covered only by a later step's part stays visible). `stats` reflect the shown step.
149
149
 
@@ -163,7 +163,7 @@ Everything above, plus:
163
163
  | export | description |
164
164
  |---|---|
165
165
  | `buildPack(options) → { data, parts, studKinds, missing, warnings? }` | Builds a pack. Options: `ldrawRoot`, `models`, `parts`, `allParts`, `studs` (`"low"` \| `"full"`), `primitives` (`"low"` \| `"full"`, default `"low"`), `weld` (`true`), `cond` (`false`), `textures` (`false`), `looseGeometry` (`true`), `colors` (`true`), `lod` (`true`: meshoptimizer-simplified LOD meshes, pack feature `lod`), `reorder` (`false`: vertex-cache order), `onProgress`. |
166
- | `Library(root, unofficialRoot?)` | Filesystem LDraw resolver: `where`, `flatten`, `mesh`, `colors`, `texture`, `register`. Handles case-insensitive lookup and `~Moved to` aliases. |
166
+ | `Library(root, unofficialRoot?)` | Filesystem LDraw resolver: `where`, `flatten`, `mesh` (`{ texmap: true }` adds `uv`, per-triangle `texture`, `textures` and, when a map has one, `glossmaps`), `colors`, `texture`, `register`. Handles case-insensitive lookup and `~Moved to` aliases. |
167
167
  | `Compiler`, `encodePack`, `writePack`, `loadColors` | Lower-level pack compiler. |
168
168
  | `ResolverLibrary`, `splitModel`, `flattenWithLibrary`, `collectDeps` | The resolver that `buildPack` uses. |
169
169
 
@@ -207,7 +207,7 @@ Reproduce: `cd bench && bun run setup && bun run perf` (regression gate) or `bun
207
207
  - MPD: `0 FILE` / `0 NOFILE`, and `0 !DATA` (base64 PNGs). An MPD file is a part or a model by its `!LDRAW_ORG` type, otherwise by whether it has its own geometry. Embedded parts override library parts.
208
208
  - Geometry drawn directly in model files (type 2–5 lines outside parts), on by default.
209
209
  - `0 STEP`, nested submodel step sequencing, `0 ROTSTEP` (`REL`/`ADD`/`ABS`/`END`), and `0 BUFEXCHG`.
210
- - `!TEXMAP` `START`/`NEXT`/`FALLBACK`/`END`, with `PLANAR`, `CYLINDRICAL` and `SPHERICAL` projections (orientation matches LDView), and model-level `!TEXMAP` around part references.
210
+ - `!TEXMAP` `START`/`NEXT`/`FALLBACK`/`END`, with `PLANAR`, `CYLINDRICAL` and `SPHERICAL` projections (orientation matches LDView), `GLOSSMAP` (alpha = gloss amount, same UVs), and model-level `!TEXMAP` around part references.
211
211
  - Colours: LDConfig `!COLOUR`, edge colours, main/edge colour inheritance (16/24), and direct colours (`0x2RRGGBB`).
212
212
  - Case-insensitive, backslash-normalized file lookup; `~Moved to` alias parts.
213
213
 
package/dist/cli.js CHANGED
@@ -1,18 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/cli.ts
3
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/cli.ts
4
4
  import fs4 from "node:fs";
5
5
  import zlib from "node:zlib";
6
6
 
7
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/node/build-pack.ts
7
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/node/build-pack.ts
8
8
  import fs3 from "node:fs";
9
9
  import path3 from "node:path";
10
10
 
11
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/node/resolver.ts
11
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/node/resolver.ts
12
12
  import fs from "node:fs";
13
13
  import path from "node:path";
14
14
 
15
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/spec.ts
15
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/spec.ts
16
16
  var norm = (s) => s.trim().toLowerCase().replace(/\\/g, "/").replace(/\/+/g, "/");
17
17
  function parseColor(tok) {
18
18
  if (/^0x/i.test(tok))
@@ -705,7 +705,7 @@ function fileStepViews(f) {
705
705
  return out;
706
706
  }
707
707
 
708
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/model.ts
708
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/model.ts
709
709
  var CALLOUT_END = 999999;
710
710
  var stepKey = (steps) => steps.map((x) => String(x).padStart(6, "0")).join(".") + "~";
711
711
  function flatten(doc, opts = {}) {
@@ -1055,7 +1055,7 @@ function parseLDConfig(text) {
1055
1055
  return out;
1056
1056
  }
1057
1057
 
1058
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/node/resolver.ts
1058
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/node/resolver.ts
1059
1059
  var TOP_STUD_RE = /^(stud|studa|stud2|stud2a|stud6|stud6a|stud10|stud15|stud2s|stud2s2|stud-logo\d*|stud2-logo\d*)\.dat$/i;
1060
1060
  var UNDER_STUD_RE = /^(stud3|stud3a|stud4.*|stud12|stud16.*|stud18a)\.dat$/i;
1061
1061
  var norm2 = (ref) => ref.trim().replace(/\\/g, "/").toLowerCase();
@@ -1325,11 +1325,11 @@ function collectDeps(lib, refs) {
1325
1325
  return out;
1326
1326
  }
1327
1327
 
1328
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/node/compile.ts
1328
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/node/compile.ts
1329
1329
  import fs2 from "node:fs";
1330
1330
  import path2 from "node:path";
1331
1331
 
1332
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/node/lod.ts
1332
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/node/lod.ts
1333
1333
  import { MeshoptSimplifier, MeshoptEncoder } from "meshoptimizer";
1334
1334
  await Promise.all([MeshoptSimplifier.ready, MeshoptEncoder.ready]);
1335
1335
  function reorderMesh(P, N, C, I) {
@@ -1521,7 +1521,7 @@ function lodChain(P, C, I) {
1521
1521
  return out;
1522
1522
  }
1523
1523
 
1524
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/node/compile.ts
1524
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/node/compile.ts
1525
1525
  function loadColors(root) {
1526
1526
  const map = new Map;
1527
1527
  for (const l of fs2.readFileSync(path2.join(root, "LDConfig.ldr"), "latin1").split(/\r?\n/)) {
@@ -1670,6 +1670,21 @@ class Compiler {
1670
1670
  az1 = Math.max(az1, b.pos[i + 2]);
1671
1671
  }
1672
1672
  out.fullBottom = isFinite(x0) && ax0 <= x0 + 0.5 && ax1 >= x1 - 0.5 && az0 <= z0 + 0.5 && az1 >= z1 - 0.5;
1673
+ if (out.under.pos.length && !underEnclosed(out)) {
1674
+ const add = (d, s) => {
1675
+ for (const v of s)
1676
+ d.push(v);
1677
+ };
1678
+ for (const b of out.tbody ? [out.body, out.tbody] : [out.body]) {
1679
+ add(b.pos, out.under.pos);
1680
+ add(b.col, out.under.col);
1681
+ }
1682
+ if (out.cond && out.condUnder) {
1683
+ add(out.cond, out.condUnder);
1684
+ out.condUnder.length = 0;
1685
+ }
1686
+ out.under = { pos: [], col: [] };
1687
+ }
1673
1688
  if (separateStuds && isFinite(x0))
1674
1689
  out.bottomMask = enclosureMask(out);
1675
1690
  this.cache.set(ck, out);
@@ -1811,9 +1826,10 @@ class Compiler {
1811
1826
  order = n === 3 ? [0, 2, 1] : [0, 2, 1, 0, 3, 2];
1812
1827
  const rgba = colorOf(code);
1813
1828
  if (tex) {
1814
- let b = texb.get(tex.texture);
1829
+ const tk = tex.glossmap ? tex.texture + "\x00" + tex.glossmap : tex.texture;
1830
+ let b = texb.get(tk);
1815
1831
  if (!b)
1816
- texb.set(tex.texture, b = { pos: [], col: [], uv: [] });
1832
+ texb.set(tk, b = { pos: [], col: [], uv: [], texture: tex.texture, ...tex.glossmap ? { glossmap: tex.glossmap } : {} });
1817
1833
  if (Math.abs(tex.a ?? 0) >= 360)
1818
1834
  b.wrapU = true;
1819
1835
  for (let k = 0;k < order.length; k += 3) {
@@ -1839,6 +1855,33 @@ class Compiler {
1839
1855
  }
1840
1856
  }
1841
1857
  }
1858
+ function underEnclosed(c) {
1859
+ const u = c.under.pos;
1860
+ let uy0 = Infinity, uy1 = -Infinity, ux0 = Infinity, ux1 = -Infinity, uz0 = Infinity, uz1 = -Infinity;
1861
+ for (let i = 0;i < u.length; i += 3) {
1862
+ ux0 = Math.min(ux0, u[i]);
1863
+ ux1 = Math.max(ux1, u[i]);
1864
+ uy0 = Math.min(uy0, u[i + 1]);
1865
+ uy1 = Math.max(uy1, u[i + 1]);
1866
+ uz0 = Math.min(uz0, u[i + 2]);
1867
+ uz1 = Math.max(uz1, u[i + 2]);
1868
+ }
1869
+ const lo = uy0 + Math.min(0.5, (uy1 - uy0) / 4), hi = uy1 - Math.min(0.5, (uy1 - uy0) / 4);
1870
+ const p = c.body.pos;
1871
+ let bx0 = Infinity, bx1 = -Infinity, bz0 = Infinity, bz1 = -Infinity;
1872
+ for (let i = 0;i < p.length; i += 9) {
1873
+ const y0 = Math.min(p[i + 1], p[i + 4], p[i + 7]), y1 = Math.max(p[i + 1], p[i + 4], p[i + 7]);
1874
+ if (y1 <= lo || y0 >= hi)
1875
+ continue;
1876
+ for (let k = i;k < i + 9; k += 3) {
1877
+ bx0 = Math.min(bx0, p[k]);
1878
+ bx1 = Math.max(bx1, p[k]);
1879
+ bz0 = Math.min(bz0, p[k + 2]);
1880
+ bz1 = Math.max(bz1, p[k + 2]);
1881
+ }
1882
+ }
1883
+ return bx0 < ux0 + 0.01 && bx1 > ux1 - 0.01 && bz0 < uz0 + 0.01 && bz1 > uz1 - 0.01;
1884
+ }
1842
1885
  function enclosureMask(c) {
1843
1886
  const [x0, , z0, x1, y1, z1] = c.bbox;
1844
1887
  const nx = Math.round((x1 - x0) / 20), nz = Math.round((z1 - z0) / 20);
@@ -2074,11 +2117,15 @@ function encodePack(parts, studKinds, weld, extra, options = {}) {
2074
2117
  const encTex = (c) => {
2075
2118
  if (!c.texb)
2076
2119
  return {};
2120
+ const idx = (name) => {
2121
+ let i = texNames.indexOf(name);
2122
+ if (i < 0)
2123
+ i = texNames.push(name) - 1;
2124
+ return i;
2125
+ };
2077
2126
  const tex = [...c.texb].map(([name, b]) => {
2078
- let t = texNames.indexOf(name);
2079
- if (t < 0)
2080
- t = texNames.push(name) - 1;
2081
- return { t, ...b.wrapU ? { wrapU: true } : {}, ...texBucket(b) };
2127
+ const t = idx(b.texture ?? name);
2128
+ return { t, ...b.glossmap ? { g: idx(b.glossmap) } : {}, ...b.wrapU ? { wrapU: true } : {}, ...texBucket(b) };
2082
2129
  });
2083
2130
  return { tbody: bucket(c.tbody), tex };
2084
2131
  };
@@ -2104,7 +2151,7 @@ function encodePack(parts, studKinds, weld, extra, options = {}) {
2104
2151
  return Buffer.concat([head, ...chunks.map((c) => new Uint8Array(c.buffer, c.byteOffset, c.byteLength))]);
2105
2152
  }
2106
2153
 
2107
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/node/build-pack.ts
2154
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/node/build-pack.ts
2108
2155
  function buildPack(o) {
2109
2156
  const lib = new Library(o.ldrawRoot);
2110
2157
  const refs = new Map;
@@ -2172,7 +2219,7 @@ function buildPack(o) {
2172
2219
  return { data: encodePack(parts, kinds, comp.opts.weld, extra, { texture, lod: o.lod !== false, reorder: !!o.reorder }), parts: parts.size, studKinds: kinds.size, missing: [...missing], ...warnings.length ? { warnings } : {} };
2173
2220
  }
2174
2221
 
2175
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/cli.ts
2222
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/cli.ts
2176
2223
  var HELP = `ldr pack — build a parts pack for the ldr three.js renderer
2177
2224
 
2178
2225
  Usage:
package/dist/index.d.ts CHANGED
@@ -4,9 +4,9 @@ export { flatten, parseModel, sequenceSteps, stepKey, parseLDConfig, parseColour
4
4
  export { planStepViews, isolateMask, viewDir, instanceBoxes, MIN_VIS, REORIENT } from "./instructions.js";
5
5
  export type { StepCameraView, StepCameraOptions, Vec3 } from "./instructions.js";
6
6
  export type { PartInstance, FlattenOptions, LDrawColor, LDrawFinish, LDrawFinishType, LDrawMaterialGrain, ColourSurface } from "./model.js";
7
- export { loadPack, decodePack, buildLdrScene, buildLdrSceneAsync, makeMaterial, makeFinishMaterial, createInstancedEdges, computeCulling, cullCore, forEachStud, conditionalLines, createInstancedConditionalEdges, conditionalLineVisible, warmPackGeometry } from "./render.js";
7
+ export { loadPack, decodePack, buildLdrScene, buildLdrSceneAsync, makeMaterial, makeFinishMaterial, createInstancedEdges, computeCulling, cullCore, forEachStud, conditionalLines, createInstancedConditionalEdges, conditionalLineVisible, warmPackGeometry, makeTexturedMaterial, packTexture, pngHasAlpha } from "./render.js";
8
8
  export { precomputeCull, sceneInstances } from "./cull.js";
9
- export type { Pack, LdrParams, Inst, CullResult, BuildLdrSceneAsyncOptions } from "./render.js";
9
+ export type { Pack, LdrParams, Inst, CullResult, BuildLdrSceneAsyncOptions, GlossMap } from "./render.js";
10
10
  export { createLdrObject, createLdrObjectAsync } from "./scene.js";
11
11
  export type { LdrObject, LdrObjectOptions, LdrStats, ColorSource } from "./scene.js";
12
12
  export { decompressPack, decodeModelText, modelFileName } from "./pack-io.js";
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/spec.ts
1
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/spec.ts
2
2
  var norm = (s) => s.trim().toLowerCase().replace(/\\/g, "/").replace(/\/+/g, "/");
3
3
  function parseColor(tok) {
4
4
  if (/^0x/i.test(tok))
@@ -689,7 +689,7 @@ function fileStepViews(f) {
689
689
  viewMemo.set(f, out);
690
690
  return out;
691
691
  }
692
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/model.ts
692
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/model.ts
693
693
  var CALLOUT_END = 999999;
694
694
  var stepKey = (steps) => steps.map((x) => String(x).padStart(6, "0")).join(".") + "~";
695
695
  function flatten(doc, opts = {}) {
@@ -1038,7 +1038,7 @@ function parseLDConfig(text) {
1038
1038
  }
1039
1039
  return out;
1040
1040
  }
1041
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/instructions.ts
1041
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/instructions.ts
1042
1042
  var MIN_VIS = 0.5;
1043
1043
  var REORIENT = 0.35;
1044
1044
  var deg = Math.PI / 180;
@@ -1406,10 +1406,10 @@ function planStepViews(insts, parts, stepOf, nSteps, o = {}) {
1406
1406
  }
1407
1407
  };
1408
1408
  }
1409
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/render.ts
1409
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/render.ts
1410
1410
  import * as THREE from "three";
1411
1411
 
1412
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/pack-io.ts
1412
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/pack-io.ts
1413
1413
  import { decompress } from "fzstd";
1414
1414
  async function decompressPack(data) {
1415
1415
  const u = data instanceof Uint8Array ? data : new Uint8Array(data);
@@ -1432,7 +1432,7 @@ function decodePack(buf) {
1432
1432
  return { header, arr, bytes: buf.byteLength, buffer: buf };
1433
1433
  }
1434
1434
 
1435
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/cull.ts
1435
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/cull.ts
1436
1436
  var upright = (m, o = 0) => Math.abs(m[o + 7] - 1) < 0.001 && Math.abs(m[o + 4]) < 0.001 && Math.abs(m[o + 10]) < 0.001;
1437
1437
  var prepCache = new WeakMap;
1438
1438
  function prepPart(H, part) {
@@ -1749,7 +1749,7 @@ function precomputeCull(instances, H, useTex = true) {
1749
1749
  return cullCore(s.all, H, s.nSteps > 1 ? s.stepOf : undefined);
1750
1750
  }
1751
1751
 
1752
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/render.ts
1752
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/render.ts
1753
1753
  async function loadPack(url) {
1754
1754
  const res = await fetch(url);
1755
1755
  if (!res.ok)
@@ -1900,7 +1900,7 @@ function makeFinishMaterial(side, surface) {
1900
1900
  m.userData.ldrSurface = surface;
1901
1901
  return m;
1902
1902
  }
1903
- function makeTexturedMaterial(side, map, wrapU = false, baseMat) {
1903
+ function makeTexturedMaterial(side, map, wrapU = false, baseMat, gloss) {
1904
1904
  const surface = baseMat?.userData?.ldrSurface ?? colourSurface({ alpha: 255, luminance: 0, finish: { type: "default" } });
1905
1905
  const m = makeFinishMaterial(side, surface);
1906
1906
  const prev = m.onBeforeCompile;
@@ -1912,20 +1912,48 @@ function makeTexturedMaterial(side, map, wrapU = false, baseMat) {
1912
1912
  sh.vertexShader = `attribute vec2 ldrUV; varying vec2 vLdrUV;
1913
1913
  ` + sh.vertexShader.replace("#include <uv_vertex>", `#include <uv_vertex>
1914
1914
  vLdrUV = ldrUV;`);
1915
- sh.fragmentShader = `uniform sampler2D ldrMap; uniform int ldrWrapU; varying vec2 vLdrUV;
1915
+ let fs = `uniform sampler2D ldrMap; uniform int ldrWrapU; varying vec2 vLdrUV;
1916
1916
  ` + sh.fragmentShader.replace("#include <color_fragment>", `#include <color_fragment>
1917
- { vec2 uv = vec2(ldrWrapU == 1 ? fract(vLdrUV.x) : vLdrUV.x, vLdrUV.y); vec4 t = texture2D(ldrMap, uv);
1918
- float inside = step(0.0, uv.x) * step(uv.x, 1.0) * step(0.0, uv.y) * step(uv.y, 1.0);
1919
- diffuseColor.rgb = mix(diffuseColor.rgb, t.rgb, t.a * inside); }`);
1917
+ vec2 ldrUv = vec2(ldrWrapU == 1 ? fract(vLdrUV.x) : vLdrUV.x, vLdrUV.y);
1918
+ float ldrInside = step(0.0, ldrUv.x) * step(ldrUv.x, 1.0) * step(0.0, ldrUv.y) * step(ldrUv.y, 1.0);
1919
+ { vec4 t = texture2D(ldrMap, ldrUv); diffuseColor.rgb = mix(diffuseColor.rgb, t.rgb, t.a * ldrInside); }`);
1920
+ if (gloss) {
1921
+ sh.uniforms.ldrGlossMap = { value: gloss.map };
1922
+ sh.uniforms.ldrGlossA = { value: gloss.alpha ? 1 : 0 };
1923
+ fs = `uniform sampler2D ldrGlossMap; uniform float ldrGlossA;
1924
+ ` + fs.replace("#include <roughnessmap_fragment>", `#include <roughnessmap_fragment>
1925
+ vec4 ldrG = texture2D(ldrGlossMap, ldrUv); float ldrGloss = mix(ldrG.r, ldrG.a, ldrGlossA) * ldrInside;
1926
+ roughnessFactor = mix(roughnessFactor, 0.08, ldrGloss);`).replace("#include <lights_fragment_end>", `#include <lights_fragment_end>
1927
+ reflectedLight.directSpecular *= 1.0 + 0.5 * ldrGloss; reflectedLight.indirectSpecular *= 1.0 + 0.5 * ldrGloss;`);
1928
+ }
1929
+ sh.fragmentShader = fs;
1920
1930
  };
1921
- m.customProgramCacheKey = () => "ldrColorTex|" + baseKey;
1931
+ m.customProgramCacheKey = () => "ldrColorTex|" + baseKey + (gloss ? "|g" : "");
1922
1932
  m.userData.ldrMap = map;
1933
+ if (gloss)
1934
+ m.userData.ldrGloss = gloss;
1923
1935
  return m;
1924
1936
  }
1925
- function packTexture(pack, i) {
1937
+ function pngHasAlpha(b) {
1938
+ if (b.length < 33 || b[0] !== 137 || b[1] !== 80 || b[2] !== 78 || b[3] !== 71)
1939
+ return false;
1940
+ const type = b[25];
1941
+ if (type === 4 || type === 6)
1942
+ return true;
1943
+ for (let o = 8;o + 8 <= b.length; ) {
1944
+ const len = (b[o] << 24 | b[o + 1] << 16 | b[o + 2] << 8 | b[o + 3]) >>> 0, t = String.fromCharCode(b[o + 4], b[o + 5], b[o + 6], b[o + 7]);
1945
+ if (t === "tRNS")
1946
+ return true;
1947
+ if (t === "IDAT" || t === "IEND")
1948
+ return false;
1949
+ o += 12 + len;
1950
+ }
1951
+ return false;
1952
+ }
1953
+ function packTexture(pack, i, data = false) {
1926
1954
  const t = new THREE.Texture;
1927
1955
  t.flipY = false;
1928
- t.colorSpace = THREE.SRGBColorSpace;
1956
+ t.colorSpace = data ? THREE.NoColorSpace : THREE.SRGBColorSpace;
1929
1957
  t.wrapS = t.wrapT = THREE.ClampToEdgeWrapping;
1930
1958
  const e = pack.header.textures?.[i];
1931
1959
  const bytes = e?.data ? pack.arr(e.data) : null;
@@ -2713,10 +2741,29 @@ function* buildLdrSceneGen(insts, pack, P, color, mark) {
2713
2741
  const texCache = new Map;
2714
2742
  const texMats = new Map;
2715
2743
  const pending = [];
2716
- const texMat = (t, base, wrapU) => {
2744
+ const glossCache = new Map;
2745
+ const glossOf = (g) => {
2746
+ if (g === undefined)
2747
+ return;
2748
+ let gm = glossCache.get(g);
2749
+ if (gm === undefined) {
2750
+ const e = H.textures?.[g];
2751
+ const bytes = e?.data ? pack.arr(e.data) : null;
2752
+ if (bytes) {
2753
+ const r = packTexture(pack, g, true);
2754
+ pending.push(r.ready);
2755
+ gm = { map: r.texture, alpha: pngHasAlpha(bytes) };
2756
+ } else
2757
+ gm = null;
2758
+ glossCache.set(g, gm);
2759
+ }
2760
+ return gm ?? undefined;
2761
+ };
2762
+ const texMat = (t, base, wrapU, g) => {
2717
2763
  const role = base.userData?.ldrSideRole ?? (base.side === THREE.BackSide ? "B" : base.side === THREE.DoubleSide ? "D" : "F");
2718
2764
  const fin = base.userData?.ldrFinish ?? "default";
2719
- const k = t + ":" + role + ":" + fin + ":" + (wrapU ? 1 : 0);
2765
+ const gloss = glossOf(g);
2766
+ const k = t + ":" + role + ":" + fin + ":" + (wrapU ? 1 : 0) + (gloss ? ":g" + g : "");
2720
2767
  let m = texMats.get(k);
2721
2768
  if (!m) {
2722
2769
  let tx = texCache.get(t);
@@ -2726,7 +2773,7 @@ function* buildLdrSceneGen(insts, pack, P, color, mark) {
2726
2773
  pending.push(r.ready);
2727
2774
  texCache.set(t, tx);
2728
2775
  }
2729
- texMats.set(k, m = makeTexturedMaterial(base.side, tx, wrapU, base));
2776
+ texMats.set(k, m = makeTexturedMaterial(base.side, tx, wrapU, base, gloss));
2730
2777
  }
2731
2778
  return m;
2732
2779
  };
@@ -2795,7 +2842,7 @@ function* buildLdrSceneGen(insts, pack, P, color, mark) {
2795
2842
  if (textured)
2796
2843
  for (const tb of p.tex) {
2797
2844
  const gg = geom(tb, arr);
2798
- const im = newIM(gg, texMat(tb.t, mat, !!tb.wrapU), ids.length);
2845
+ const im = newIM(gg, texMat(tb.t, mat, !!tb.wrapU, tb.g), ids.length);
2799
2846
  texBatches++;
2800
2847
  lv0.push(im);
2801
2848
  if (bodyIm) {
@@ -3209,6 +3256,7 @@ function* buildLdrSceneGen(insts, pack, P, color, mark) {
3209
3256
  stats.condBatches = condObjs.length;
3210
3257
  stats.texBatches = texBatches;
3211
3258
  stats.textures = texCache.size;
3259
+ stats.glossmaps = [...glossCache.values()].filter(Boolean).length;
3212
3260
  stats.clusters = root.clusters.length;
3213
3261
  const texturesReady = Promise.all(pending).then(() => {});
3214
3262
  let stepS = STEP_NEVER - 1, cullS = !!P.cullStuds, cullU = !!P.cullUnder;
@@ -3415,7 +3463,7 @@ async function warmPackGeometry(pack, stop = () => false, sliceMs = 8) {
3415
3463
  await new Promise((r) => setTimeout(r, 0));
3416
3464
  }
3417
3465
  }
3418
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/scene.ts
3466
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/scene.ts
3419
3467
  import * as THREE2 from "three";
3420
3468
  function colorFn(src, pack, fallback) {
3421
3469
  const table = new Map;
@@ -3516,6 +3564,7 @@ function createLdrObjectInner(instances, pack, options, progressive) {
3516
3564
  });
3517
3565
  for (const m of [...mats, ...pairs.values()]) {
3518
3566
  m.userData.ldrMap?.dispose();
3567
+ m.userData.ldrGloss?.map.dispose();
3519
3568
  m.dispose();
3520
3569
  }
3521
3570
  cur = null;
@@ -3545,7 +3594,7 @@ function createLdrObjectInner(instances, pack, options, progressive) {
3545
3594
  cur.setLod(false);
3546
3595
  if (cur.root.parent !== object)
3547
3596
  object.add(cur.root);
3548
- Object.assign(stats, { drawn: drawnAt(null), studsTotal: cur.stats.studsTotal, studsHidden: cur.stats.studsHidden, undersTotal: cur.stats.undersTotal, undersHidden: cur.stats.undersHidden, condBatches: cur.stats.condBatches ?? 0, texBatches: cur.stats.texBatches ?? 0, textures: cur.stats.textures ?? 0, clusters: cur.stats.clusters ?? 0, longestSliceMs: cur.stats.longestSliceMs, buildYields: cur.stats.buildYields, buildMs: performance.now() - t0 });
3597
+ Object.assign(stats, { drawn: drawnAt(null), studsTotal: cur.stats.studsTotal, studsHidden: cur.stats.studsHidden, undersTotal: cur.stats.undersTotal, undersHidden: cur.stats.undersHidden, condBatches: cur.stats.condBatches ?? 0, texBatches: cur.stats.texBatches ?? 0, textures: cur.stats.textures ?? 0, glossmaps: cur.stats.glossmaps ?? 0, clusters: cur.stats.clusters ?? 0, longestSliceMs: cur.stats.longestSliceMs, buildYields: cur.stats.buildYields, buildMs: performance.now() - t0 });
3549
3598
  api.texturesReady = cur.texturesReady;
3550
3599
  };
3551
3600
  const setStep = (step, mask = null) => {
@@ -3632,10 +3681,10 @@ function createLdrObjectInner(instances, pack, options, progressive) {
3632
3681
  return finish();
3633
3682
  })();
3634
3683
  }
3635
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/parts-cdn.ts
3684
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/parts-cdn.ts
3636
3685
  import { decompress as decompress2 } from "fzstd";
3637
3686
 
3638
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/embedded.ts
3687
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/embedded.ts
3639
3688
  function splitFiles(text, fallback) {
3640
3689
  const files = new Map;
3641
3690
  let cur = null, saw = false;
@@ -3707,7 +3756,7 @@ function embeddedKeys(text, names, fallback = "main.ldr") {
3707
3756
  return out;
3708
3757
  }
3709
3758
 
3710
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/parts-cdn.ts
3759
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/parts-cdn.ts
3711
3760
  var DEFAULT_PARTS_CDN = "https://ldr-demo.vercel.app/cdn/v1/";
3712
3761
  var indexCache = new Map;
3713
3762
  var shardCache = new Map;
@@ -3939,10 +3988,10 @@ async function prepareModel(source, o = {}) {
3939
3988
  return { insts, pack, missing, requests, bytes, cull, timings: { fetchModel: t1 - t0, parse: t2 - t1, parts: t3 - t2, ...cull ? { cull: performance.now() - t3 } : {} } };
3940
3989
  }
3941
3990
 
3942
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/load-worker.gen.ts
3991
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/load-worker.gen.ts
3943
3992
  var load_worker_gen_default = 'var bt=ArrayBuffer,W=Uint8Array,de=Uint16Array,yt=Int16Array;var be=Int32Array,Ee=function(e,t,n){if(W.prototype.slice)return W.prototype.slice.call(e,t,n);if(t==null||t<0)t=0;if(n==null||n>e.length)n=e.length;var r=new W(n-t);return r.set(e.subarray(t,n)),r},ce=function(e,t,n,r){if(W.prototype.fill)return W.prototype.fill.call(e,t,n,r);if(n==null||n<0)n=0;if(r==null||r>e.length)r=e.length;for(;n<r;++n)e[n]=t;return e},xt=function(e,t,n,r){if(W.prototype.copyWithin)return W.prototype.copyWithin.call(e,t,n,r);if(n==null||n<0)n=0;if(r==null||r>e.length)r=e.length;while(n<r)e[t++]=e[n++]};var wt=["invalid zstd data","window size too large (>2046MB)","invalid block type","FSE accuracy too high","match distance too far back","unexpected EOF"],K=function(e,t,n){var r=Error(t||wt[e]);if(r.code=e,Error.captureStackTrace)Error.captureStackTrace(r,K);if(!n)throw r;return r},je=function(e,t,n){var r=0,s=0;for(;r<n;++r)s|=e[t++]<<(r<<3);return s},At=function(e,t){return(e[t]|e[t+1]<<8|e[t+2]<<16|e[t+3]<<24)>>>0},Mt=function(e,t){var n=e[0]|e[1]<<8|e[2]<<16;if(n==3126568&&e[3]==253){var r=e[4],s=r>>5&1,o=r>>2&1,i=r&3,l=r>>6;if(r&8)K(0);var a=6-s,c=i==3?4:i,f=je(e,a,c);a+=c;var m=l?1<<l:s,g=je(e,a,m)+(l==1&&256),y=g;if(!s){var v=1<<10+(e[5]>>3);y=v+(v>>3)*(e[5]&7)}if(y>2145386496)K(1);var x=new W((t==1?g||y:t?0:y)+12);return x[0]=1,x[4]=4,x[8]=8,{b:a+m,y:0,l:0,d:f,w:t&&t!=1?t:x.subarray(12),e:y,o:new be(x.buffer,0,3),u:g,c:o,m:Math.min(131072,y)}}else if((n>>4|e[3]<<20)==25481893)return At(e,4)+8;K(0)},re=function(e){var t=0;for(;1<<t<=e;++t);return t-1},ue=function(e,t,n){var r=(t<<3)+4,s=(e[t]&15)+5;if(s>n)K(3);var o=1<<s,i=o,l=-1,a=-1,c=-1,f=o,m=new bt(512+(o<<2)),g=new yt(m,0,256),y=new de(m,0,256),v=new de(m,512,o),x=512+(o<<1),S=new W(m,x,o),h=new W(m,x+o);while(l<255&&i>0){var u=re(i+1),d=r>>3,p=(1<<u+1)-1,L=(e[d]|e[d+1]<<8|e[d+2]<<16)>>(r&7)&p,b=(1<<u)-1,T=p-i-1,E=L&b;if(E<T)r+=u,L=E;else if(r+=u+1,L>b)L-=T;if(g[++l]=--L,L==-1)i+=L,S[--f]=l;else i-=L;if(!L)do{var D=r>>3;a=(e[D]|e[D+1]<<8)>>(r&7)&3,r+=2,l+=a}while(a==3)}if(l>255||i)K(0);var C=0,U=(o>>1)+(o>>3)+3,X=o-1;for(var H=0;H<=l;++H){var M=g[H];if(M<1){y[H]=-M;continue}for(c=0;c<M;++c){S[C]=H;do C=C+U&X;while(C>=f)}}if(C)K(0);for(c=0;c<o;++c){var A=y[S[c]]++,k=h[c]=s-re(A);v[c]=(A<<k)-o}return[r+7>>3,{b:s,s:S,n:h,t:v}]},vt=function(e,t){var n=0,r=-1,s=new W(292),o=e[t],i=s.subarray(0,256),l=s.subarray(256,268),a=new de(s.buffer,268);if(o<128){var c=ue(e,t+1,6),f=c[0],m=c[1];t+=o;var g=f<<3,y=e[t];if(!y)K(0);var v=0,x=0,S=m.b,h=S,u=(++t<<3)-8+re(y);for(;;){if(u-=S,u<g)break;var d=u>>3;if(v+=(e[d]|e[d+1]<<8)>>(u&7)&(1<<S)-1,i[++r]=m.s[v],u-=h,u<g)break;d=u>>3,x+=(e[d]|e[d+1]<<8)>>(u&7)&(1<<h)-1,i[++r]=m.s[x],S=m.n[v],v=m.t[v],h=m.n[x],x=m.t[x]}if(++r>255)K(0)}else{r=o-127;for(;n<r;n+=2){var p=e[++t];i[n]=p>>4,i[n+1]=p&15}++t}var L=0;for(n=0;n<r;++n){var b=i[n];if(b>11)K(0);L+=b&&1<<b-1}var T=re(L)+1,E=1<<T,D=E-L;if(D&D-1)K(0);i[r++]=re(D)+1;for(n=0;n<r;++n){var b=i[n];++l[i[n]=b&&T+1-b]}var C=new W(E<<1),U=C.subarray(0,E),X=C.subarray(E);a[T]=0;for(n=T;n>0;--n){var H=a[n];ce(X,n,H,a[n-1]=H+l[n]*(1<<T-n))}if(a[0]!=E)K(0);for(n=0;n<r;++n){var M=i[n];if(M){var A=a[M];ce(U,n,A,a[M]=A+(1<<T-M))}}return[t,{n:X,b:T,s:U}]},Tt=ue(new W([81,16,99,140,49,198,24,99,12,33,196,24,99,102,102,134,70,146,4]),0,6)[1],Ct=ue(new W([33,20,196,24,99,140,33,132,16,66,8,33,132,16,66,8,33,68,68,68,68,68,68,68,68,36,9]),0,6)[1],Et=ue(new W([32,132,16,66,102,70,68,68,68,68,36,73,2]),0,5)[1],Ve=function(e,t){var n=e.length,r=new be(n);for(var s=0;s<n;++s)r[s]=t,t+=1<<e[s];return r},Se=new W(new be([0,0,0,0,16843009,50528770,134678020,202050057,269422093]).buffer,0,36),St=Ve(Se,0),Pe=new W(new be([0,0,0,0,0,0,0,0,16843009,50528770,117769220,185207048,252579084,16]).buffer,0,53),Pt=Ve(Pe,3),le=function(e,t,n){var r=e.length,s=t.length,o=e[r-1],i=(1<<n.b)-1,l=-n.b;if(!o)K(0);var a=0,c=n.b,f=(r<<3)-8+re(o)-c,m=-1;for(;f>l&&m<s;){var g=f>>3,y=(e[g]|e[g+1]<<8|e[g+2]<<16)>>(f&7);a=(a<<c|y)&i,t[++m]=n.s[a],f-=c=n.n[a]}if(f!=l||m+1!=s)K(0)},Lt=function(e,t,n){var r=6,s=t.length,o=s+3>>2,i=o<<1,l=o+i;le(e.subarray(r,r+=e[0]|e[1]<<8),t.subarray(0,o),n),le(e.subarray(r,r+=e[2]|e[3]<<8),t.subarray(o,i),n),le(e.subarray(r,r+=e[4]|e[5]<<8),t.subarray(i,l),n),le(e.subarray(r),t.subarray(l),n)},kt=function(e,t,n){var r,s=t.b,o=e[s],i=o>>1&3;t.l=o&1;var l=o>>3|e[s+1]<<5|e[s+2]<<13,a=(s+=3)+l;if(i==1){if(s>=e.length)return;if(t.b=s+1,n)return ce(n,e[s],t.y,t.y+=l),n;return ce(new W(l),e[s])}if(a>e.length)return;if(i==0){if(t.b=a,n)return n.set(e.subarray(s,a),t.y),t.y+=l,n;return Ee(e,s,a)}if(i==2){var c=e[s],f=c&3,m=c>>2&3,g=c>>4,y=0,v=0;if(f<2)if(m&1)g|=e[++s]<<4|(m&2&&e[++s]<<12);else g=c>>3;else if(v=m,m<2)g|=(e[++s]&63)<<4,y=e[s]>>6|e[++s]<<2;else if(m==2)g|=e[++s]<<4|(e[++s]&3)<<12,y=e[s]>>2|e[++s]<<6;else g|=e[++s]<<4|(e[++s]&63)<<12,y=e[s]>>6|e[++s]<<2|e[++s]<<10;++s;var x=n?n.subarray(t.y,t.y+t.m):new W(t.m),S=x.length-g;if(f==0)x.set(e.subarray(s,s+=g),S);else if(f==1)ce(x,e[s++],S);else{var h=t.h;if(f==2){var u=vt(e,s);y+=s-(s=u[0]),t.h=h=u[1]}else if(!h)K(0);(v?Lt:le)(e.subarray(s,s+=y),x.subarray(S),h)}var d=e[s++];if(d){if(d==255)d=(e[s++]|e[s++]<<8)+32512;else if(d>127)d=d-128<<8|e[s++];var p=e[s++];if(p&3)K(0);var L=[Ct,Et,Tt];for(var b=2;b>-1;--b){var T=p>>(b<<1)+2&3;if(T==1){var E=new W([0,0,e[s++]]);L[b]={s:E.subarray(2,3),n:E.subarray(0,1),t:new de(E.buffer,0,1),b:0}}else if(T==2)r=ue(e,s,9-(b&1)),s=r[0],L[b]=r[1];else if(T==3){if(!t.t)K(0);L[b]=t.t[b]}}var D=t.t=L,C=D[0],U=D[1],X=D[2],H=e[a-1];if(!H)K(0);var M=(a<<3)-8+re(H)-X.b,A=M>>3,k=0,V=(e[A]|e[A+1]<<8)>>(M&7)&(1<<X.b)-1;A=(M-=U.b)>>3;var B=(e[A]|e[A+1]<<8)>>(M&7)&(1<<U.b)-1;A=(M-=C.b)>>3;var N=(e[A]|e[A+1]<<8)>>(M&7)&(1<<C.b)-1;for(++d;--d;){var P=X.s[V],w=X.n[V],O=C.s[N],F=C.n[N],R=U.s[B],z=U.n[B];A=(M-=R)>>3;var j=1<<R,I=j+((e[A]|e[A+1]<<8|e[A+2]<<16|e[A+3]<<24)>>>(M&7)&j-1);A=(M-=Pe[O])>>3;var G=Pt[O]+((e[A]|e[A+1]<<8|e[A+2]<<16)>>(M&7)&(1<<Pe[O])-1);A=(M-=Se[P])>>3;var _=St[P]+((e[A]|e[A+1]<<8|e[A+2]<<16)>>(M&7)&(1<<Se[P])-1);if(A=(M-=w)>>3,V=X.t[V]+((e[A]|e[A+1]<<8)>>(M&7)&(1<<w)-1),A=(M-=F)>>3,N=C.t[N]+((e[A]|e[A+1]<<8)>>(M&7)&(1<<F)-1),A=(M-=z)>>3,B=U.t[B]+((e[A]|e[A+1]<<8)>>(M&7)&(1<<z)-1),I>3)t.o[2]=t.o[1],t.o[1]=t.o[0],t.o[0]=I-=3;else{var Y=I-(_!=0);if(Y){if(I=Y==3?t.o[0]-1:t.o[Y],Y>1)t.o[2]=t.o[1];t.o[1]=t.o[0],t.o[0]=I}else I=t.o[0]}for(var b=0;b<_;++b)x[k+b]=x[S+b];k+=_,S+=_;var q=k-I;if(q<0){var J=-q,ee=t.e+q;if(J>G)J=G;for(var b=0;b<J;++b)x[k+b]=t.w[ee+b];k+=J,G-=J,q=0}for(var b=0;b<G;++b)x[k+b]=x[q+b];k+=G}if(k!=S)while(S<x.length)x[k++]=x[S++];else k=x.length;if(n)t.y+=k;else x=Ee(x,0,k)}else if(n){if(t.y+=g,S)for(var b=0;b<g;++b)x[b]=x[S+b]}else if(S)x=Ee(x,S);return t.b=a,x}K(2)},Bt=function(e,t){if(e.length==1)return e[0];var n=new W(t);for(var r=0,s=0;r<e.length;++r){var o=e[r];n.set(o,s),s+=o.length}return n};function ye(e,t){var n=[],r=+!t,s=0,o=0;for(;e.length;){var i=Mt(e,r||t);if(typeof i=="object"){if(r){if(t=null,i.w.length==i.u)n.push(t=i.w),o+=i.u}else n.push(t),i.e=0;for(;!i.l;){var l=kt(e,i,t);if(!l)K(5);if(t)i.e=i.y;else n.push(l),o+=l.length,xt(i.w,0,l.length),i.w.set(l,i.w.length-l.length)}s=i.b+i.c*4}else s=i;e=e.subarray(s)}return Bt(n,o)}async function It(e){let t=e instanceof Uint8Array?e:new Uint8Array(e),n=(r)=>r.buffer.slice(r.byteOffset,r.byteOffset+r.byteLength);if(t[0]===40&&t[1]===181&&t[2]===47&&t[3]===253)return n(ye(t));if(t[0]===31&&t[1]===139)return new Response(new Blob([t]).stream().pipeThrough(new DecompressionStream("gzip"))).arrayBuffer();return n(t)}async function He(e){return new TextDecoder().decode(await It(e))}var Ge=(e)=>decodeURIComponent(e.split(/[?#]/)[0].split("/").pop()||"main.ldr").replace(/\\.(zst|gz)$/i,"")||"main.ldr";function xe(e){let t=new DataView(e).getUint32(0,!0),n=JSON.parse(new TextDecoder().decode(new Uint8Array(e,4,t))),r=4+t;return{header:n,arr:(o)=>o&&(o[2]==="f32"?new Float32Array(e,r+o[0],o[1]):o[2]==="u16"?new Uint16Array(e,r+o[0],o[1]):o[2]==="u8"?new Uint8Array(e,r+o[0],o[1]):new Uint32Array(e,r+o[0],o[1])),bytes:e.byteLength,buffer:e}}var Z=(e)=>e.trim().toLowerCase().replace(/\\\\/g,"/").replace(/\\/+/g,"/");function Le(e){if(/^0x/i.test(e))return parseInt(e,16);return+e}var _e=/^~?\\s*moved\\s+to\\s+(.+)$/i,Ft=new Set(["FILE","NOFILE","!DATA","!:","STEP","BFC","ROTSTEP","BUFEXCHG","!TEXMAP"]),Qe=new Set(["part","subpart","primitive","8_primitive","48_primitive","shortcut","helper"]),Rt=new Set(["model","submodel","configuration"]),Dt=/^(?:!?LDRAW_ORG|Unofficial|Official\\s+LCAD)\\s+(\\S+)/i;function We(e){let t=Dt.exec(e);if(!t)return;let n=t[1].toLowerCase().replace(/^unofficial_/,"");return Qe.has(n)||Rt.has(n)?n:void 0}function Ut(e){let t=[],n=0;while(n<e.length){while(e[n]===" "||e[n]==="\\t")n++;if(n>=e.length)break;if(e[n]===\'"\'){let r="";n++;while(n<e.length&&e[n]!==\'"\'){if(e[n]==="\\\\"&&n+1<e.length)n++;r+=e[n++]}n++,t.push(r)}else{let r=n;while(r<e.length&&e[r]!==" "&&e[r]!=="\\t")r++;t.push(e.slice(n,r)),n=r}}return t}function zt(e,t=!1){let n=/^\\s*(\\S+)\\s+(.*)$/.exec(e);if(!n)return null;let r=n[1].toUpperCase(),s=r==="PLANAR"?9:r==="CYLINDRICAL"?10:r==="SPHERICAL"?11:0;if(!s)return null;let o=n[2].trim().split(/\\s+/);if(o.length<s+1)return null;let i=o.slice(0,s).map(Number);if(!i.every(Number.isFinite))return null;let l=n[2].trim();for(let m=0;m<s;m++)l=l.replace(/^\\S+\\s*/,"");let a=Ut(l);if(!a.length)return null;let c={method:r,p1:i.slice(0,3),p2:i.slice(3,6),p3:i.slice(6,9),texture:a[0]};if(s>=10)c.a=i[9];if(s===11)c.b=i[10];let f=a.findIndex((m,g)=>g>0&&m.toUpperCase()==="GLOSSMAP");if(f>0&&a[f+1])c.glossmap=a[f+1];if(t)c.next=!0;return c}var Ot=(e)=>{let t=e.replace(/[^A-Za-z0-9+/]/g,""),n=t+"=".repeat((4-t.length%4)%4),r=atob(n),s=new Uint8Array(r.length);for(let o=0;o<r.length;o++)s[o]=r.charCodeAt(o);return s};function Nt(e){let t=e.length,n=1,r=(c)=>c===32||c===9,s=()=>{while(n<t&&r(e.charCodeAt(n)))n++;let c=n;while(n<t&&!r(e.charCodeAt(n)))n++;return c<n?e.slice(c,n):null},o=s();if(o===null)return null;let i=Array(12);for(let c=0;c<12;c++){let f=s();if(f===null)return null;i[c]=+f}while(n<t&&r(e.charCodeAt(n)))n++;if(n>=t)return null;let l=e.slice(n),a=/[ \\t]{2,}|\\t/.test(l)?l.split(/\\s+/).join(" "):l;return{t:1,color:Le(o),m:i,file:Z(a)}}function Ke(e){let t=+e[0];if(!(t>=1&&t<=5)||e.length<2)return null;let n=Le(e[1]),r=e.slice(2).map(Number);if(t===1)return{t:1,color:n,m:r.slice(0,12),file:Z(e.slice(14).join(" "))};if(t===2)return{t:2,color:n,p:r.slice(0,6)};if(t===3)return{t:3,color:n,p:r.slice(0,9)};if(t===4)return{t:4,color:n,p:r.slice(0,12)};return{t:5,color:n,p:r.slice(0,12)}}function et(e,t="main.ldr",n={}){let r=!!n.geometryText,s=new Map,o=null,i="",l,a=null,c=[],f=null,m=()=>{if(!a)return;let u=Ot(a.chunks.join("")),d=u[0]===137&&u[1]===80&&u[2]===78&&u[3]===71;(l??=new Map).set(Z(a.name),{name:a.name,bytes:u,...d?{mime:"image/png"}:{}}),a=null},g=!0,y=(u)=>{if(m(),o={name:Z(u),cmds:[]},s.set(o.name,o),!i)i=o.name;c=[],f=null,g=!0},v=!1,x=!1,S=()=>{if(x)return;x=!0,s.clear(),o=null,i=""},h=(u,d)=>{if(f){u.tex=f,f=null;return}let p=c[c.length-1];if(!p)return;if(p.fallback){if(!d)u.fallback=!0}else u.tex=p.map};for(let u of e.split(`\n`)){let d=u.trim();if(!d)continue;if(r&&d.charCodeAt(0)>=50&&d.charCodeAt(0)<=53&&(d.charCodeAt(1)===32||d.charCodeAt(1)===9)){if(a||v)continue;if(!o)y(t);let T=d.slice(2).trimStart(),E=T.search(/\\s/),D={t:d.charCodeAt(0)-48,color:Le(E<0?T:T.slice(0,E)),p:[],src:d};h(D,!1),o.cmds.push(D),g=!1;continue}if(d.charCodeAt(0)===48&&(d.charCodeAt(1)===32||d.charCodeAt(1)===9)){let T=2;while(d.charCodeAt(T)===32||d.charCodeAt(T)===9)T++;let E=T;while(E<d.length&&d.charCodeAt(E)!==32&&d.charCodeAt(E)!==9)E++;let D=d.slice(T,E);if(!Ft.has(D)){if(v||a)continue;if(!o)y(t);let C=d.slice(T),U={t:0,text:C};if(D.charCodeAt(0)===33)U.meta=D;if(f=null,g&&!o.org){let H=We(C);if(H)o.org=H}let X=C.match(_e);if(X&&!o.movedTo){let H=X[1].trim().replace(/\\\\/g,"/");if(!/\\.(dat|ldr|mpd)$/i.test(H))H+=".dat";o.movedTo=Z(H)}o.cmds.push(U);continue}}if(d.charCodeAt(0)===49&&(d.charCodeAt(1)===32||d.charCodeAt(1)===9)&&!a&&!v){let T=Nt(d);if(T){if(!o)y(t);h(T,!1),o.cmds.push(T),g=!1;continue}}let p=d.split(/\\s+/);if(+p[0]===0){let T=d.slice(1).trim();if(p[1]==="FILE"){S(),v=!1,y(T.slice(5));continue}if(p[1]==="NOFILE"){m(),o=null,v=!0;continue}if(p[1]==="!DATA"){S(),m(),o=null,v=!1,a={name:T.slice(5).trim(),chunks:[]};continue}if(v)continue;if(a){if(p[1]==="!:")a.chunks.push(p.slice(2).join(""));continue}if(!o)y(t);let E={t:0,text:T};if(p[1]==="STEP")E.step=!0,c=[],f=null;else if(p[1]==="BFC")E.bfc=p.slice(2).join(" ");else if(p[1]==="ROTSTEP")if(E.meta="ROTSTEP",E.step=!0,c=[],f=null,(p[2]??"").toUpperCase()==="END")E.rotstep={x:0,y:0,z:0,mode:"END"};else{let C=(p[5]??"REL").toUpperCase();E.rotstep={x:+p[2]||0,y:+p[3]||0,z:+p[4]||0,mode:["REL","ABS","ADD"].includes(C)?C:"REL"}}else if(p[1]==="BUFEXCHG"){E.meta="BUFEXCHG";let C=(p[2]??"").toUpperCase();E.bufexchg={op:C==="RETRIEVE"?"RETRIEVE":"STORE",buf:p[3]??"A"}}else if(p[1]?.startsWith("!"))E.meta=p[1];if(g&&!o.org){let C=We(T);if(C)o.org=C}if(p[1]==="!TEXMAP"){let C=(p[2]??"").toUpperCase();if(f=null,C==="START"||C==="NEXT"){let U=zt(T.replace(/^!TEXMAP\\s+\\S+/i,""),C==="NEXT");if(E.texmap={op:C,...U?{map:U}:{}},U)if((o.texmaps??=[]).push(U),C==="START")c.push({map:U,fallback:!1});else f=U}else if(C==="FALLBACK"){E.texmap={op:C};let U=c[c.length-1];if(U)U.fallback=!0}else if(C==="END")E.texmap={op:C},c.pop()}else if(p[1]==="!:"){f=null;let C=Ke(p.slice(2));if(C)h(C,!0),E.geom=C}else f=null;let D=T.match(_e);if(D&&!o.movedTo){let C=D[1].trim().replace(/\\\\/g,"/");if(!/\\.(dat|ldr|mpd)$/i.test(C))C+=".dat";o.movedTo=Z(C)}o.cmds.push(E);continue}if(a||v)continue;if(!o)y(t);let b=Ke(p);if(!b)continue;h(b,!1),o.cmds.push(b),g=!1}return m(),l?{main:i,files:s,data:l}:{main:i,files:s}}var cn=Math.PI/180;function tt(e,t){let n=(r)=>[t[3]*r[0]+t[4]*r[1]+t[5]*r[2]+t[0],t[6]*r[0]+t[7]*r[1]+t[8]*r[2]+t[1],t[9]*r[0]+t[10]*r[1]+t[11]*r[2]+t[2]];return{...e,p1:n(e.p1),p2:n(e.p2),p3:n(e.p3)}}function nt(e,t){let[n,r,s,o,i,l,a,c,f,m,g,y]=e,[v,x,S,h,u,d,p,L,b,T,E,D]=t;return[o*v+i*x+l*S+n,a*v+c*x+f*S+r,m*v+g*x+y*S+s,o*h+i*p+l*T,o*u+i*L+l*E,o*d+i*b+l*D,a*h+c*p+f*T,a*u+c*L+f*E,a*d+c*b+f*D,m*h+g*p+y*T,m*u+g*L+y*E,m*d+g*b+y*D]}var ke=[0,0,0,1,0,0,0,1,0,0,0,1];var Xe=new WeakMap,$t=/^(s|8|48)\\//;function Be(e,t){let n=e.files.get(Z(t));if(!n)return"part";let r=Xe.get(n);if(r)return r;if(n.org)r=Qe.has(n.org)?"part":"model";else if(n.name===e.main)r="model";else r=n.cmds.some((s)=>s.t>=2||s.t===0&&!!s.geom||s.t===1&&$t.test(s.file))?"part":"model";return Xe.set(n,r),r}var Ze=new WeakMap;function rt(e){let t=Ze.get(e);if(t)return t;t=[];let n=0;for(let r of e.cmds){if(r.t===0&&r.step){n++;continue}if((r.t>=2||r.t===0&&r.geom&&r.geom.t>=2)&&t[t.length-1]!==n)t.push(n)}return Ze.set(e,t),t}var Ye=(e)=>e>16777215?"0x"+e.toString(16).toUpperCase():String(e),jt=(e)=>e.t===0?"0 "+e.text:e.t===1?`1 ${Ye(e.color)} ${e.m.join(" ")} ${e.file}`:e.src?e.src:`${e.t} ${Ye(e.color)} ${e.p.join(" ")}`,Ie=(e,t)=>`${Z(e)}#${t}`;function Vt(e,t){let n=[],r=0,s=(o)=>!!o&&o.t===0&&o.texmap?.op==="NEXT";for(let o of e.cmds){if(o.t===0){if(o.step){r++;continue}if(o.bfc!==void 0){n.push(o);continue}if(r!==t)continue;if(o.texmap)n.push(o);else if(o.geom){if(o.geom.t>=2)n.push(o);else if(s(n[n.length-1]))n.pop()}continue}if(r!==t)continue;if(o.t===1){if(s(n[n.length-1]))n.pop();continue}n.push(o)}return{name:Ie(e.name,t),cmds:n}}function st(e,t){return[`0 ldr: own geometry of ${e.name}, step ${t}`,...Vt(e,t).cmds.map(jt)].join(`\n`)+`\n`}function Ht(e){let t=2166136261;for(let n=0;n<e.length;n++)t^=e.charCodeAt(n),t=Math.imul(t,16777619);return(t>>>0).toString(16).padStart(8,"0")}function ot(e){let[t,n,r,s,o,i,l,a,c,f,m,g]=e,y=a*g-c*m,v=-(l*g-c*f),x=l*m-a*f,S=s*y+o*v+i*x;if(!Number.isFinite(S)||Math.abs(S)<0.000000000001)return null;let h=1/S,u=[y*h,-(o*g-i*m)*h,(o*c-i*a)*h,v*h,(s*g-i*f)*h,-(s*c-i*l)*h,x*h,-(s*m-o*f)*h,(s*a-o*l)*h];return[-(u[0]*t+u[1]*n+u[2]*r),-(u[3]*t+u[4]*n+u[5]*r),-(u[6]*t+u[7]*n+u[8]*r),...u]}var Gt=(e)=>String(+e.toFixed(6)||0),Je=(e)=>\'"\'+e.replace(/\\\\/g,"\\\\\\\\").replace(/"/g,"\\\\\\"")+\'"\';function it(e,t){let n=[...t.p1,...t.p2,...t.p3,...t.method!=="PLANAR"?[t.a??360]:[],...t.method==="SPHERICAL"?[t.b??180]:[]].map(Gt).join(" "),r=`0 ldr: ${e} with a model-level !TEXMAP\n0 BFC CERTIFY CCW\n0 !TEXMAP START ${t.method} ${n} ${Je(t.texture)}${t.glossmap?" GLOSSMAP "+Je(t.glossmap):""}\n1 16 0 0 0 1 0 0 0 1 0 0 0 1 ${Z(e)}\n0 !TEXMAP END\n`;return{name:`${Z(e)}#tex-${Ht(r)}`,source:r}}var _t=(e,t)=>{let n=[,,,,,,,,,];for(let r=0;r<3;r++)for(let s=0;s<3;s++)n[r*3+s]=e[r*3]*t[s]+e[r*3+1]*t[3+s]+e[r*3+2]*t[6+s];return n};function Wt(e,t,n){let r=Math.PI/180,s=Math.sin(e*r),o=Math.cos(e*r),i=Math.sin(t*r),l=Math.cos(t*r),a=Math.sin(n*r),c=Math.cos(n*r);return[l*c,-l*a,i,o*a+s*i*c,o*c-s*i*a,-s*l,s*a-o*i*c,s*c+o*i*a,o*l]}var qe=new WeakMap;function at(e){let t=qe.get(e);if(t)return t;let n=[],r=null;for(let s of e.cmds){if(s.t!==0||!s.step)continue;let o=s.rotstep;if(o)if(o.mode==="END")r=null;else{let i=Wt(o.x,o.y,o.z);r=o.mode==="ABS"?{abs:!0,m:i,rotstep:o}:o.mode==="ADD"&&r?{abs:r.abs,m:_t(r.m,i),rotstep:o}:{abs:!1,m:i,rotstep:o}}n.push(r)}return n.push(r),qe.set(e,n),n}var lt=999999,ct=(e)=>e.map((t)=>String(t).padStart(6,"0")).join(".")+"~";function Kt(e,t={}){let n=[],r=t.maxDepth??64,s=t.looseGeometry!==!1,o=!!t.textures,i=!!t.callouts,l=new Map,a=0,c=(m,g)=>{let y=m.name+"#"+g,v=l.get(y);if(!v){let x;v=Object.assign(()=>x??=st(m,g),{file:m,step:g}),l.set(y,v)}return v},f=(m,g,y,v,x,S,h,u,d=null,p=[],L=[])=>{let b=m.files.get(g);if(!b||h>r)return;let T=0,E=h>0?++a:0,D=i&&E?[...p,E]:p,C=i&&E?[...L,y]:L,U=i&&!d?new Map:null,X=-1,H=0,M=(P,w,O,F,R,z)=>{if(X!==T)X=T,H=0;let j=U?.get(T+"|"+w);if(j)return j.n++,f(P,P===m?w:P.main,O,F,x+"/"+w,[...R,lt],h+1,z,[...R,lt],D,C);let I=[...R,H++],G=n.length;f(P,P===m?w:P.main,O,F,x+"/"+w,I,h+1,z,d,D,C),U?.set(T+"|"+w,{n:1,from:G,to:n.length})},A=at(b),k=s?rt(b):[],V=0,B=(P,w,O,F,R,z,j)=>{if(d)F=d;let I={part:P,color:w,m:O,step:F[0],steps:F,localStep:R,path:x};if(E){if(I.sub=E,i)I.subPath=D,I.subMats=C}if(z)I.embedded=!0;if(j){let _=it(P,j);I.texPart=_.name,I.texSource=_.source}let G=A[R];if(G)I.view=G;return n.push(I),I},N=(P)=>{while(V<k.length&&k[V]<=P){let w=k[V++];B(Ie(b.name,w),v,y,[...S,w],w,!1,null).source=c(b,w)}};for(let P of b.cmds){if(P.t===0&&P.step){T++;continue}let w=P;if(w.t>=2||w.t===0&&w.geom&&w.geom.t>=2){N(T);continue}if(w.t===0)if(o&&w.geom?.t===1)w=w.geom;else continue;if(w.t!==1)continue;if(o&&w.fallback)continue;let O=w.color===16?v:w.color,F=nt(y,w.m),R=[...S,T],z=o?w.tex??u:null,j=z?ot(w.m):null,I=z&&j?tt(z,j):null;if(m.files.has(w.file)){if(Be(m,w.file)==="model"){M(m,w.file,F,O,R,I);continue}B(w.file,O,F,R,T,!0,I);continue}let G=t.resolveModel?.(w.file);if(G){M(G,w.file,F,O,R,I);continue}B(w.file,O,F,R,T,!1,I)}if(N(1/0),U){for(let P of U.values())if(P.n>1)for(let w=P.from;w<P.to;w++)n[w].mult??=P.n}};if(Be(e,e.main)==="part"){if(e.files.get(e.main))n.push({part:e.main,color:16,m:[...ke],step:0,steps:[0],localStep:0,path:e.main,embedded:!0});return n}return f(e,e.main,ke,16,e.main,[],0,null),n}function ut(e,t="main.ldr",n){return Kt(et(e,t,{geometryText:!0}),n)}function Xt(e,t){let n=new Map,r=null,s=!1;for(let o of e.split(/\\r?\\n/)){let i=/^\\s*0\\s+FILE\\s+(.+)$/i.exec(o);if(i){if(!s)n.clear();s=!0,r=[],n.set(Z(i[1]),r);continue}if(/^\\s*0\\s+(NOFILE|!DATA)(\\s|$)/i.test(o)){if(!s)n.clear();s=!0,r=null;continue}if(!r){if(s)continue;r=[],n.set(Z(t),r)}let l=o.trim().replace(/\\s+/g," ");if(l)r.push(l)}return n}var Zt=(e)=>{if(!e.startsWith("1 "))return null;let t=e.split(" ");return t.length>=15?Z(t.slice(14).join(" ")):null};function Yt(e){let t=2166136261,n=1523705862;for(let r=0;r<e.length;r++){let s=e.charCodeAt(r);t=Math.imul(t^s,16777619),n=Math.imul(n^s,16777629)^n>>>15}return(t>>>0).toString(16).padStart(8,"0")+(n>>>0).toString(16).padStart(8,"0")}function ft(e,t,n="main.ldr"){let r=Xt(e,n),s=new Map;for(let o of t){let i=Z(o);if(!r.has(i)||s.has(i))continue;let l=new Set([i]),a=[i];while(a.length)for(let f of r.get(a.pop())){let m=Zt(f);if(m&&r.has(m)&&!l.has(m))l.add(m),a.push(m)}let c=[i,...[...l].filter((f)=>f!==i).sort()].map((f)=>"0 FILE "+(f===i?"":f)+`\n`+r.get(f).join(`\n`)).join(`\n`);s.set(i,`emb/${Yt(c)}.dat`)}return s}var pt=(e,t=0)=>Math.abs(e[t+7]-1)<0.001&&Math.abs(e[t+4])<0.001&&Math.abs(e[t+10])<0.001,mt=new WeakMap;function Fe(e,t){let n=mt.get(e);if(!n)mt.set(e,n=new Map);let r=n.get(t);if(r!==void 0)return r;let s=e.parts[t];if(!s)return n.set(t,null),null;let o=s.studs.length,i=new Float64Array(o*12),l=[],a=[];s.studs.forEach((g,y)=>{l.push(g[0]),a.push(g[1]);for(let v=0;v<12;v++)i[y*12+v]=g[2+v]});let c=null,f=0,m=0;if(s.mask){f=s.mask[0],m=s.mask[1];let g=s.mask[2];c=new Uint8Array(g.length);for(let y=0;y<g.length;y++)c[y]=g.charCodeAt(y)===49?1:0}return n.set(t,r={nStuds:o,studM:i,kind:l,color:a,mask:c,nx:f,nz:m}),r}var ie=1073741824;class Re{a;b;c;vals;used;mask;size=0;constructor(e=1024){let t=1024;while(t<e*2)t*=2;this.a=new Int32Array(t),this.b=new Int32Array(t),this.c=new Int32Array(t),this.vals=new Int32Array(t),this.used=new Uint8Array(t),this.mask=t-1}slot(e,t,n){let r=Math.imul(e,2654435761)^Math.imul(t,2246822507)^Math.imul(n,3266489909);return r^=r>>>15,r=Math.imul(r,739982445),r^=r>>>12,r&this.mask}get(e,t,n){let r=this.slot(e,t,n),s=this.used,o=this.a,i=this.b,l=this.c;for(;;){if(!s[r])return-1;if(o[r]===e&&i[r]===t&&l[r]===n)return this.vals[r];r=r+1&this.mask}}put(e,t,n,r){if((this.size+1)*2>this.used.length)this.grow();let s=this.slot(e,t,n),o=this.used,i=this.a,l=this.b,a=this.c;for(;;){if(!o[s])return o[s]=1,i[s]=e,l[s]=t,a[s]=n,this.vals[s]=r,this.size++,-1;if(i[s]===e&&l[s]===t&&a[s]===n){let c=this.vals[s];return this.vals[s]=r,c}s=s+1&this.mask}}grow(){let{a:e,b:t,c:n,vals:r,used:s}=this,o=s.length*2;this.a=new Int32Array(o),this.b=new Int32Array(o),this.c=new Int32Array(o),this.vals=new Int32Array(o),this.used=new Uint8Array(o),this.mask=o-1,this.size=0;for(let i=0;i<s.length;i++)if(s[i])this.put(e[i],t[i],n[i],r[i])}}var we=(e,t,n)=>e>-ie&&e<ie&&t>-ie&&t<ie&&n>-ie&&n<ie,fe=2147483647;function Jt(e,t,n){let s=[],o=[],i=[],l=[],a=[],c=[],f=[],m=[],g=new Re(e.length*2),y=[],v=[];for(let M=0;M<e.length;M++){let A=e[M],k=A.m,V=t.parts[A.part];if(!V||!V.mask||!pt(k))continue;let B=V.bbox,N=1/0,P=-1/0,w=1/0,O=-1/0;for(let z=0;z<2;z++)for(let j=0;j<2;j++){let I=z?B[3]:B[0],G=j?B[5]:B[2],_=k[0]+k[3]*I+k[5]*G,Y=k[2]+k[9]*I+k[11]*G;if(_<N)N=_;if(_>P)P=_;if(Y<w)w=Y;if(Y>O)O=Y}let F=a.length;s.push(N),o.push(P),i.push(w),l.push(O),a.push(M),c.push(Fe(t,A.part)),f.push(B[0]),m.push(B[2]);let R=Math.round((k[1]+B[4])*2);for(let z=Math.floor(N/40);z<=Math.floor(P/40);z++)for(let j=Math.floor(w/40);j<=Math.floor(O/40);j++){if(!we(R,z,j))continue;let I=v.length;v.push(F),y.push(g.put(R,z,j,I))}}let x=[0,5,-5,5,-5],S=[0,5,5,-5,-5],h=NaN,u=NaN,d=NaN,p=-1,L=(M,A,k,V)=>{let B=Math.round(A*2),N=Math.floor(M/40),P=Math.floor(k/40);if(!we(B,N,P))return fe;let w=fe;if(B!==h||N!==u||P!==d)h=B,u=N,d=P,p=g.get(B,N,P);for(let O=p;O>=0;O=y[O]){let F=v[O];if(a[F]===V||!(M-6>=s[F]-0.5&&M+6<=o[F]+0.5&&k-6>=i[F]-0.5&&k+6<=l[F]+0.5))continue;let R=e[a[F]].m,z=c[F],j=f[F],I=m[F],G=M-R[0],_=A-R[1],Y=k-R[2],q=R[3]*G+R[6]*_+R[9]*Y,J=R[5]*G+R[8]*_+R[11]*Y,{nx:ee,nz:se,mask:ae}=z,oe=!0;for(let Q=0;Q<5;Q++){let te=Math.floor((q+x[Q]-j)/20),ne=Math.floor((J+S[Q]-I)/20);if(te<0||ne<0||te>=ee||ne>=se||ae[te*se+ne]!==1){oe=!1;break}}if(oe){if(!n)return 0;let Q=n[a[F]];if(Q<w)w=Q}}return w},b=0;for(let M of e){let A=Fe(t,M.part);if(A)b+=A.nStuds}let T=new Uint8Array(b),E=new Uint8Array(b),D=new Re(b),C=0,U=n?new Int32Array(b).fill(fe):void 0;for(let M=0,A=0;M<e.length;M++){let k=e[M],V=Fe(t,k.part);if(!V)continue;let B=k.m,N=V.studM,P=B[0],w=B[1],O=B[2],F=B[3],R=B[4],z=B[5],j=B[6],I=B[7],G=B[8],_=B[9],Y=B[10],q=B[11];for(let J=0;J<V.nStuds;J++,A++){let ee=J*12,se=N[ee+4],ae=N[ee+7],oe=N[ee+10];if(!(Math.abs(j*se+I*ae+G*oe-1)<0.001&&Math.abs(F*se+R*ae+z*oe)<0.001&&Math.abs(_*se+Y*ae+q*oe)<0.001))continue;E[A]=1;let Q=N[ee],te=N[ee+1],ne=N[ee+2],De=P+F*Q+R*te+z*ne,Ue=w+j*Q+I*te+G*ne,ze=O+_*Q+Y*te+q*ne,Oe=L(De,Ue,ze,M);if(Oe!==fe){if(T[A]=1,C++,U)U[A]=Oe}let me=Math.round(De),he=Math.round(Ue),ge=Math.round(ze);if(!we(me,he,ge))continue;if(!n){D.put(me,he,ge,1);continue}let Ne=n[M],$e=D.get(me,he,ge);if($e<0||Ne<$e)D.put(me,he,ge,Ne)}}let X=new Uint8Array(e.length),H=n?new Int32Array(e.length).fill(fe):void 0;for(let M=0;M<e.length;M++){let A=e[M],k=t.parts[A.part];if(!k||!k.under||!pt(A.m))continue;let V=k.bbox,B=Math.round((V[3]-V[0])/20),N=Math.round((V[5]-V[2])/20);if(!B||!N||B*N>4096)continue;let P=A.m,w=!0,O=0;for(let F=0;F<B&&w;F++)for(let R=0;R<N;R++){let z=V[0]+10+20*F,j=V[2]+10+20*R,I=P[0]+P[3]*z+P[5]*j,G=P[2]+P[9]*z+P[11]*j,_=Math.round(I),Y=Math.round(P[1]+V[4]),q=Math.round(G),J=we(_,Y,q)?D.get(_,Y,q):-1;if(J<0){w=!1;break}if(J>O)O=J}if(w){if(X[M]=1,H)H[M]=O}}return{nStuds:b,studHidden:T,studUp:E,underHidden:X,studsHidden:C,studHideStep:U,underHideStep:H}}function qt(e,t,n){let r=(a)=>n&&a.texPart&&t.parts[a.texPart]?a.texPart:a.part,s=e.map((a)=>({part:r(a),color:String(a.color),m:a.m,steps:a.steps})),o=s.map((a)=>ct(a.steps)),i=[...new Set(o)].sort(),l=new Map(i.map((a,c)=>[a,c]));return{all:s,stepOf:o.map((a)=>l.get(a)),nSteps:i.length}}function Ae(e,t,n=!0){let r=qt(e,t,n);return Jt(r.all,t,r.nSteps>1?r.stepOf:void 0)}var Ce="https://ldr-demo.vercel.app/cdn/v1/",ve=new Map,Me=new Map;function gt(e=Ce,t=fetch){let n=ve.get(e);if(!n)n=t(e+"parts.json").then((r)=>{if(!r.ok)throw Error(`ldr: parts CDN index ${r.status} (${e}parts.json)`);return r.json()}),n.catch(()=>ve.delete(e)),ve.set(e,n);return n}var Qt=(e)=>Array.isArray(e)&&e.length===3&&typeof e[0]==="number"&&typeof e[1]==="number"&&(e[2]==="f32"||e[2]==="u16"||e[2]==="u8"||e[2]==="u32"),Te=(e,t)=>Qt(e)?[e[0]+t,e[1],e[2]]:Array.isArray(e)?e.map((n)=>Te(n,t)):e&&typeof e==="object"?Object.fromEntries(Object.entries(e).map(([n,r])=>[n,Te(r,t)])):e;function en(e,t={}){let n=[],r=0,s={},o={};for(let g of e){let y=g.buffer;if(!y)throw Error("ldr: mergePacks needs packs from decodePack/loadPack");let v=new DataView(y).getUint32(0,!0),x=new Uint8Array(y,4+v),S=r;n.push(x),r+=x.byteLength;let h=(4-r%4)%4;if(h)n.push(new Uint8Array(h)),r+=h;for(let[u,d]of Object.entries(g.header.parts??{}))if(!(u in s))s[u]=Te(d,S);for(let[u,d]of Object.entries(g.header.studKinds??{}))if(!(u in o))o[u]=Te(d,S)}let i=e[0]?.header??{version:1,weld:!0},l=new TextEncoder().encode(JSON.stringify({...i,parts:s,studKinds:o,...t})),a=(4-(4+l.length)%4)%4,c=4+l.length+a,f=new Uint8Array(c+r);new DataView(f.buffer).setUint32(0,l.length+a,!0),f.set(l,4),f.fill(32,4+l.length,c);let m=c;for(let g of n)f.set(g,m),m+=g.byteLength;return xe(f.buffer)}async function tn(e,t={}){e=[...e];try{return await ht(e,t)}catch(n){let r=t.cdn??Ce,s=t.fetch??fetch;ve.delete(r);let o=(i,l)=>s(i,{...l,cache:"reload"});try{return await ht(e,{...t,fetch:o})}catch{throw n}}}async function ht(e,t){let n=t.cdn??Ce,r=t.fetch??fetch,s=t.gap??16384,o=await gt(n,r),i=[...new Set([...e].map(Z))],l=[],a=[];for(let h of i){if(Me.has(n+h))continue;let u=o.parts[h];if(u)a.push({name:h,off:u[0],len:u[1],file:u[2]??0});else l.push(h)}a.sort((h,u)=>h.file-u.file||h.off-u.off);let c=[];for(let h of a){let u=c[c.length-1];if(u&&u.file===h.file&&h.off-u.end<=s)u.end=Math.max(u.end,h.off+h.len),u.items.push(h);else c.push({file:h.file,start:h.off,end:h.off+h.len,items:[h]})}let f=0,m=0,g=0,y=new Map,v=async()=>{while(g<c.length){let h=c[g++],u=y.get(h.file);if(u)u=u.subarray(h.start,h.end);else{let d=await r(new URL(o.archives[h.file],n).href,{headers:{Range:`bytes=${h.start}-${h.end-1}`,"Accept-Encoding":"identity"}});if(!d.ok)throw Error(`ldr: parts CDN ${d.status}`);if(m++,u=new Uint8Array(await d.arrayBuffer()),f+=u.byteLength,d.status===200)y.set(h.file,u),u=u.subarray(h.start,h.end)}for(let d of h.items){let p=ye(u.subarray(d.off-h.start,d.off-h.start+d.len)),L=p.buffer.slice(p.byteOffset,p.byteOffset+p.byteLength);Me.set(n+d.name,xe(L))}}};await Promise.all(Array.from({length:Math.min(t.concurrency??24,c.length)},v));let x=i.filter((h)=>Me.has(n+h)).map((h)=>Me.get(n+h));return{pack:x.length?en(x,{colors:o.colors}):xe(nn(o.colors)),missing:l,requests:m,bytes:f}}function nn(e){let t=new TextEncoder().encode(JSON.stringify({version:1,weld:!0,parts:{},studKinds:{},colors:e})),n=(4-(4+t.length)%4)%4,r=new Uint8Array(4+t.length+n).fill(32);return new DataView(r.buffer).setUint32(0,t.length+n,!0),r.set(t,4),r.buffer}function rn(e,t){let n=t.header.parts;if(!n)return 0;let r=0;for(let s of e){let o=n[s.part]?.body?.tris;if(o)r+=o}return r}function sn(e,t,n){let r=0,s=0,o=!1,i=new Set;for(let l of e){let a=l.embedded?t.get(l.part):Z(l.part),c=a?n.parts[a]:void 0;if(!c)continue;if(c[3]!=null)o=!0,r+=c[3];if(!i.has(a))i.add(a),s+=c[1]}return{bodyTris:r,packBytes:s,known:o}}class pe extends Error{code="ldr-budget";bodyTris;packBytes;maxBodyTris;maxPackBytes;constructor(e){let t=[e.maxBodyTris!=null&&e.bodyTris>e.maxBodyTris?`${e.bodyTris} triangles (limit ${e.maxBodyTris})`:"",e.maxPackBytes!=null&&e.packBytes>e.maxPackBytes?`${(e.packBytes/1e6).toFixed(1)} MB parts pack (limit ${(e.maxPackBytes/1e6).toFixed(0)} MB)`:""].filter(Boolean).join(", ");super(`ldr: model exceeds device budget: ${t}`);this.name="BudgetError",this.bodyTris=e.bodyTris,this.packBytes=e.packBytes,this.maxBodyTris=e.maxBodyTris,this.maxPackBytes=e.maxPackBytes}}async function dt(e,t={}){let n=performance.now(),r=t.fetch??fetch,s=gt(t.cdn??Ce,r);s.catch(()=>{});let o=t.text?e:await r(e).then(async(p)=>{if(!p.ok)throw Error(`ldr: ${p.status} fetching ${e}`);return He(await p.arrayBuffer())}),i=performance.now(),l=t.name??(t.text?"main.ldr":Ge(e)),a=ut(o,l,{looseGeometry:!1,callouts:t.callouts}),c=performance.now();t.onParsed?.(a),await s;let f=ft(o,new Set(a.filter((p)=>p.embedded).map((p)=>p.part)),l),m=(p,L)=>t.maxBodyTris!=null&&p>t.maxBodyTris||t.maxPackBytes!=null&&L>t.maxPackBytes;if(t.maxBodyTris!=null||t.maxPackBytes!=null){let p=sn(a,f,await s);if(m(p.known?p.bodyTris:0,p.packBytes))throw new pe({bodyTris:p.bodyTris,packBytes:p.packBytes,maxBodyTris:t.maxBodyTris,maxPackBytes:t.maxPackBytes})}let{pack:g,missing:y,requests:v,bytes:x}=await tn([...a.filter((p)=>!p.embedded).map((p)=>p.part),...f.values()],t),S=new Map([...f].map(([p,L])=>[L,p])),h=y.map((p)=>S.get(p)??p);for(let[p,L]of f){let b=g.header.parts[L];if(b)g.header.parts[p]=b}let u=performance.now();if(t.maxBodyTris!=null||t.maxPackBytes!=null){let p=g.buffer?.byteLength??g.bytes??0,L=rn(a,g);if(m(L,p))throw new pe({bodyTris:L,packBytes:p,maxBodyTris:t.maxBodyTris,maxPackBytes:t.maxPackBytes})}let d=t.cull?Ae(a,g.header,t.textures!==!1):void 0;return{insts:a,pack:g,missing:h,requests:v,bytes:x,cull:d,timings:{fetchModel:i-n,parse:c-i,parts:u-c,...d?{cull:performance.now()-u}:{}}}}self.onmessage=async(e)=>{let{id:t,source:n,opts:r}=e.data;try{let s=await dt(n,{...r,cull:!1,onParsed:(f)=>self.postMessage({id:t,insts:f})}),o=s.pack.buffer;if(self.postMessage({id:t,buffer:o,missing:s.missing,requests:s.requests,bytes:s.bytes,timings:s.timings},[o]),!r.cull)return;let i=performance.now(),l=Ae(s.insts,s.pack.header,r.textures!==!1),a=performance.now()-i,c=[];for(let f of[l.studHidden,l.studUp,l.underHidden,l.studHideStep,l.underHideStep])if(f)c.push(f.buffer);self.postMessage({id:t,cull:l,ms:a},c)}catch(s){if(s instanceof pe||s?.code==="ldr-budget")self.postMessage({id:t,phase:"error",error:String(s?.message??s),budget:{bodyTris:s.bodyTris,packBytes:s.packBytes,maxBodyTris:s.maxBodyTris,maxPackBytes:s.maxPackBytes}});else self.postMessage({id:t,phase:"error",error:String(s?.message??s)})}};\n';
3944
3993
 
3945
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/cdn.ts
3994
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/cdn.ts
3946
3995
  var worker;
3947
3996
  var seq = 0;
3948
3997
  var pending = new Map;
@@ -4100,10 +4149,12 @@ export {
4100
4149
  looseSteps,
4101
4150
  makeFinishMaterial,
4102
4151
  makeMaterial,
4152
+ makeTexturedMaterial,
4103
4153
  mergePacks,
4104
4154
  modelFileName,
4105
4155
  mul,
4106
4156
  norm,
4157
+ packTexture,
4107
4158
  parse,
4108
4159
  parseColor,
4109
4160
  parseColourLine,
@@ -4111,6 +4162,7 @@ export {
4111
4162
  parseModel,
4112
4163
  parseTexMap,
4113
4164
  planStepViews,
4165
+ pngHasAlpha,
4114
4166
  precomputeCull,
4115
4167
  prepareModel,
4116
4168
  resolveTexture,
@@ -13,10 +13,12 @@ export type Bucket = {
13
13
  pos: number[];
14
14
  col: number[];
15
15
  };
16
- /** Textured triangles for one PNG: `uv` = 2 floats per vertex (LDraw !TEXMAP projection, v = 0 at the image's top row). */
16
+ /** Textured triangles for one PNG (+ optional GLOSSMAP PNG): `uv` = 2 floats per vertex (LDraw !TEXMAP projection, v = 0 at the image's top row). */
17
17
  export type TexBucket = Bucket & {
18
18
  uv: number[];
19
19
  wrapU?: boolean;
20
+ texture?: string;
21
+ glossmap?: string;
20
22
  };
21
23
  export type Compiled = {
22
24
  body: Bucket;
@@ -41,7 +43,7 @@ export type Compiled = {
41
43
  bits: number[];
42
44
  };
43
45
  /** Only with `opts.tex`, for parts whose tree uses !TEXMAP: the untextured body a TEXMAP-aware renderer draws instead of
44
- * `body` (FALLBACK geometry dropped, `0 !:` geometry kept), plus textured triangles per texture name. Under/studs/edges are shared. */
46
+ * `body` (FALLBACK geometry dropped, `0 !:` geometry kept), plus textured triangles per texture (keyed by texture name, plus `\0<glossmap>` when the map has a GLOSSMAP). Under/studs/edges are shared. */
45
47
  tbody?: Bucket;
46
48
  texb?: Map<string, TexBucket>;
47
49
  };
@@ -1,4 +1,4 @@
1
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/spec.ts
1
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/spec.ts
2
2
  var norm = (s) => s.trim().toLowerCase().replace(/\\/g, "/").replace(/\/+/g, "/");
3
3
  function parseColor(tok) {
4
4
  if (/^0x/i.test(tok))
@@ -690,7 +690,7 @@ function fileStepViews(f) {
690
690
  viewMemo.set(f, out);
691
691
  return out;
692
692
  }
693
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/model.ts
693
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/model.ts
694
694
  var CALLOUT_END = 999999;
695
695
  var stepKey = (steps) => steps.map((x) => String(x).padStart(6, "0")).join(".") + "~";
696
696
  function flatten(doc, opts = {}) {
@@ -1039,7 +1039,7 @@ function parseLDConfig(text) {
1039
1039
  }
1040
1040
  return out;
1041
1041
  }
1042
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/instructions.ts
1042
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/instructions.ts
1043
1043
  var MIN_VIS = 0.5;
1044
1044
  var REORIENT = 0.35;
1045
1045
  var deg = Math.PI / 180;
@@ -1407,10 +1407,10 @@ function planStepViews(insts, parts, stepOf, nSteps, o = {}) {
1407
1407
  }
1408
1408
  };
1409
1409
  }
1410
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/render.ts
1410
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/render.ts
1411
1411
  import * as THREE from "three";
1412
1412
 
1413
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/pack-io.ts
1413
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/pack-io.ts
1414
1414
  import { decompress } from "fzstd";
1415
1415
  async function decompressPack(data) {
1416
1416
  const u = data instanceof Uint8Array ? data : new Uint8Array(data);
@@ -1433,7 +1433,7 @@ function decodePack(buf) {
1433
1433
  return { header, arr, bytes: buf.byteLength, buffer: buf };
1434
1434
  }
1435
1435
 
1436
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/cull.ts
1436
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/cull.ts
1437
1437
  var upright = (m, o = 0) => Math.abs(m[o + 7] - 1) < 0.001 && Math.abs(m[o + 4]) < 0.001 && Math.abs(m[o + 10]) < 0.001;
1438
1438
  var prepCache = new WeakMap;
1439
1439
  function prepPart(H, part) {
@@ -1750,7 +1750,7 @@ function precomputeCull(instances, H, useTex = true) {
1750
1750
  return cullCore(s.all, H, s.nSteps > 1 ? s.stepOf : undefined);
1751
1751
  }
1752
1752
 
1753
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/render.ts
1753
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/render.ts
1754
1754
  async function loadPack(url) {
1755
1755
  const res = await fetch(url);
1756
1756
  if (!res.ok)
@@ -1901,7 +1901,7 @@ function makeFinishMaterial(side, surface) {
1901
1901
  m.userData.ldrSurface = surface;
1902
1902
  return m;
1903
1903
  }
1904
- function makeTexturedMaterial(side, map, wrapU = false, baseMat) {
1904
+ function makeTexturedMaterial(side, map, wrapU = false, baseMat, gloss) {
1905
1905
  const surface = baseMat?.userData?.ldrSurface ?? colourSurface({ alpha: 255, luminance: 0, finish: { type: "default" } });
1906
1906
  const m = makeFinishMaterial(side, surface);
1907
1907
  const prev = m.onBeforeCompile;
@@ -1913,20 +1913,48 @@ function makeTexturedMaterial(side, map, wrapU = false, baseMat) {
1913
1913
  sh.vertexShader = `attribute vec2 ldrUV; varying vec2 vLdrUV;
1914
1914
  ` + sh.vertexShader.replace("#include <uv_vertex>", `#include <uv_vertex>
1915
1915
  vLdrUV = ldrUV;`);
1916
- sh.fragmentShader = `uniform sampler2D ldrMap; uniform int ldrWrapU; varying vec2 vLdrUV;
1916
+ let fs = `uniform sampler2D ldrMap; uniform int ldrWrapU; varying vec2 vLdrUV;
1917
1917
  ` + sh.fragmentShader.replace("#include <color_fragment>", `#include <color_fragment>
1918
- { vec2 uv = vec2(ldrWrapU == 1 ? fract(vLdrUV.x) : vLdrUV.x, vLdrUV.y); vec4 t = texture2D(ldrMap, uv);
1919
- float inside = step(0.0, uv.x) * step(uv.x, 1.0) * step(0.0, uv.y) * step(uv.y, 1.0);
1920
- diffuseColor.rgb = mix(diffuseColor.rgb, t.rgb, t.a * inside); }`);
1918
+ vec2 ldrUv = vec2(ldrWrapU == 1 ? fract(vLdrUV.x) : vLdrUV.x, vLdrUV.y);
1919
+ float ldrInside = step(0.0, ldrUv.x) * step(ldrUv.x, 1.0) * step(0.0, ldrUv.y) * step(ldrUv.y, 1.0);
1920
+ { vec4 t = texture2D(ldrMap, ldrUv); diffuseColor.rgb = mix(diffuseColor.rgb, t.rgb, t.a * ldrInside); }`);
1921
+ if (gloss) {
1922
+ sh.uniforms.ldrGlossMap = { value: gloss.map };
1923
+ sh.uniforms.ldrGlossA = { value: gloss.alpha ? 1 : 0 };
1924
+ fs = `uniform sampler2D ldrGlossMap; uniform float ldrGlossA;
1925
+ ` + fs.replace("#include <roughnessmap_fragment>", `#include <roughnessmap_fragment>
1926
+ vec4 ldrG = texture2D(ldrGlossMap, ldrUv); float ldrGloss = mix(ldrG.r, ldrG.a, ldrGlossA) * ldrInside;
1927
+ roughnessFactor = mix(roughnessFactor, 0.08, ldrGloss);`).replace("#include <lights_fragment_end>", `#include <lights_fragment_end>
1928
+ reflectedLight.directSpecular *= 1.0 + 0.5 * ldrGloss; reflectedLight.indirectSpecular *= 1.0 + 0.5 * ldrGloss;`);
1929
+ }
1930
+ sh.fragmentShader = fs;
1921
1931
  };
1922
- m.customProgramCacheKey = () => "ldrColorTex|" + baseKey;
1932
+ m.customProgramCacheKey = () => "ldrColorTex|" + baseKey + (gloss ? "|g" : "");
1923
1933
  m.userData.ldrMap = map;
1934
+ if (gloss)
1935
+ m.userData.ldrGloss = gloss;
1924
1936
  return m;
1925
1937
  }
1926
- function packTexture(pack, i) {
1938
+ function pngHasAlpha(b) {
1939
+ if (b.length < 33 || b[0] !== 137 || b[1] !== 80 || b[2] !== 78 || b[3] !== 71)
1940
+ return false;
1941
+ const type = b[25];
1942
+ if (type === 4 || type === 6)
1943
+ return true;
1944
+ for (let o = 8;o + 8 <= b.length; ) {
1945
+ const len = (b[o] << 24 | b[o + 1] << 16 | b[o + 2] << 8 | b[o + 3]) >>> 0, t = String.fromCharCode(b[o + 4], b[o + 5], b[o + 6], b[o + 7]);
1946
+ if (t === "tRNS")
1947
+ return true;
1948
+ if (t === "IDAT" || t === "IEND")
1949
+ return false;
1950
+ o += 12 + len;
1951
+ }
1952
+ return false;
1953
+ }
1954
+ function packTexture(pack, i, data = false) {
1927
1955
  const t = new THREE.Texture;
1928
1956
  t.flipY = false;
1929
- t.colorSpace = THREE.SRGBColorSpace;
1957
+ t.colorSpace = data ? THREE.NoColorSpace : THREE.SRGBColorSpace;
1930
1958
  t.wrapS = t.wrapT = THREE.ClampToEdgeWrapping;
1931
1959
  const e = pack.header.textures?.[i];
1932
1960
  const bytes = e?.data ? pack.arr(e.data) : null;
@@ -2714,10 +2742,29 @@ function* buildLdrSceneGen(insts, pack, P, color, mark) {
2714
2742
  const texCache = new Map;
2715
2743
  const texMats = new Map;
2716
2744
  const pending = [];
2717
- const texMat = (t, base, wrapU) => {
2745
+ const glossCache = new Map;
2746
+ const glossOf = (g) => {
2747
+ if (g === undefined)
2748
+ return;
2749
+ let gm = glossCache.get(g);
2750
+ if (gm === undefined) {
2751
+ const e = H.textures?.[g];
2752
+ const bytes = e?.data ? pack.arr(e.data) : null;
2753
+ if (bytes) {
2754
+ const r = packTexture(pack, g, true);
2755
+ pending.push(r.ready);
2756
+ gm = { map: r.texture, alpha: pngHasAlpha(bytes) };
2757
+ } else
2758
+ gm = null;
2759
+ glossCache.set(g, gm);
2760
+ }
2761
+ return gm ?? undefined;
2762
+ };
2763
+ const texMat = (t, base, wrapU, g) => {
2718
2764
  const role = base.userData?.ldrSideRole ?? (base.side === THREE.BackSide ? "B" : base.side === THREE.DoubleSide ? "D" : "F");
2719
2765
  const fin = base.userData?.ldrFinish ?? "default";
2720
- const k = t + ":" + role + ":" + fin + ":" + (wrapU ? 1 : 0);
2766
+ const gloss = glossOf(g);
2767
+ const k = t + ":" + role + ":" + fin + ":" + (wrapU ? 1 : 0) + (gloss ? ":g" + g : "");
2721
2768
  let m = texMats.get(k);
2722
2769
  if (!m) {
2723
2770
  let tx = texCache.get(t);
@@ -2727,7 +2774,7 @@ function* buildLdrSceneGen(insts, pack, P, color, mark) {
2727
2774
  pending.push(r.ready);
2728
2775
  texCache.set(t, tx);
2729
2776
  }
2730
- texMats.set(k, m = makeTexturedMaterial(base.side, tx, wrapU, base));
2777
+ texMats.set(k, m = makeTexturedMaterial(base.side, tx, wrapU, base, gloss));
2731
2778
  }
2732
2779
  return m;
2733
2780
  };
@@ -2796,7 +2843,7 @@ function* buildLdrSceneGen(insts, pack, P, color, mark) {
2796
2843
  if (textured)
2797
2844
  for (const tb of p.tex) {
2798
2845
  const gg = geom(tb, arr);
2799
- const im = newIM(gg, texMat(tb.t, mat, !!tb.wrapU), ids.length);
2846
+ const im = newIM(gg, texMat(tb.t, mat, !!tb.wrapU, tb.g), ids.length);
2800
2847
  texBatches++;
2801
2848
  lv0.push(im);
2802
2849
  if (bodyIm) {
@@ -3210,6 +3257,7 @@ function* buildLdrSceneGen(insts, pack, P, color, mark) {
3210
3257
  stats.condBatches = condObjs.length;
3211
3258
  stats.texBatches = texBatches;
3212
3259
  stats.textures = texCache.size;
3260
+ stats.glossmaps = [...glossCache.values()].filter(Boolean).length;
3213
3261
  stats.clusters = root.clusters.length;
3214
3262
  const texturesReady = Promise.all(pending).then(() => {});
3215
3263
  let stepS = STEP_NEVER - 1, cullS = !!P.cullStuds, cullU = !!P.cullUnder;
@@ -3416,7 +3464,7 @@ async function warmPackGeometry(pack, stop = () => false, sliceMs = 8) {
3416
3464
  await new Promise((r) => setTimeout(r, 0));
3417
3465
  }
3418
3466
  }
3419
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/scene.ts
3467
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/scene.ts
3420
3468
  import * as THREE2 from "three";
3421
3469
  function colorFn(src, pack, fallback) {
3422
3470
  const table = new Map;
@@ -3517,6 +3565,7 @@ function createLdrObjectInner(instances, pack, options, progressive) {
3517
3565
  });
3518
3566
  for (const m of [...mats, ...pairs.values()]) {
3519
3567
  m.userData.ldrMap?.dispose();
3568
+ m.userData.ldrGloss?.map.dispose();
3520
3569
  m.dispose();
3521
3570
  }
3522
3571
  cur = null;
@@ -3546,7 +3595,7 @@ function createLdrObjectInner(instances, pack, options, progressive) {
3546
3595
  cur.setLod(false);
3547
3596
  if (cur.root.parent !== object)
3548
3597
  object.add(cur.root);
3549
- Object.assign(stats, { drawn: drawnAt(null), studsTotal: cur.stats.studsTotal, studsHidden: cur.stats.studsHidden, undersTotal: cur.stats.undersTotal, undersHidden: cur.stats.undersHidden, condBatches: cur.stats.condBatches ?? 0, texBatches: cur.stats.texBatches ?? 0, textures: cur.stats.textures ?? 0, clusters: cur.stats.clusters ?? 0, longestSliceMs: cur.stats.longestSliceMs, buildYields: cur.stats.buildYields, buildMs: performance.now() - t0 });
3598
+ Object.assign(stats, { drawn: drawnAt(null), studsTotal: cur.stats.studsTotal, studsHidden: cur.stats.studsHidden, undersTotal: cur.stats.undersTotal, undersHidden: cur.stats.undersHidden, condBatches: cur.stats.condBatches ?? 0, texBatches: cur.stats.texBatches ?? 0, textures: cur.stats.textures ?? 0, glossmaps: cur.stats.glossmaps ?? 0, clusters: cur.stats.clusters ?? 0, longestSliceMs: cur.stats.longestSliceMs, buildYields: cur.stats.buildYields, buildMs: performance.now() - t0 });
3550
3599
  api.texturesReady = cur.texturesReady;
3551
3600
  };
3552
3601
  const setStep = (step, mask = null) => {
@@ -3633,10 +3682,10 @@ function createLdrObjectInner(instances, pack, options, progressive) {
3633
3682
  return finish();
3634
3683
  })();
3635
3684
  }
3636
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/parts-cdn.ts
3685
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/parts-cdn.ts
3637
3686
  import { decompress as decompress2 } from "fzstd";
3638
3687
 
3639
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/embedded.ts
3688
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/embedded.ts
3640
3689
  function splitFiles(text, fallback) {
3641
3690
  const files = new Map;
3642
3691
  let cur = null, saw = false;
@@ -3708,7 +3757,7 @@ function embeddedKeys(text, names, fallback = "main.ldr") {
3708
3757
  return out;
3709
3758
  }
3710
3759
 
3711
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/parts-cdn.ts
3760
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/parts-cdn.ts
3712
3761
  var DEFAULT_PARTS_CDN = "https://ldr-demo.vercel.app/cdn/v1/";
3713
3762
  var indexCache = new Map;
3714
3763
  var shardCache = new Map;
@@ -3940,10 +3989,10 @@ async function prepareModel(source, o = {}) {
3940
3989
  return { insts, pack, missing, requests, bytes, cull, timings: { fetchModel: t1 - t0, parse: t2 - t1, parts: t3 - t2, ...cull ? { cull: performance.now() - t3 } : {} } };
3941
3990
  }
3942
3991
 
3943
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/load-worker.gen.ts
3992
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/load-worker.gen.ts
3944
3993
  var load_worker_gen_default = 'var bt=ArrayBuffer,W=Uint8Array,de=Uint16Array,yt=Int16Array;var be=Int32Array,Ee=function(e,t,n){if(W.prototype.slice)return W.prototype.slice.call(e,t,n);if(t==null||t<0)t=0;if(n==null||n>e.length)n=e.length;var r=new W(n-t);return r.set(e.subarray(t,n)),r},ce=function(e,t,n,r){if(W.prototype.fill)return W.prototype.fill.call(e,t,n,r);if(n==null||n<0)n=0;if(r==null||r>e.length)r=e.length;for(;n<r;++n)e[n]=t;return e},xt=function(e,t,n,r){if(W.prototype.copyWithin)return W.prototype.copyWithin.call(e,t,n,r);if(n==null||n<0)n=0;if(r==null||r>e.length)r=e.length;while(n<r)e[t++]=e[n++]};var wt=["invalid zstd data","window size too large (>2046MB)","invalid block type","FSE accuracy too high","match distance too far back","unexpected EOF"],K=function(e,t,n){var r=Error(t||wt[e]);if(r.code=e,Error.captureStackTrace)Error.captureStackTrace(r,K);if(!n)throw r;return r},je=function(e,t,n){var r=0,s=0;for(;r<n;++r)s|=e[t++]<<(r<<3);return s},At=function(e,t){return(e[t]|e[t+1]<<8|e[t+2]<<16|e[t+3]<<24)>>>0},Mt=function(e,t){var n=e[0]|e[1]<<8|e[2]<<16;if(n==3126568&&e[3]==253){var r=e[4],s=r>>5&1,o=r>>2&1,i=r&3,l=r>>6;if(r&8)K(0);var a=6-s,c=i==3?4:i,f=je(e,a,c);a+=c;var m=l?1<<l:s,g=je(e,a,m)+(l==1&&256),y=g;if(!s){var v=1<<10+(e[5]>>3);y=v+(v>>3)*(e[5]&7)}if(y>2145386496)K(1);var x=new W((t==1?g||y:t?0:y)+12);return x[0]=1,x[4]=4,x[8]=8,{b:a+m,y:0,l:0,d:f,w:t&&t!=1?t:x.subarray(12),e:y,o:new be(x.buffer,0,3),u:g,c:o,m:Math.min(131072,y)}}else if((n>>4|e[3]<<20)==25481893)return At(e,4)+8;K(0)},re=function(e){var t=0;for(;1<<t<=e;++t);return t-1},ue=function(e,t,n){var r=(t<<3)+4,s=(e[t]&15)+5;if(s>n)K(3);var o=1<<s,i=o,l=-1,a=-1,c=-1,f=o,m=new bt(512+(o<<2)),g=new yt(m,0,256),y=new de(m,0,256),v=new de(m,512,o),x=512+(o<<1),S=new W(m,x,o),h=new W(m,x+o);while(l<255&&i>0){var u=re(i+1),d=r>>3,p=(1<<u+1)-1,L=(e[d]|e[d+1]<<8|e[d+2]<<16)>>(r&7)&p,b=(1<<u)-1,T=p-i-1,E=L&b;if(E<T)r+=u,L=E;else if(r+=u+1,L>b)L-=T;if(g[++l]=--L,L==-1)i+=L,S[--f]=l;else i-=L;if(!L)do{var D=r>>3;a=(e[D]|e[D+1]<<8)>>(r&7)&3,r+=2,l+=a}while(a==3)}if(l>255||i)K(0);var C=0,U=(o>>1)+(o>>3)+3,X=o-1;for(var H=0;H<=l;++H){var M=g[H];if(M<1){y[H]=-M;continue}for(c=0;c<M;++c){S[C]=H;do C=C+U&X;while(C>=f)}}if(C)K(0);for(c=0;c<o;++c){var A=y[S[c]]++,k=h[c]=s-re(A);v[c]=(A<<k)-o}return[r+7>>3,{b:s,s:S,n:h,t:v}]},vt=function(e,t){var n=0,r=-1,s=new W(292),o=e[t],i=s.subarray(0,256),l=s.subarray(256,268),a=new de(s.buffer,268);if(o<128){var c=ue(e,t+1,6),f=c[0],m=c[1];t+=o;var g=f<<3,y=e[t];if(!y)K(0);var v=0,x=0,S=m.b,h=S,u=(++t<<3)-8+re(y);for(;;){if(u-=S,u<g)break;var d=u>>3;if(v+=(e[d]|e[d+1]<<8)>>(u&7)&(1<<S)-1,i[++r]=m.s[v],u-=h,u<g)break;d=u>>3,x+=(e[d]|e[d+1]<<8)>>(u&7)&(1<<h)-1,i[++r]=m.s[x],S=m.n[v],v=m.t[v],h=m.n[x],x=m.t[x]}if(++r>255)K(0)}else{r=o-127;for(;n<r;n+=2){var p=e[++t];i[n]=p>>4,i[n+1]=p&15}++t}var L=0;for(n=0;n<r;++n){var b=i[n];if(b>11)K(0);L+=b&&1<<b-1}var T=re(L)+1,E=1<<T,D=E-L;if(D&D-1)K(0);i[r++]=re(D)+1;for(n=0;n<r;++n){var b=i[n];++l[i[n]=b&&T+1-b]}var C=new W(E<<1),U=C.subarray(0,E),X=C.subarray(E);a[T]=0;for(n=T;n>0;--n){var H=a[n];ce(X,n,H,a[n-1]=H+l[n]*(1<<T-n))}if(a[0]!=E)K(0);for(n=0;n<r;++n){var M=i[n];if(M){var A=a[M];ce(U,n,A,a[M]=A+(1<<T-M))}}return[t,{n:X,b:T,s:U}]},Tt=ue(new W([81,16,99,140,49,198,24,99,12,33,196,24,99,102,102,134,70,146,4]),0,6)[1],Ct=ue(new W([33,20,196,24,99,140,33,132,16,66,8,33,132,16,66,8,33,68,68,68,68,68,68,68,68,36,9]),0,6)[1],Et=ue(new W([32,132,16,66,102,70,68,68,68,68,36,73,2]),0,5)[1],Ve=function(e,t){var n=e.length,r=new be(n);for(var s=0;s<n;++s)r[s]=t,t+=1<<e[s];return r},Se=new W(new be([0,0,0,0,16843009,50528770,134678020,202050057,269422093]).buffer,0,36),St=Ve(Se,0),Pe=new W(new be([0,0,0,0,0,0,0,0,16843009,50528770,117769220,185207048,252579084,16]).buffer,0,53),Pt=Ve(Pe,3),le=function(e,t,n){var r=e.length,s=t.length,o=e[r-1],i=(1<<n.b)-1,l=-n.b;if(!o)K(0);var a=0,c=n.b,f=(r<<3)-8+re(o)-c,m=-1;for(;f>l&&m<s;){var g=f>>3,y=(e[g]|e[g+1]<<8|e[g+2]<<16)>>(f&7);a=(a<<c|y)&i,t[++m]=n.s[a],f-=c=n.n[a]}if(f!=l||m+1!=s)K(0)},Lt=function(e,t,n){var r=6,s=t.length,o=s+3>>2,i=o<<1,l=o+i;le(e.subarray(r,r+=e[0]|e[1]<<8),t.subarray(0,o),n),le(e.subarray(r,r+=e[2]|e[3]<<8),t.subarray(o,i),n),le(e.subarray(r,r+=e[4]|e[5]<<8),t.subarray(i,l),n),le(e.subarray(r),t.subarray(l),n)},kt=function(e,t,n){var r,s=t.b,o=e[s],i=o>>1&3;t.l=o&1;var l=o>>3|e[s+1]<<5|e[s+2]<<13,a=(s+=3)+l;if(i==1){if(s>=e.length)return;if(t.b=s+1,n)return ce(n,e[s],t.y,t.y+=l),n;return ce(new W(l),e[s])}if(a>e.length)return;if(i==0){if(t.b=a,n)return n.set(e.subarray(s,a),t.y),t.y+=l,n;return Ee(e,s,a)}if(i==2){var c=e[s],f=c&3,m=c>>2&3,g=c>>4,y=0,v=0;if(f<2)if(m&1)g|=e[++s]<<4|(m&2&&e[++s]<<12);else g=c>>3;else if(v=m,m<2)g|=(e[++s]&63)<<4,y=e[s]>>6|e[++s]<<2;else if(m==2)g|=e[++s]<<4|(e[++s]&3)<<12,y=e[s]>>2|e[++s]<<6;else g|=e[++s]<<4|(e[++s]&63)<<12,y=e[s]>>6|e[++s]<<2|e[++s]<<10;++s;var x=n?n.subarray(t.y,t.y+t.m):new W(t.m),S=x.length-g;if(f==0)x.set(e.subarray(s,s+=g),S);else if(f==1)ce(x,e[s++],S);else{var h=t.h;if(f==2){var u=vt(e,s);y+=s-(s=u[0]),t.h=h=u[1]}else if(!h)K(0);(v?Lt:le)(e.subarray(s,s+=y),x.subarray(S),h)}var d=e[s++];if(d){if(d==255)d=(e[s++]|e[s++]<<8)+32512;else if(d>127)d=d-128<<8|e[s++];var p=e[s++];if(p&3)K(0);var L=[Ct,Et,Tt];for(var b=2;b>-1;--b){var T=p>>(b<<1)+2&3;if(T==1){var E=new W([0,0,e[s++]]);L[b]={s:E.subarray(2,3),n:E.subarray(0,1),t:new de(E.buffer,0,1),b:0}}else if(T==2)r=ue(e,s,9-(b&1)),s=r[0],L[b]=r[1];else if(T==3){if(!t.t)K(0);L[b]=t.t[b]}}var D=t.t=L,C=D[0],U=D[1],X=D[2],H=e[a-1];if(!H)K(0);var M=(a<<3)-8+re(H)-X.b,A=M>>3,k=0,V=(e[A]|e[A+1]<<8)>>(M&7)&(1<<X.b)-1;A=(M-=U.b)>>3;var B=(e[A]|e[A+1]<<8)>>(M&7)&(1<<U.b)-1;A=(M-=C.b)>>3;var N=(e[A]|e[A+1]<<8)>>(M&7)&(1<<C.b)-1;for(++d;--d;){var P=X.s[V],w=X.n[V],O=C.s[N],F=C.n[N],R=U.s[B],z=U.n[B];A=(M-=R)>>3;var j=1<<R,I=j+((e[A]|e[A+1]<<8|e[A+2]<<16|e[A+3]<<24)>>>(M&7)&j-1);A=(M-=Pe[O])>>3;var G=Pt[O]+((e[A]|e[A+1]<<8|e[A+2]<<16)>>(M&7)&(1<<Pe[O])-1);A=(M-=Se[P])>>3;var _=St[P]+((e[A]|e[A+1]<<8|e[A+2]<<16)>>(M&7)&(1<<Se[P])-1);if(A=(M-=w)>>3,V=X.t[V]+((e[A]|e[A+1]<<8)>>(M&7)&(1<<w)-1),A=(M-=F)>>3,N=C.t[N]+((e[A]|e[A+1]<<8)>>(M&7)&(1<<F)-1),A=(M-=z)>>3,B=U.t[B]+((e[A]|e[A+1]<<8)>>(M&7)&(1<<z)-1),I>3)t.o[2]=t.o[1],t.o[1]=t.o[0],t.o[0]=I-=3;else{var Y=I-(_!=0);if(Y){if(I=Y==3?t.o[0]-1:t.o[Y],Y>1)t.o[2]=t.o[1];t.o[1]=t.o[0],t.o[0]=I}else I=t.o[0]}for(var b=0;b<_;++b)x[k+b]=x[S+b];k+=_,S+=_;var q=k-I;if(q<0){var J=-q,ee=t.e+q;if(J>G)J=G;for(var b=0;b<J;++b)x[k+b]=t.w[ee+b];k+=J,G-=J,q=0}for(var b=0;b<G;++b)x[k+b]=x[q+b];k+=G}if(k!=S)while(S<x.length)x[k++]=x[S++];else k=x.length;if(n)t.y+=k;else x=Ee(x,0,k)}else if(n){if(t.y+=g,S)for(var b=0;b<g;++b)x[b]=x[S+b]}else if(S)x=Ee(x,S);return t.b=a,x}K(2)},Bt=function(e,t){if(e.length==1)return e[0];var n=new W(t);for(var r=0,s=0;r<e.length;++r){var o=e[r];n.set(o,s),s+=o.length}return n};function ye(e,t){var n=[],r=+!t,s=0,o=0;for(;e.length;){var i=Mt(e,r||t);if(typeof i=="object"){if(r){if(t=null,i.w.length==i.u)n.push(t=i.w),o+=i.u}else n.push(t),i.e=0;for(;!i.l;){var l=kt(e,i,t);if(!l)K(5);if(t)i.e=i.y;else n.push(l),o+=l.length,xt(i.w,0,l.length),i.w.set(l,i.w.length-l.length)}s=i.b+i.c*4}else s=i;e=e.subarray(s)}return Bt(n,o)}async function It(e){let t=e instanceof Uint8Array?e:new Uint8Array(e),n=(r)=>r.buffer.slice(r.byteOffset,r.byteOffset+r.byteLength);if(t[0]===40&&t[1]===181&&t[2]===47&&t[3]===253)return n(ye(t));if(t[0]===31&&t[1]===139)return new Response(new Blob([t]).stream().pipeThrough(new DecompressionStream("gzip"))).arrayBuffer();return n(t)}async function He(e){return new TextDecoder().decode(await It(e))}var Ge=(e)=>decodeURIComponent(e.split(/[?#]/)[0].split("/").pop()||"main.ldr").replace(/\\.(zst|gz)$/i,"")||"main.ldr";function xe(e){let t=new DataView(e).getUint32(0,!0),n=JSON.parse(new TextDecoder().decode(new Uint8Array(e,4,t))),r=4+t;return{header:n,arr:(o)=>o&&(o[2]==="f32"?new Float32Array(e,r+o[0],o[1]):o[2]==="u16"?new Uint16Array(e,r+o[0],o[1]):o[2]==="u8"?new Uint8Array(e,r+o[0],o[1]):new Uint32Array(e,r+o[0],o[1])),bytes:e.byteLength,buffer:e}}var Z=(e)=>e.trim().toLowerCase().replace(/\\\\/g,"/").replace(/\\/+/g,"/");function Le(e){if(/^0x/i.test(e))return parseInt(e,16);return+e}var _e=/^~?\\s*moved\\s+to\\s+(.+)$/i,Ft=new Set(["FILE","NOFILE","!DATA","!:","STEP","BFC","ROTSTEP","BUFEXCHG","!TEXMAP"]),Qe=new Set(["part","subpart","primitive","8_primitive","48_primitive","shortcut","helper"]),Rt=new Set(["model","submodel","configuration"]),Dt=/^(?:!?LDRAW_ORG|Unofficial|Official\\s+LCAD)\\s+(\\S+)/i;function We(e){let t=Dt.exec(e);if(!t)return;let n=t[1].toLowerCase().replace(/^unofficial_/,"");return Qe.has(n)||Rt.has(n)?n:void 0}function Ut(e){let t=[],n=0;while(n<e.length){while(e[n]===" "||e[n]==="\\t")n++;if(n>=e.length)break;if(e[n]===\'"\'){let r="";n++;while(n<e.length&&e[n]!==\'"\'){if(e[n]==="\\\\"&&n+1<e.length)n++;r+=e[n++]}n++,t.push(r)}else{let r=n;while(r<e.length&&e[r]!==" "&&e[r]!=="\\t")r++;t.push(e.slice(n,r)),n=r}}return t}function zt(e,t=!1){let n=/^\\s*(\\S+)\\s+(.*)$/.exec(e);if(!n)return null;let r=n[1].toUpperCase(),s=r==="PLANAR"?9:r==="CYLINDRICAL"?10:r==="SPHERICAL"?11:0;if(!s)return null;let o=n[2].trim().split(/\\s+/);if(o.length<s+1)return null;let i=o.slice(0,s).map(Number);if(!i.every(Number.isFinite))return null;let l=n[2].trim();for(let m=0;m<s;m++)l=l.replace(/^\\S+\\s*/,"");let a=Ut(l);if(!a.length)return null;let c={method:r,p1:i.slice(0,3),p2:i.slice(3,6),p3:i.slice(6,9),texture:a[0]};if(s>=10)c.a=i[9];if(s===11)c.b=i[10];let f=a.findIndex((m,g)=>g>0&&m.toUpperCase()==="GLOSSMAP");if(f>0&&a[f+1])c.glossmap=a[f+1];if(t)c.next=!0;return c}var Ot=(e)=>{let t=e.replace(/[^A-Za-z0-9+/]/g,""),n=t+"=".repeat((4-t.length%4)%4),r=atob(n),s=new Uint8Array(r.length);for(let o=0;o<r.length;o++)s[o]=r.charCodeAt(o);return s};function Nt(e){let t=e.length,n=1,r=(c)=>c===32||c===9,s=()=>{while(n<t&&r(e.charCodeAt(n)))n++;let c=n;while(n<t&&!r(e.charCodeAt(n)))n++;return c<n?e.slice(c,n):null},o=s();if(o===null)return null;let i=Array(12);for(let c=0;c<12;c++){let f=s();if(f===null)return null;i[c]=+f}while(n<t&&r(e.charCodeAt(n)))n++;if(n>=t)return null;let l=e.slice(n),a=/[ \\t]{2,}|\\t/.test(l)?l.split(/\\s+/).join(" "):l;return{t:1,color:Le(o),m:i,file:Z(a)}}function Ke(e){let t=+e[0];if(!(t>=1&&t<=5)||e.length<2)return null;let n=Le(e[1]),r=e.slice(2).map(Number);if(t===1)return{t:1,color:n,m:r.slice(0,12),file:Z(e.slice(14).join(" "))};if(t===2)return{t:2,color:n,p:r.slice(0,6)};if(t===3)return{t:3,color:n,p:r.slice(0,9)};if(t===4)return{t:4,color:n,p:r.slice(0,12)};return{t:5,color:n,p:r.slice(0,12)}}function et(e,t="main.ldr",n={}){let r=!!n.geometryText,s=new Map,o=null,i="",l,a=null,c=[],f=null,m=()=>{if(!a)return;let u=Ot(a.chunks.join("")),d=u[0]===137&&u[1]===80&&u[2]===78&&u[3]===71;(l??=new Map).set(Z(a.name),{name:a.name,bytes:u,...d?{mime:"image/png"}:{}}),a=null},g=!0,y=(u)=>{if(m(),o={name:Z(u),cmds:[]},s.set(o.name,o),!i)i=o.name;c=[],f=null,g=!0},v=!1,x=!1,S=()=>{if(x)return;x=!0,s.clear(),o=null,i=""},h=(u,d)=>{if(f){u.tex=f,f=null;return}let p=c[c.length-1];if(!p)return;if(p.fallback){if(!d)u.fallback=!0}else u.tex=p.map};for(let u of e.split(`\n`)){let d=u.trim();if(!d)continue;if(r&&d.charCodeAt(0)>=50&&d.charCodeAt(0)<=53&&(d.charCodeAt(1)===32||d.charCodeAt(1)===9)){if(a||v)continue;if(!o)y(t);let T=d.slice(2).trimStart(),E=T.search(/\\s/),D={t:d.charCodeAt(0)-48,color:Le(E<0?T:T.slice(0,E)),p:[],src:d};h(D,!1),o.cmds.push(D),g=!1;continue}if(d.charCodeAt(0)===48&&(d.charCodeAt(1)===32||d.charCodeAt(1)===9)){let T=2;while(d.charCodeAt(T)===32||d.charCodeAt(T)===9)T++;let E=T;while(E<d.length&&d.charCodeAt(E)!==32&&d.charCodeAt(E)!==9)E++;let D=d.slice(T,E);if(!Ft.has(D)){if(v||a)continue;if(!o)y(t);let C=d.slice(T),U={t:0,text:C};if(D.charCodeAt(0)===33)U.meta=D;if(f=null,g&&!o.org){let H=We(C);if(H)o.org=H}let X=C.match(_e);if(X&&!o.movedTo){let H=X[1].trim().replace(/\\\\/g,"/");if(!/\\.(dat|ldr|mpd)$/i.test(H))H+=".dat";o.movedTo=Z(H)}o.cmds.push(U);continue}}if(d.charCodeAt(0)===49&&(d.charCodeAt(1)===32||d.charCodeAt(1)===9)&&!a&&!v){let T=Nt(d);if(T){if(!o)y(t);h(T,!1),o.cmds.push(T),g=!1;continue}}let p=d.split(/\\s+/);if(+p[0]===0){let T=d.slice(1).trim();if(p[1]==="FILE"){S(),v=!1,y(T.slice(5));continue}if(p[1]==="NOFILE"){m(),o=null,v=!0;continue}if(p[1]==="!DATA"){S(),m(),o=null,v=!1,a={name:T.slice(5).trim(),chunks:[]};continue}if(v)continue;if(a){if(p[1]==="!:")a.chunks.push(p.slice(2).join(""));continue}if(!o)y(t);let E={t:0,text:T};if(p[1]==="STEP")E.step=!0,c=[],f=null;else if(p[1]==="BFC")E.bfc=p.slice(2).join(" ");else if(p[1]==="ROTSTEP")if(E.meta="ROTSTEP",E.step=!0,c=[],f=null,(p[2]??"").toUpperCase()==="END")E.rotstep={x:0,y:0,z:0,mode:"END"};else{let C=(p[5]??"REL").toUpperCase();E.rotstep={x:+p[2]||0,y:+p[3]||0,z:+p[4]||0,mode:["REL","ABS","ADD"].includes(C)?C:"REL"}}else if(p[1]==="BUFEXCHG"){E.meta="BUFEXCHG";let C=(p[2]??"").toUpperCase();E.bufexchg={op:C==="RETRIEVE"?"RETRIEVE":"STORE",buf:p[3]??"A"}}else if(p[1]?.startsWith("!"))E.meta=p[1];if(g&&!o.org){let C=We(T);if(C)o.org=C}if(p[1]==="!TEXMAP"){let C=(p[2]??"").toUpperCase();if(f=null,C==="START"||C==="NEXT"){let U=zt(T.replace(/^!TEXMAP\\s+\\S+/i,""),C==="NEXT");if(E.texmap={op:C,...U?{map:U}:{}},U)if((o.texmaps??=[]).push(U),C==="START")c.push({map:U,fallback:!1});else f=U}else if(C==="FALLBACK"){E.texmap={op:C};let U=c[c.length-1];if(U)U.fallback=!0}else if(C==="END")E.texmap={op:C},c.pop()}else if(p[1]==="!:"){f=null;let C=Ke(p.slice(2));if(C)h(C,!0),E.geom=C}else f=null;let D=T.match(_e);if(D&&!o.movedTo){let C=D[1].trim().replace(/\\\\/g,"/");if(!/\\.(dat|ldr|mpd)$/i.test(C))C+=".dat";o.movedTo=Z(C)}o.cmds.push(E);continue}if(a||v)continue;if(!o)y(t);let b=Ke(p);if(!b)continue;h(b,!1),o.cmds.push(b),g=!1}return m(),l?{main:i,files:s,data:l}:{main:i,files:s}}var cn=Math.PI/180;function tt(e,t){let n=(r)=>[t[3]*r[0]+t[4]*r[1]+t[5]*r[2]+t[0],t[6]*r[0]+t[7]*r[1]+t[8]*r[2]+t[1],t[9]*r[0]+t[10]*r[1]+t[11]*r[2]+t[2]];return{...e,p1:n(e.p1),p2:n(e.p2),p3:n(e.p3)}}function nt(e,t){let[n,r,s,o,i,l,a,c,f,m,g,y]=e,[v,x,S,h,u,d,p,L,b,T,E,D]=t;return[o*v+i*x+l*S+n,a*v+c*x+f*S+r,m*v+g*x+y*S+s,o*h+i*p+l*T,o*u+i*L+l*E,o*d+i*b+l*D,a*h+c*p+f*T,a*u+c*L+f*E,a*d+c*b+f*D,m*h+g*p+y*T,m*u+g*L+y*E,m*d+g*b+y*D]}var ke=[0,0,0,1,0,0,0,1,0,0,0,1];var Xe=new WeakMap,$t=/^(s|8|48)\\//;function Be(e,t){let n=e.files.get(Z(t));if(!n)return"part";let r=Xe.get(n);if(r)return r;if(n.org)r=Qe.has(n.org)?"part":"model";else if(n.name===e.main)r="model";else r=n.cmds.some((s)=>s.t>=2||s.t===0&&!!s.geom||s.t===1&&$t.test(s.file))?"part":"model";return Xe.set(n,r),r}var Ze=new WeakMap;function rt(e){let t=Ze.get(e);if(t)return t;t=[];let n=0;for(let r of e.cmds){if(r.t===0&&r.step){n++;continue}if((r.t>=2||r.t===0&&r.geom&&r.geom.t>=2)&&t[t.length-1]!==n)t.push(n)}return Ze.set(e,t),t}var Ye=(e)=>e>16777215?"0x"+e.toString(16).toUpperCase():String(e),jt=(e)=>e.t===0?"0 "+e.text:e.t===1?`1 ${Ye(e.color)} ${e.m.join(" ")} ${e.file}`:e.src?e.src:`${e.t} ${Ye(e.color)} ${e.p.join(" ")}`,Ie=(e,t)=>`${Z(e)}#${t}`;function Vt(e,t){let n=[],r=0,s=(o)=>!!o&&o.t===0&&o.texmap?.op==="NEXT";for(let o of e.cmds){if(o.t===0){if(o.step){r++;continue}if(o.bfc!==void 0){n.push(o);continue}if(r!==t)continue;if(o.texmap)n.push(o);else if(o.geom){if(o.geom.t>=2)n.push(o);else if(s(n[n.length-1]))n.pop()}continue}if(r!==t)continue;if(o.t===1){if(s(n[n.length-1]))n.pop();continue}n.push(o)}return{name:Ie(e.name,t),cmds:n}}function st(e,t){return[`0 ldr: own geometry of ${e.name}, step ${t}`,...Vt(e,t).cmds.map(jt)].join(`\n`)+`\n`}function Ht(e){let t=2166136261;for(let n=0;n<e.length;n++)t^=e.charCodeAt(n),t=Math.imul(t,16777619);return(t>>>0).toString(16).padStart(8,"0")}function ot(e){let[t,n,r,s,o,i,l,a,c,f,m,g]=e,y=a*g-c*m,v=-(l*g-c*f),x=l*m-a*f,S=s*y+o*v+i*x;if(!Number.isFinite(S)||Math.abs(S)<0.000000000001)return null;let h=1/S,u=[y*h,-(o*g-i*m)*h,(o*c-i*a)*h,v*h,(s*g-i*f)*h,-(s*c-i*l)*h,x*h,-(s*m-o*f)*h,(s*a-o*l)*h];return[-(u[0]*t+u[1]*n+u[2]*r),-(u[3]*t+u[4]*n+u[5]*r),-(u[6]*t+u[7]*n+u[8]*r),...u]}var Gt=(e)=>String(+e.toFixed(6)||0),Je=(e)=>\'"\'+e.replace(/\\\\/g,"\\\\\\\\").replace(/"/g,"\\\\\\"")+\'"\';function it(e,t){let n=[...t.p1,...t.p2,...t.p3,...t.method!=="PLANAR"?[t.a??360]:[],...t.method==="SPHERICAL"?[t.b??180]:[]].map(Gt).join(" "),r=`0 ldr: ${e} with a model-level !TEXMAP\n0 BFC CERTIFY CCW\n0 !TEXMAP START ${t.method} ${n} ${Je(t.texture)}${t.glossmap?" GLOSSMAP "+Je(t.glossmap):""}\n1 16 0 0 0 1 0 0 0 1 0 0 0 1 ${Z(e)}\n0 !TEXMAP END\n`;return{name:`${Z(e)}#tex-${Ht(r)}`,source:r}}var _t=(e,t)=>{let n=[,,,,,,,,,];for(let r=0;r<3;r++)for(let s=0;s<3;s++)n[r*3+s]=e[r*3]*t[s]+e[r*3+1]*t[3+s]+e[r*3+2]*t[6+s];return n};function Wt(e,t,n){let r=Math.PI/180,s=Math.sin(e*r),o=Math.cos(e*r),i=Math.sin(t*r),l=Math.cos(t*r),a=Math.sin(n*r),c=Math.cos(n*r);return[l*c,-l*a,i,o*a+s*i*c,o*c-s*i*a,-s*l,s*a-o*i*c,s*c+o*i*a,o*l]}var qe=new WeakMap;function at(e){let t=qe.get(e);if(t)return t;let n=[],r=null;for(let s of e.cmds){if(s.t!==0||!s.step)continue;let o=s.rotstep;if(o)if(o.mode==="END")r=null;else{let i=Wt(o.x,o.y,o.z);r=o.mode==="ABS"?{abs:!0,m:i,rotstep:o}:o.mode==="ADD"&&r?{abs:r.abs,m:_t(r.m,i),rotstep:o}:{abs:!1,m:i,rotstep:o}}n.push(r)}return n.push(r),qe.set(e,n),n}var lt=999999,ct=(e)=>e.map((t)=>String(t).padStart(6,"0")).join(".")+"~";function Kt(e,t={}){let n=[],r=t.maxDepth??64,s=t.looseGeometry!==!1,o=!!t.textures,i=!!t.callouts,l=new Map,a=0,c=(m,g)=>{let y=m.name+"#"+g,v=l.get(y);if(!v){let x;v=Object.assign(()=>x??=st(m,g),{file:m,step:g}),l.set(y,v)}return v},f=(m,g,y,v,x,S,h,u,d=null,p=[],L=[])=>{let b=m.files.get(g);if(!b||h>r)return;let T=0,E=h>0?++a:0,D=i&&E?[...p,E]:p,C=i&&E?[...L,y]:L,U=i&&!d?new Map:null,X=-1,H=0,M=(P,w,O,F,R,z)=>{if(X!==T)X=T,H=0;let j=U?.get(T+"|"+w);if(j)return j.n++,f(P,P===m?w:P.main,O,F,x+"/"+w,[...R,lt],h+1,z,[...R,lt],D,C);let I=[...R,H++],G=n.length;f(P,P===m?w:P.main,O,F,x+"/"+w,I,h+1,z,d,D,C),U?.set(T+"|"+w,{n:1,from:G,to:n.length})},A=at(b),k=s?rt(b):[],V=0,B=(P,w,O,F,R,z,j)=>{if(d)F=d;let I={part:P,color:w,m:O,step:F[0],steps:F,localStep:R,path:x};if(E){if(I.sub=E,i)I.subPath=D,I.subMats=C}if(z)I.embedded=!0;if(j){let _=it(P,j);I.texPart=_.name,I.texSource=_.source}let G=A[R];if(G)I.view=G;return n.push(I),I},N=(P)=>{while(V<k.length&&k[V]<=P){let w=k[V++];B(Ie(b.name,w),v,y,[...S,w],w,!1,null).source=c(b,w)}};for(let P of b.cmds){if(P.t===0&&P.step){T++;continue}let w=P;if(w.t>=2||w.t===0&&w.geom&&w.geom.t>=2){N(T);continue}if(w.t===0)if(o&&w.geom?.t===1)w=w.geom;else continue;if(w.t!==1)continue;if(o&&w.fallback)continue;let O=w.color===16?v:w.color,F=nt(y,w.m),R=[...S,T],z=o?w.tex??u:null,j=z?ot(w.m):null,I=z&&j?tt(z,j):null;if(m.files.has(w.file)){if(Be(m,w.file)==="model"){M(m,w.file,F,O,R,I);continue}B(w.file,O,F,R,T,!0,I);continue}let G=t.resolveModel?.(w.file);if(G){M(G,w.file,F,O,R,I);continue}B(w.file,O,F,R,T,!1,I)}if(N(1/0),U){for(let P of U.values())if(P.n>1)for(let w=P.from;w<P.to;w++)n[w].mult??=P.n}};if(Be(e,e.main)==="part"){if(e.files.get(e.main))n.push({part:e.main,color:16,m:[...ke],step:0,steps:[0],localStep:0,path:e.main,embedded:!0});return n}return f(e,e.main,ke,16,e.main,[],0,null),n}function ut(e,t="main.ldr",n){return Kt(et(e,t,{geometryText:!0}),n)}function Xt(e,t){let n=new Map,r=null,s=!1;for(let o of e.split(/\\r?\\n/)){let i=/^\\s*0\\s+FILE\\s+(.+)$/i.exec(o);if(i){if(!s)n.clear();s=!0,r=[],n.set(Z(i[1]),r);continue}if(/^\\s*0\\s+(NOFILE|!DATA)(\\s|$)/i.test(o)){if(!s)n.clear();s=!0,r=null;continue}if(!r){if(s)continue;r=[],n.set(Z(t),r)}let l=o.trim().replace(/\\s+/g," ");if(l)r.push(l)}return n}var Zt=(e)=>{if(!e.startsWith("1 "))return null;let t=e.split(" ");return t.length>=15?Z(t.slice(14).join(" ")):null};function Yt(e){let t=2166136261,n=1523705862;for(let r=0;r<e.length;r++){let s=e.charCodeAt(r);t=Math.imul(t^s,16777619),n=Math.imul(n^s,16777629)^n>>>15}return(t>>>0).toString(16).padStart(8,"0")+(n>>>0).toString(16).padStart(8,"0")}function ft(e,t,n="main.ldr"){let r=Xt(e,n),s=new Map;for(let o of t){let i=Z(o);if(!r.has(i)||s.has(i))continue;let l=new Set([i]),a=[i];while(a.length)for(let f of r.get(a.pop())){let m=Zt(f);if(m&&r.has(m)&&!l.has(m))l.add(m),a.push(m)}let c=[i,...[...l].filter((f)=>f!==i).sort()].map((f)=>"0 FILE "+(f===i?"":f)+`\n`+r.get(f).join(`\n`)).join(`\n`);s.set(i,`emb/${Yt(c)}.dat`)}return s}var pt=(e,t=0)=>Math.abs(e[t+7]-1)<0.001&&Math.abs(e[t+4])<0.001&&Math.abs(e[t+10])<0.001,mt=new WeakMap;function Fe(e,t){let n=mt.get(e);if(!n)mt.set(e,n=new Map);let r=n.get(t);if(r!==void 0)return r;let s=e.parts[t];if(!s)return n.set(t,null),null;let o=s.studs.length,i=new Float64Array(o*12),l=[],a=[];s.studs.forEach((g,y)=>{l.push(g[0]),a.push(g[1]);for(let v=0;v<12;v++)i[y*12+v]=g[2+v]});let c=null,f=0,m=0;if(s.mask){f=s.mask[0],m=s.mask[1];let g=s.mask[2];c=new Uint8Array(g.length);for(let y=0;y<g.length;y++)c[y]=g.charCodeAt(y)===49?1:0}return n.set(t,r={nStuds:o,studM:i,kind:l,color:a,mask:c,nx:f,nz:m}),r}var ie=1073741824;class Re{a;b;c;vals;used;mask;size=0;constructor(e=1024){let t=1024;while(t<e*2)t*=2;this.a=new Int32Array(t),this.b=new Int32Array(t),this.c=new Int32Array(t),this.vals=new Int32Array(t),this.used=new Uint8Array(t),this.mask=t-1}slot(e,t,n){let r=Math.imul(e,2654435761)^Math.imul(t,2246822507)^Math.imul(n,3266489909);return r^=r>>>15,r=Math.imul(r,739982445),r^=r>>>12,r&this.mask}get(e,t,n){let r=this.slot(e,t,n),s=this.used,o=this.a,i=this.b,l=this.c;for(;;){if(!s[r])return-1;if(o[r]===e&&i[r]===t&&l[r]===n)return this.vals[r];r=r+1&this.mask}}put(e,t,n,r){if((this.size+1)*2>this.used.length)this.grow();let s=this.slot(e,t,n),o=this.used,i=this.a,l=this.b,a=this.c;for(;;){if(!o[s])return o[s]=1,i[s]=e,l[s]=t,a[s]=n,this.vals[s]=r,this.size++,-1;if(i[s]===e&&l[s]===t&&a[s]===n){let c=this.vals[s];return this.vals[s]=r,c}s=s+1&this.mask}}grow(){let{a:e,b:t,c:n,vals:r,used:s}=this,o=s.length*2;this.a=new Int32Array(o),this.b=new Int32Array(o),this.c=new Int32Array(o),this.vals=new Int32Array(o),this.used=new Uint8Array(o),this.mask=o-1,this.size=0;for(let i=0;i<s.length;i++)if(s[i])this.put(e[i],t[i],n[i],r[i])}}var we=(e,t,n)=>e>-ie&&e<ie&&t>-ie&&t<ie&&n>-ie&&n<ie,fe=2147483647;function Jt(e,t,n){let s=[],o=[],i=[],l=[],a=[],c=[],f=[],m=[],g=new Re(e.length*2),y=[],v=[];for(let M=0;M<e.length;M++){let A=e[M],k=A.m,V=t.parts[A.part];if(!V||!V.mask||!pt(k))continue;let B=V.bbox,N=1/0,P=-1/0,w=1/0,O=-1/0;for(let z=0;z<2;z++)for(let j=0;j<2;j++){let I=z?B[3]:B[0],G=j?B[5]:B[2],_=k[0]+k[3]*I+k[5]*G,Y=k[2]+k[9]*I+k[11]*G;if(_<N)N=_;if(_>P)P=_;if(Y<w)w=Y;if(Y>O)O=Y}let F=a.length;s.push(N),o.push(P),i.push(w),l.push(O),a.push(M),c.push(Fe(t,A.part)),f.push(B[0]),m.push(B[2]);let R=Math.round((k[1]+B[4])*2);for(let z=Math.floor(N/40);z<=Math.floor(P/40);z++)for(let j=Math.floor(w/40);j<=Math.floor(O/40);j++){if(!we(R,z,j))continue;let I=v.length;v.push(F),y.push(g.put(R,z,j,I))}}let x=[0,5,-5,5,-5],S=[0,5,5,-5,-5],h=NaN,u=NaN,d=NaN,p=-1,L=(M,A,k,V)=>{let B=Math.round(A*2),N=Math.floor(M/40),P=Math.floor(k/40);if(!we(B,N,P))return fe;let w=fe;if(B!==h||N!==u||P!==d)h=B,u=N,d=P,p=g.get(B,N,P);for(let O=p;O>=0;O=y[O]){let F=v[O];if(a[F]===V||!(M-6>=s[F]-0.5&&M+6<=o[F]+0.5&&k-6>=i[F]-0.5&&k+6<=l[F]+0.5))continue;let R=e[a[F]].m,z=c[F],j=f[F],I=m[F],G=M-R[0],_=A-R[1],Y=k-R[2],q=R[3]*G+R[6]*_+R[9]*Y,J=R[5]*G+R[8]*_+R[11]*Y,{nx:ee,nz:se,mask:ae}=z,oe=!0;for(let Q=0;Q<5;Q++){let te=Math.floor((q+x[Q]-j)/20),ne=Math.floor((J+S[Q]-I)/20);if(te<0||ne<0||te>=ee||ne>=se||ae[te*se+ne]!==1){oe=!1;break}}if(oe){if(!n)return 0;let Q=n[a[F]];if(Q<w)w=Q}}return w},b=0;for(let M of e){let A=Fe(t,M.part);if(A)b+=A.nStuds}let T=new Uint8Array(b),E=new Uint8Array(b),D=new Re(b),C=0,U=n?new Int32Array(b).fill(fe):void 0;for(let M=0,A=0;M<e.length;M++){let k=e[M],V=Fe(t,k.part);if(!V)continue;let B=k.m,N=V.studM,P=B[0],w=B[1],O=B[2],F=B[3],R=B[4],z=B[5],j=B[6],I=B[7],G=B[8],_=B[9],Y=B[10],q=B[11];for(let J=0;J<V.nStuds;J++,A++){let ee=J*12,se=N[ee+4],ae=N[ee+7],oe=N[ee+10];if(!(Math.abs(j*se+I*ae+G*oe-1)<0.001&&Math.abs(F*se+R*ae+z*oe)<0.001&&Math.abs(_*se+Y*ae+q*oe)<0.001))continue;E[A]=1;let Q=N[ee],te=N[ee+1],ne=N[ee+2],De=P+F*Q+R*te+z*ne,Ue=w+j*Q+I*te+G*ne,ze=O+_*Q+Y*te+q*ne,Oe=L(De,Ue,ze,M);if(Oe!==fe){if(T[A]=1,C++,U)U[A]=Oe}let me=Math.round(De),he=Math.round(Ue),ge=Math.round(ze);if(!we(me,he,ge))continue;if(!n){D.put(me,he,ge,1);continue}let Ne=n[M],$e=D.get(me,he,ge);if($e<0||Ne<$e)D.put(me,he,ge,Ne)}}let X=new Uint8Array(e.length),H=n?new Int32Array(e.length).fill(fe):void 0;for(let M=0;M<e.length;M++){let A=e[M],k=t.parts[A.part];if(!k||!k.under||!pt(A.m))continue;let V=k.bbox,B=Math.round((V[3]-V[0])/20),N=Math.round((V[5]-V[2])/20);if(!B||!N||B*N>4096)continue;let P=A.m,w=!0,O=0;for(let F=0;F<B&&w;F++)for(let R=0;R<N;R++){let z=V[0]+10+20*F,j=V[2]+10+20*R,I=P[0]+P[3]*z+P[5]*j,G=P[2]+P[9]*z+P[11]*j,_=Math.round(I),Y=Math.round(P[1]+V[4]),q=Math.round(G),J=we(_,Y,q)?D.get(_,Y,q):-1;if(J<0){w=!1;break}if(J>O)O=J}if(w){if(X[M]=1,H)H[M]=O}}return{nStuds:b,studHidden:T,studUp:E,underHidden:X,studsHidden:C,studHideStep:U,underHideStep:H}}function qt(e,t,n){let r=(a)=>n&&a.texPart&&t.parts[a.texPart]?a.texPart:a.part,s=e.map((a)=>({part:r(a),color:String(a.color),m:a.m,steps:a.steps})),o=s.map((a)=>ct(a.steps)),i=[...new Set(o)].sort(),l=new Map(i.map((a,c)=>[a,c]));return{all:s,stepOf:o.map((a)=>l.get(a)),nSteps:i.length}}function Ae(e,t,n=!0){let r=qt(e,t,n);return Jt(r.all,t,r.nSteps>1?r.stepOf:void 0)}var Ce="https://ldr-demo.vercel.app/cdn/v1/",ve=new Map,Me=new Map;function gt(e=Ce,t=fetch){let n=ve.get(e);if(!n)n=t(e+"parts.json").then((r)=>{if(!r.ok)throw Error(`ldr: parts CDN index ${r.status} (${e}parts.json)`);return r.json()}),n.catch(()=>ve.delete(e)),ve.set(e,n);return n}var Qt=(e)=>Array.isArray(e)&&e.length===3&&typeof e[0]==="number"&&typeof e[1]==="number"&&(e[2]==="f32"||e[2]==="u16"||e[2]==="u8"||e[2]==="u32"),Te=(e,t)=>Qt(e)?[e[0]+t,e[1],e[2]]:Array.isArray(e)?e.map((n)=>Te(n,t)):e&&typeof e==="object"?Object.fromEntries(Object.entries(e).map(([n,r])=>[n,Te(r,t)])):e;function en(e,t={}){let n=[],r=0,s={},o={};for(let g of e){let y=g.buffer;if(!y)throw Error("ldr: mergePacks needs packs from decodePack/loadPack");let v=new DataView(y).getUint32(0,!0),x=new Uint8Array(y,4+v),S=r;n.push(x),r+=x.byteLength;let h=(4-r%4)%4;if(h)n.push(new Uint8Array(h)),r+=h;for(let[u,d]of Object.entries(g.header.parts??{}))if(!(u in s))s[u]=Te(d,S);for(let[u,d]of Object.entries(g.header.studKinds??{}))if(!(u in o))o[u]=Te(d,S)}let i=e[0]?.header??{version:1,weld:!0},l=new TextEncoder().encode(JSON.stringify({...i,parts:s,studKinds:o,...t})),a=(4-(4+l.length)%4)%4,c=4+l.length+a,f=new Uint8Array(c+r);new DataView(f.buffer).setUint32(0,l.length+a,!0),f.set(l,4),f.fill(32,4+l.length,c);let m=c;for(let g of n)f.set(g,m),m+=g.byteLength;return xe(f.buffer)}async function tn(e,t={}){e=[...e];try{return await ht(e,t)}catch(n){let r=t.cdn??Ce,s=t.fetch??fetch;ve.delete(r);let o=(i,l)=>s(i,{...l,cache:"reload"});try{return await ht(e,{...t,fetch:o})}catch{throw n}}}async function ht(e,t){let n=t.cdn??Ce,r=t.fetch??fetch,s=t.gap??16384,o=await gt(n,r),i=[...new Set([...e].map(Z))],l=[],a=[];for(let h of i){if(Me.has(n+h))continue;let u=o.parts[h];if(u)a.push({name:h,off:u[0],len:u[1],file:u[2]??0});else l.push(h)}a.sort((h,u)=>h.file-u.file||h.off-u.off);let c=[];for(let h of a){let u=c[c.length-1];if(u&&u.file===h.file&&h.off-u.end<=s)u.end=Math.max(u.end,h.off+h.len),u.items.push(h);else c.push({file:h.file,start:h.off,end:h.off+h.len,items:[h]})}let f=0,m=0,g=0,y=new Map,v=async()=>{while(g<c.length){let h=c[g++],u=y.get(h.file);if(u)u=u.subarray(h.start,h.end);else{let d=await r(new URL(o.archives[h.file],n).href,{headers:{Range:`bytes=${h.start}-${h.end-1}`,"Accept-Encoding":"identity"}});if(!d.ok)throw Error(`ldr: parts CDN ${d.status}`);if(m++,u=new Uint8Array(await d.arrayBuffer()),f+=u.byteLength,d.status===200)y.set(h.file,u),u=u.subarray(h.start,h.end)}for(let d of h.items){let p=ye(u.subarray(d.off-h.start,d.off-h.start+d.len)),L=p.buffer.slice(p.byteOffset,p.byteOffset+p.byteLength);Me.set(n+d.name,xe(L))}}};await Promise.all(Array.from({length:Math.min(t.concurrency??24,c.length)},v));let x=i.filter((h)=>Me.has(n+h)).map((h)=>Me.get(n+h));return{pack:x.length?en(x,{colors:o.colors}):xe(nn(o.colors)),missing:l,requests:m,bytes:f}}function nn(e){let t=new TextEncoder().encode(JSON.stringify({version:1,weld:!0,parts:{},studKinds:{},colors:e})),n=(4-(4+t.length)%4)%4,r=new Uint8Array(4+t.length+n).fill(32);return new DataView(r.buffer).setUint32(0,t.length+n,!0),r.set(t,4),r.buffer}function rn(e,t){let n=t.header.parts;if(!n)return 0;let r=0;for(let s of e){let o=n[s.part]?.body?.tris;if(o)r+=o}return r}function sn(e,t,n){let r=0,s=0,o=!1,i=new Set;for(let l of e){let a=l.embedded?t.get(l.part):Z(l.part),c=a?n.parts[a]:void 0;if(!c)continue;if(c[3]!=null)o=!0,r+=c[3];if(!i.has(a))i.add(a),s+=c[1]}return{bodyTris:r,packBytes:s,known:o}}class pe extends Error{code="ldr-budget";bodyTris;packBytes;maxBodyTris;maxPackBytes;constructor(e){let t=[e.maxBodyTris!=null&&e.bodyTris>e.maxBodyTris?`${e.bodyTris} triangles (limit ${e.maxBodyTris})`:"",e.maxPackBytes!=null&&e.packBytes>e.maxPackBytes?`${(e.packBytes/1e6).toFixed(1)} MB parts pack (limit ${(e.maxPackBytes/1e6).toFixed(0)} MB)`:""].filter(Boolean).join(", ");super(`ldr: model exceeds device budget: ${t}`);this.name="BudgetError",this.bodyTris=e.bodyTris,this.packBytes=e.packBytes,this.maxBodyTris=e.maxBodyTris,this.maxPackBytes=e.maxPackBytes}}async function dt(e,t={}){let n=performance.now(),r=t.fetch??fetch,s=gt(t.cdn??Ce,r);s.catch(()=>{});let o=t.text?e:await r(e).then(async(p)=>{if(!p.ok)throw Error(`ldr: ${p.status} fetching ${e}`);return He(await p.arrayBuffer())}),i=performance.now(),l=t.name??(t.text?"main.ldr":Ge(e)),a=ut(o,l,{looseGeometry:!1,callouts:t.callouts}),c=performance.now();t.onParsed?.(a),await s;let f=ft(o,new Set(a.filter((p)=>p.embedded).map((p)=>p.part)),l),m=(p,L)=>t.maxBodyTris!=null&&p>t.maxBodyTris||t.maxPackBytes!=null&&L>t.maxPackBytes;if(t.maxBodyTris!=null||t.maxPackBytes!=null){let p=sn(a,f,await s);if(m(p.known?p.bodyTris:0,p.packBytes))throw new pe({bodyTris:p.bodyTris,packBytes:p.packBytes,maxBodyTris:t.maxBodyTris,maxPackBytes:t.maxPackBytes})}let{pack:g,missing:y,requests:v,bytes:x}=await tn([...a.filter((p)=>!p.embedded).map((p)=>p.part),...f.values()],t),S=new Map([...f].map(([p,L])=>[L,p])),h=y.map((p)=>S.get(p)??p);for(let[p,L]of f){let b=g.header.parts[L];if(b)g.header.parts[p]=b}let u=performance.now();if(t.maxBodyTris!=null||t.maxPackBytes!=null){let p=g.buffer?.byteLength??g.bytes??0,L=rn(a,g);if(m(L,p))throw new pe({bodyTris:L,packBytes:p,maxBodyTris:t.maxBodyTris,maxPackBytes:t.maxPackBytes})}let d=t.cull?Ae(a,g.header,t.textures!==!1):void 0;return{insts:a,pack:g,missing:h,requests:v,bytes:x,cull:d,timings:{fetchModel:i-n,parse:c-i,parts:u-c,...d?{cull:performance.now()-u}:{}}}}self.onmessage=async(e)=>{let{id:t,source:n,opts:r}=e.data;try{let s=await dt(n,{...r,cull:!1,onParsed:(f)=>self.postMessage({id:t,insts:f})}),o=s.pack.buffer;if(self.postMessage({id:t,buffer:o,missing:s.missing,requests:s.requests,bytes:s.bytes,timings:s.timings},[o]),!r.cull)return;let i=performance.now(),l=Ae(s.insts,s.pack.header,r.textures!==!1),a=performance.now()-i,c=[];for(let f of[l.studHidden,l.studUp,l.underHidden,l.studHideStep,l.underHideStep])if(f)c.push(f.buffer);self.postMessage({id:t,cull:l,ms:a},c)}catch(s){if(s instanceof pe||s?.code==="ldr-budget")self.postMessage({id:t,phase:"error",error:String(s?.message??s),budget:{bodyTris:s.bodyTris,packBytes:s.packBytes,maxBodyTris:s.maxBodyTris,maxPackBytes:s.maxPackBytes}});else self.postMessage({id:t,phase:"error",error:String(s?.message??s)})}};\n';
3945
3994
 
3946
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/cdn.ts
3995
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/cdn.ts
3947
3996
  var worker;
3948
3997
  var seq = 0;
3949
3998
  var pending = new Map;
@@ -4056,7 +4105,7 @@ async function loadModel(source, o = {}) {
4056
4105
  timings: { ...prep.timings, parts: t3 - t0 - prep.timings.fetchModel - prep.timings.parse, build: t4 - t3, total: t4 - t0 }
4057
4106
  });
4058
4107
  }
4059
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/node/library.ts
4108
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/node/library.ts
4060
4109
  import { existsSync, readFileSync } from "fs";
4061
4110
  import { join } from "path";
4062
4111
  var xf = (m, x, y, z) => [m[3] * x + m[4] * y + m[5] * z + m[0], m[6] * x + m[7] * y + m[8] * z + m[1], m[9] * x + m[10] * y + m[11] * z + m[2]];
@@ -4147,7 +4196,7 @@ class Library {
4147
4196
  if (hit)
4148
4197
  return hit;
4149
4198
  const pos = [], col = [], edges = [], cond = [];
4150
- const tx = opts.texmap ? { uv: [], tri: [], names: [] } : undefined;
4199
+ const tx = opts.texmap ? { uv: [], tri: [], names: [], gloss: [] } : undefined;
4151
4200
  const r = this.where(name);
4152
4201
  if (r)
4153
4202
  this.emit(r.doc, r.doc.main, I, 16, false, true, pos, col, edges, cond, tx, null);
@@ -4156,6 +4205,8 @@ class Library {
4156
4205
  m.uv = new Float32Array(tx.uv);
4157
4206
  m.texture = new Int32Array(tx.tri);
4158
4207
  m.textures = tx.names;
4208
+ if (tx.gloss.some((g) => g))
4209
+ m.glossmaps = tx.gloss;
4159
4210
  }
4160
4211
  this.meshCache.set(ck, m);
4161
4212
  return m;
@@ -4188,10 +4239,11 @@ class Library {
4188
4239
  const flip = inv !== det(m) < 0;
4189
4240
  const cc = (c) => c === 16 ? color : c;
4190
4241
  const texIdx = (t) => {
4191
- let i = tx.names.indexOf(t.texture);
4242
+ let i = tx.names.findIndex((n, k) => n === t.texture && tx.gloss[k] === t.glossmap);
4192
4243
  if (i < 0) {
4193
4244
  i = tx.names.length;
4194
4245
  tx.names.push(t.texture);
4246
+ tx.gloss.push(t.glossmap);
4195
4247
  }
4196
4248
  return i;
4197
4249
  };
@@ -4307,11 +4359,11 @@ class Library {
4307
4359
  return insts;
4308
4360
  }
4309
4361
  }
4310
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/node/build-pack.ts
4362
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/node/build-pack.ts
4311
4363
  import fs3 from "node:fs";
4312
4364
  import path3 from "node:path";
4313
4365
 
4314
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/node/resolver.ts
4366
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/node/resolver.ts
4315
4367
  import fs from "node:fs";
4316
4368
  import path from "node:path";
4317
4369
  var TOP_STUD_RE = /^(stud|studa|stud2|stud2a|stud6|stud6a|stud10|stud15|stud2s|stud2s2|stud-logo\d*|stud2-logo\d*)\.dat$/i;
@@ -4583,11 +4635,11 @@ function collectDeps(lib, refs) {
4583
4635
  return out;
4584
4636
  }
4585
4637
 
4586
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/node/compile.ts
4638
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/node/compile.ts
4587
4639
  import fs2 from "node:fs";
4588
4640
  import path2 from "node:path";
4589
4641
 
4590
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/node/lod.ts
4642
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/node/lod.ts
4591
4643
  import { MeshoptSimplifier, MeshoptEncoder } from "meshoptimizer";
4592
4644
  await Promise.all([MeshoptSimplifier.ready, MeshoptEncoder.ready]);
4593
4645
  function reorderMesh(P, N, C, I) {
@@ -4779,7 +4831,7 @@ function lodChain(P, C, I) {
4779
4831
  return out;
4780
4832
  }
4781
4833
 
4782
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/node/compile.ts
4834
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/node/compile.ts
4783
4835
  function loadColors(root) {
4784
4836
  const map = new Map;
4785
4837
  for (const l of fs2.readFileSync(path2.join(root, "LDConfig.ldr"), "latin1").split(/\r?\n/)) {
@@ -4928,6 +4980,21 @@ class Compiler {
4928
4980
  az1 = Math.max(az1, b.pos[i + 2]);
4929
4981
  }
4930
4982
  out.fullBottom = isFinite(x0) && ax0 <= x0 + 0.5 && ax1 >= x1 - 0.5 && az0 <= z0 + 0.5 && az1 >= z1 - 0.5;
4983
+ if (out.under.pos.length && !underEnclosed(out)) {
4984
+ const add = (d, s) => {
4985
+ for (const v of s)
4986
+ d.push(v);
4987
+ };
4988
+ for (const b of out.tbody ? [out.body, out.tbody] : [out.body]) {
4989
+ add(b.pos, out.under.pos);
4990
+ add(b.col, out.under.col);
4991
+ }
4992
+ if (out.cond && out.condUnder) {
4993
+ add(out.cond, out.condUnder);
4994
+ out.condUnder.length = 0;
4995
+ }
4996
+ out.under = { pos: [], col: [] };
4997
+ }
4931
4998
  if (separateStuds && isFinite(x0))
4932
4999
  out.bottomMask = enclosureMask(out);
4933
5000
  this.cache.set(ck, out);
@@ -5069,9 +5136,10 @@ class Compiler {
5069
5136
  order = n === 3 ? [0, 2, 1] : [0, 2, 1, 0, 3, 2];
5070
5137
  const rgba = colorOf(code);
5071
5138
  if (tex) {
5072
- let b = texb.get(tex.texture);
5139
+ const tk = tex.glossmap ? tex.texture + "\x00" + tex.glossmap : tex.texture;
5140
+ let b = texb.get(tk);
5073
5141
  if (!b)
5074
- texb.set(tex.texture, b = { pos: [], col: [], uv: [] });
5142
+ texb.set(tk, b = { pos: [], col: [], uv: [], texture: tex.texture, ...tex.glossmap ? { glossmap: tex.glossmap } : {} });
5075
5143
  if (Math.abs(tex.a ?? 0) >= 360)
5076
5144
  b.wrapU = true;
5077
5145
  for (let k = 0;k < order.length; k += 3) {
@@ -5097,6 +5165,33 @@ class Compiler {
5097
5165
  }
5098
5166
  }
5099
5167
  }
5168
+ function underEnclosed(c) {
5169
+ const u = c.under.pos;
5170
+ let uy0 = Infinity, uy1 = -Infinity, ux0 = Infinity, ux1 = -Infinity, uz0 = Infinity, uz1 = -Infinity;
5171
+ for (let i = 0;i < u.length; i += 3) {
5172
+ ux0 = Math.min(ux0, u[i]);
5173
+ ux1 = Math.max(ux1, u[i]);
5174
+ uy0 = Math.min(uy0, u[i + 1]);
5175
+ uy1 = Math.max(uy1, u[i + 1]);
5176
+ uz0 = Math.min(uz0, u[i + 2]);
5177
+ uz1 = Math.max(uz1, u[i + 2]);
5178
+ }
5179
+ const lo = uy0 + Math.min(0.5, (uy1 - uy0) / 4), hi = uy1 - Math.min(0.5, (uy1 - uy0) / 4);
5180
+ const p = c.body.pos;
5181
+ let bx0 = Infinity, bx1 = -Infinity, bz0 = Infinity, bz1 = -Infinity;
5182
+ for (let i = 0;i < p.length; i += 9) {
5183
+ const y0 = Math.min(p[i + 1], p[i + 4], p[i + 7]), y1 = Math.max(p[i + 1], p[i + 4], p[i + 7]);
5184
+ if (y1 <= lo || y0 >= hi)
5185
+ continue;
5186
+ for (let k = i;k < i + 9; k += 3) {
5187
+ bx0 = Math.min(bx0, p[k]);
5188
+ bx1 = Math.max(bx1, p[k]);
5189
+ bz0 = Math.min(bz0, p[k + 2]);
5190
+ bz1 = Math.max(bz1, p[k + 2]);
5191
+ }
5192
+ }
5193
+ return bx0 < ux0 + 0.01 && bx1 > ux1 - 0.01 && bz0 < uz0 + 0.01 && bz1 > uz1 - 0.01;
5194
+ }
5100
5195
  function enclosureMask(c) {
5101
5196
  const [x0, , z0, x1, y1, z1] = c.bbox;
5102
5197
  const nx = Math.round((x1 - x0) / 20), nz = Math.round((z1 - z0) / 20);
@@ -5332,11 +5427,15 @@ function encodePack(parts, studKinds, weld, extra, options = {}) {
5332
5427
  const encTex = (c) => {
5333
5428
  if (!c.texb)
5334
5429
  return {};
5430
+ const idx = (name) => {
5431
+ let i = texNames.indexOf(name);
5432
+ if (i < 0)
5433
+ i = texNames.push(name) - 1;
5434
+ return i;
5435
+ };
5335
5436
  const tex = [...c.texb].map(([name, b]) => {
5336
- let t = texNames.indexOf(name);
5337
- if (t < 0)
5338
- t = texNames.push(name) - 1;
5339
- return { t, ...b.wrapU ? { wrapU: true } : {}, ...texBucket(b) };
5437
+ const t = idx(b.texture ?? name);
5438
+ return { t, ...b.glossmap ? { g: idx(b.glossmap) } : {}, ...b.wrapU ? { wrapU: true } : {}, ...texBucket(b) };
5340
5439
  });
5341
5440
  return { tbody: bucket(c.tbody), tex };
5342
5441
  };
@@ -5362,7 +5461,7 @@ function encodePack(parts, studKinds, weld, extra, options = {}) {
5362
5461
  return Buffer.concat([head, ...chunks.map((c) => new Uint8Array(c.buffer, c.byteOffset, c.byteLength))]);
5363
5462
  }
5364
5463
 
5365
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/node/build-pack.ts
5464
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/node/build-pack.ts
5366
5465
  function buildPack(o) {
5367
5466
  const lib = new Library2(o.ldrawRoot);
5368
5467
  const refs = new Map;
@@ -5429,7 +5528,7 @@ function buildPack(o) {
5429
5528
  const warnings = [...lib.conflicts].map((n) => `${n}: defined differently by two models; the first definition is used`);
5430
5529
  return { data: encodePack(parts, kinds, comp.opts.weld, extra, { texture, lod: o.lod !== false, reorder: !!o.reorder }), parts: parts.size, studKinds: kinds.size, missing: [...missing], ...warnings.length ? { warnings } : {} };
5431
5530
  }
5432
- // ../../../../../../../tmp/ldr-build-ogMgu7/src/node/promote.ts
5531
+ // ../../../../../../../tmp/ldr-build-Q8idpr/src/node/promote.ts
5433
5532
  var GEOM = /^\s*[2-5]\s/;
5434
5533
  var REF = /^\s*1\s/;
5435
5534
  var FILE = /^\s*0\s+FILE\s+(.+?)\s*$/i;
@@ -5723,10 +5822,12 @@ export {
5723
5822
  looseSteps,
5724
5823
  makeFinishMaterial,
5725
5824
  makeMaterial,
5825
+ makeTexturedMaterial,
5726
5826
  mergePacks,
5727
5827
  modelFileName,
5728
5828
  mul,
5729
5829
  norm,
5830
+ packTexture,
5730
5831
  parse,
5731
5832
  parseColor,
5732
5833
  parseColourLine,
@@ -5734,6 +5835,7 @@ export {
5734
5835
  parseModel,
5735
5836
  parseTexMap,
5736
5837
  planStepViews,
5838
+ pngHasAlpha,
5737
5839
  precomputeCull,
5738
5840
  prepareFlexParts,
5739
5841
  prepareModel,
@@ -30,7 +30,8 @@ export declare class Library {
30
30
  /** Flattened triangle mesh: positions + color code per tri (16 = inherit); edges as line segments.
31
31
  * `cond` stores type-5 lines as 12 floats each: endpoint0, endpoint1, control0, control1.
32
32
  * Default = what a renderer without !TEXMAP support draws (`0 !:` geometry skipped, FALLBACK geometry drawn).
33
- * `{ texmap: true }` = TEXMAP-aware geometry (START..FALLBACK incl. `0 !:` lines, FALLBACK..END skipped) plus `uv` / `texture`. */
33
+ * `{ texmap: true }` = TEXMAP-aware geometry (START..FALLBACK incl. `0 !:` lines, FALLBACK..END skipped) plus `uv` / `texture`.
34
+ * Texture slots are keyed by (texture, GLOSSMAP): `textures[i]` is the PNG, `glossmaps[i]` its glossmap (only set when some map has one). */
34
35
  mesh(name: string, opts?: {
35
36
  texmap?: boolean;
36
37
  }): PartMesh;
package/dist/render.d.ts CHANGED
@@ -35,11 +35,21 @@ export declare function loadPack(url: string): Promise<Pack>;
35
35
  export declare function makeMaterial(side: THREE.Side): THREE.MeshStandardMaterial;
36
36
  /** Build a MeshStandard/Physical material for one LDraw finish bucket (vertex colours supply the RGB). */
37
37
  export declare function makeFinishMaterial(side: THREE.Side, surface: ColourSurface): THREE.MeshStandardMaterial;
38
+ /** A !TEXMAP GLOSSMAP texture: `alpha` = the PNG has an alpha channel (gloss = A, per the LDraw spec); false = single-channel /
39
+ * RGB without alpha, where the grey (red) value is the gloss amount. */
40
+ export type GlossMap = {
41
+ map: THREE.Texture;
42
+ alpha: boolean;
43
+ };
38
44
  /** Material for textured (!TEXMAP) triangles: the LDraw colour as base, the PNG blended over it by its alpha (LDraw spec),
39
- * and no texture outside the projection's [0,1] range. `map` should have flipY = false (v = 0 is the image's top row). */
40
- export declare function makeTexturedMaterial(side: THREE.Side, map: THREE.Texture, wrapU?: boolean, baseMat?: THREE.MeshStandardMaterial): THREE.MeshStandardMaterial;
41
- /** Decode a pack's embedded PNG into a texture (async; the texture fills in when decoded). Needs `createImageBitmap` (browsers, workers). */
42
- export declare function packTexture(pack: Pack, i: number): {
45
+ * and no texture outside the projection's [0,1] range. `map` should have flipY = false (v = 0 is the image's top row).
46
+ * `gloss` (GLOSSMAP): sampled with the same UVs and inside mask; its amount adds specularity on top of the base finish. */
47
+ export declare function makeTexturedMaterial(side: THREE.Side, map: THREE.Texture, wrapU?: boolean, baseMat?: THREE.MeshStandardMaterial, gloss?: GlossMap): THREE.MeshStandardMaterial;
48
+ /** True if the PNG has an alpha channel: colour type 4 / 6 (grey / RGB + alpha), or 0 / 2 / 3 with a tRNS chunk. False for non-PNG bytes. */
49
+ export declare function pngHasAlpha(b: Uint8Array): boolean;
50
+ /** Decode a pack's embedded PNG into a texture (async; the texture fills in when decoded). Needs `createImageBitmap` (browsers, workers).
51
+ * `data`: a non-colour map (GLOSSMAP), sampled linearly instead of as sRGB. */
52
+ export declare function packTexture(pack: Pack, i: number, data?: boolean): {
43
53
  texture: THREE.Texture;
44
54
  ready: Promise<void>;
45
55
  };
package/dist/scene.d.ts CHANGED
@@ -76,9 +76,11 @@ export interface LdrStats {
76
76
  buildYields?: number;
77
77
  /** Conditional-line batches (draw calls) created; 0 for packs without conditional lines. */
78
78
  condBatches?: number;
79
- /** Textured batches (one per part x texture) and distinct textures; 0 for packs without textures. */
79
+ /** Textured batches (one per part x texture) and distinct textures; 0 for packs without textures.
80
+ * `glossmaps`: distinct !TEXMAP GLOSSMAP textures in use (Round 44); 0 when no map has one. */
80
81
  texBatches?: number;
81
82
  textures?: number;
83
+ glossmaps?: number;
82
84
  /** Drawable clusters (LOD / frustum-culling units). */
83
85
  clusters?: number;
84
86
  }
package/dist/spec.d.ts CHANGED
@@ -149,6 +149,8 @@ export interface PartMesh {
149
149
  /** Only with texmap: per-triangle index into `textures` (-1 = untextured). */
150
150
  texture?: Int32Array;
151
151
  textures?: string[];
152
+ /** Only with texmap, when some map has a GLOSSMAP: the glossmap PNG name per `textures` slot (undefined = none). */
153
+ glossmaps?: (string | undefined)[];
152
154
  }
153
155
  /** A placed part from `Library.flatten` (same fields as `PartInstance`, plus library resolution). */
154
156
  export interface Instance {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ldr",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
4
4
  "description": "High-performance LEGO (LDraw) rendering for three.js: instanced parts packs, hidden stud/tube culling, low-detail studs, indexed edges, build steps.",
5
5
  "license": "MIT",
6
6
  "author": "Evan Bacon",