@layoutit/polycss-core 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -25,7 +25,7 @@ type MeshResolution = "lossless" | "lossy";
25
25
  * and the difference adds up. Destructure with `const [x, y, z] = v` when
26
26
  * you need named axes.
27
27
  *
28
- * Polycss world space convention: +X right, +Y forward, +Z up.
28
+ * PolyCSS world space convention: +X right, +Y forward, +Z up.
29
29
  */
30
30
  type Vec3 = [number, number, number];
31
31
  /**
@@ -39,6 +39,12 @@ interface TextureTriangle {
39
39
  vertices: [Vec3, Vec3, Vec3];
40
40
  uvs: [Vec2, Vec2, Vec2];
41
41
  }
42
+ type PolyTextureWrapMode = "repeat" | "clamp-to-edge" | "mirrored-repeat";
43
+ interface PolyTextureWrap {
44
+ s: PolyTextureWrapMode;
45
+ t: PolyTextureWrapMode;
46
+ }
47
+ type PolyTextureAlphaMode = "opaque" | "mask" | "blend";
42
48
  /**
43
49
  * Directional light — simulates a single distant source (sun, key light).
44
50
  * Contributes Lambert shading scaled by `intensity`. `direction` is in
@@ -70,7 +76,7 @@ interface PolyAmbientLight {
70
76
  *
71
77
  * In CSS terms, a material bundles the `background-image` source plus paint
72
78
  * config. When a polygon references a material AND its UVs form an
73
- * axis-aligned rectangle, polycss renders the polygon as an <i> with
79
+ * axis-aligned rectangle, PolyCSS renders the polygon as an <i> with
74
80
  * `background-image: url(material.texture)` directly — no per-polygon canvas
75
81
  * rasterization, browser-cached texture, mounting / unmounting one polygon
76
82
  * does not affect any other.
@@ -82,7 +88,7 @@ interface PolyAmbientLight {
82
88
  interface PolyMaterial {
83
89
  /** Image source. Anything `background-image: url(...)` can use. */
84
90
  texture: string;
85
- /** Optional unique key (used by polycss to dedupe / cache). Caller can
91
+ /** Optional unique key (used by PolyCSS to dedupe / cache). Caller can
86
92
  * pass a stable string; if omitted, the material's identity is its object
87
93
  * reference. */
88
94
  key?: string;
@@ -107,10 +113,24 @@ interface Polygon {
107
113
  * `color` (or default gray).
108
114
  */
109
115
  texture?: string;
116
+ /**
117
+ * Texture sampler wrap mode for UVs outside [0, 1]. glTF imports preserve
118
+ * sampler.wrapS / wrapT here so the atlas rasterizer can tile repeated UVs.
119
+ * When unset, renderers keep the historical single-image behavior.
120
+ * @internal
121
+ */
122
+ textureWrap?: PolyTextureWrap;
123
+ /**
124
+ * Texture alpha interpretation imported from glTF `material.alphaMode`.
125
+ * Opaque textures can use transparent PNG padding without cutting holes in
126
+ * the rendered polygon.
127
+ * @internal
128
+ */
129
+ textureAlphaMode?: PolyTextureAlphaMode;
110
130
  /**
111
131
  * Shared material. When set, `material.texture` takes precedence over the
112
132
  * inline `texture` field. If the polygon's UVs form an axis-aligned
113
- * rectangle, polycss uses the direct CSS background-image path (no per-
133
+ * rectangle, PolyCSS uses the direct CSS background-image path (no per-
114
134
  * polygon canvas rasterization). Falls back to the atlas path otherwise.
115
135
  */
116
136
  material?: PolyMaterial;
@@ -127,6 +147,26 @@ interface Polygon {
127
147
  * @internal
128
148
  */
129
149
  textureTriangles?: TextureTriangle[];
150
+ /**
151
+ * Import-time simplifier seam keys retained after solid texture baking.
152
+ * Renderers ignore this; mesh optimization uses it to avoid welding vertices
153
+ * that shared a position but came from different texture/attribute seams.
154
+ * @internal
155
+ */
156
+ simplifyVertexKeys?: string[];
157
+ /**
158
+ * Stricter import-time simplifier keys that preserve source vertex identity.
159
+ * Renderers ignore this; candidate simplification uses it only for fallback
160
+ * passes where relaxed seam welding loses after render-cost optimization.
161
+ * @internal
162
+ */
163
+ simplifySourceVertexKeys?: string[];
164
+ /**
165
+ * Source material requested two-sided rendering. Importers use this so
166
+ * optimization passes do not collapse intentional reverse-wound faces.
167
+ * @internal
168
+ */
169
+ doubleSided?: boolean;
130
170
  /**
131
171
  * User-controlled metadata. Reflected to DOM as `data-*` attributes via
132
172
  * stringification by the framework wrappers. Only string|number|boolean
@@ -157,8 +197,8 @@ declare function normalizePolygons(input: Polygon[]): NormalizeResult;
157
197
  * (already normalized) and returns the data the framework wrappers need
158
198
  * to render.
159
199
  *
160
- * No cube grid, no per-Z layer bucketing, no wall mask, no neighbor-based
161
- * occlusion. Just a polygon list and a scene bbox.
200
+ * The renderer consumes a flat polygon list plus a scene bbox; higher-level
201
+ * culling or mesh optimization happens before this context is built.
162
202
  */
163
203
 
164
204
  interface SceneBbox {
@@ -210,10 +250,8 @@ declare function buildSceneContext(args: SceneContextBuildArgs): SceneContextBui
210
250
 
211
251
  /**
212
252
  * Polygon geometry helpers — pure math operating on Polygon vertices.
213
- *
214
- * After cube removal in Phase 2, this module carries small polygon-level
215
- * helpers for downstream consumers (lighting, debug metrics, etc.). The
216
- * cube / ramp / wedge / spike face emitters lived here in voxcss; they're gone.
253
+ * These helpers feed lighting, diagnostics, and renderer metrics without
254
+ * depending on browser APIs.
217
255
  */
218
256
 
219
257
  interface PolygonFace {
@@ -252,8 +290,7 @@ interface TexturePaintMetrics {
252
290
  }
253
291
  /**
254
292
  * Surface a polygon as a single face. The returned array always has length 1;
255
- * the indirection exists so callers that historically iterated faces (e.g.
256
- * the manifold check, the canvas validator) can keep their loop shape.
293
+ * the indirection lets face-oriented consumers keep a common loop shape.
257
294
  *
258
295
  * Returns an empty array for degenerate polygons (< 3 vertices).
259
296
  */
@@ -262,7 +299,7 @@ declare function computeTexturePaintMetrics(polygons: Polygon[], options?: Textu
262
299
 
263
300
  /**
264
301
  * Apply CSS-style chained `rotateX(rx) rotateY(ry) rotateZ(rz)` rotation
265
- * to a 3D vector. Matches the matrix composition used by polycss mesh
302
+ * to a 3D vector. Matches the matrix composition used by PolyCSS mesh
266
303
  * wrapper transforms (see `buildTransform` in each PolyMesh implementation).
267
304
  *
268
305
  * CSS composes `transform: rotateX(rx) rotateY(ry) rotateZ(rz)` as the
@@ -287,6 +324,20 @@ declare function rotateVec3(v: Vec3, rxDeg: number, ryDeg: number, rzDeg: number
287
324
  * `rot` is `[rxDeg, ryDeg, rzDeg]` matching the mesh's CSS rotation prop.
288
325
  */
289
326
  declare function inverseRotateVec3(v: Vec3, rot: Vec3): Vec3;
327
+ /**
328
+ * Apply the wrapper's actual CSS rotation matrix to a CSS-frame vector,
329
+ * given the user-supplied world rotation tuple `[rx_w, ry_w, rz_w]`.
330
+ *
331
+ * The wrapper emits `rotateY(-rx_w) rotateX(-ry_w) rotateZ(-rz_w)` (the
332
+ * world↔CSS reflection conjugation of three.js XYZ Euler). That matrix
333
+ * is `Ry(-rx_w) · Rx(-ry_w) · Rz(-rz_w) · v` applied to a vector. Use
334
+ * this anywhere downstream code needs to transform a CSS-frame normal or
335
+ * point through the same rotation the wrapper applies — e.g. back-face
336
+ * culling, voxel item ordering. NOT for inverse-rotating a world light
337
+ * direction into mesh-local frame — that still uses `inverseRotateVec3`
338
+ * with the user's world rotation, since it operates in world frame.
339
+ */
340
+ declare function rotateVec3InWrapperCssFrame(v: Vec3, worldRot: Vec3): Vec3;
290
341
 
291
342
  /**
292
343
  * Minimal quaternion helpers for composing rotations.
@@ -334,7 +385,7 @@ declare function quatFromEulerXYZ(eulerDeg: Vec3): Quat;
334
385
  declare function eulerXYZFromQuat(q: Quat): Vec3;
335
386
 
336
387
  /**
337
- * Base tile size in CSS pixels. One polycss world unit = BASE_TILE CSS
388
+ * Base tile size in CSS pixels. One PolyCSS world unit = BASE_TILE CSS
338
389
  * pixels (pre-scale). Used to convert world-coordinate target values to
339
390
  * CSS translations in the transform string.
340
391
  */
@@ -349,7 +400,7 @@ type AutoRotateOption = boolean | number | AutoRotateConfig;
349
400
  * World-coordinate camera state (Three.js-style).
350
401
  *
351
402
  * `target` is the world point that should appear at the viewport centre.
352
- * Polycss world axes: [0]=X (rows/south), [1]=Y (cols/east), [2]=Z (up).
403
+ * PolyCSS world axes: [0]=X (rows/south), [1]=Y (cols/east), [2]=Z (up).
353
404
  *
354
405
  * `pan`, `tilt`, and `depthOffset` are gone. Translations now live inside
355
406
  * `target` so they happen BEFORE rotations — enabling correct world-space
@@ -358,7 +409,7 @@ type AutoRotateOption = boolean | number | AutoRotateConfig;
358
409
  * `distance` is the camera's pull-back from the target in CSS pixels.
359
410
  * Increasing distance moves the camera farther from the target along the
360
411
  * view axis (dolly out) — analogous to three.js's spherical radius.
361
- * Default 0 keeps the legacy behaviour unchanged.
412
+ * Default 0 keeps orthographic/isometric scenes flat.
362
413
  */
363
414
  interface CameraState {
364
415
  target: Vec3;
@@ -385,6 +436,66 @@ declare function normalizeInvertMultiplier(value: number | boolean | undefined):
385
436
  declare const DEFAULT_CAMERA_STATE: CameraState;
386
437
  declare function createIsometricCamera(initial?: Partial<CameraState>): CameraHandle;
387
438
 
439
+ /**
440
+ * Screen → world inverse projection helpers.
441
+ *
442
+ * The camera transform a CameraHandle emits is:
443
+ *
444
+ * `translateZ(-distance) scale(zoom/tile) rotateX(rotX) rotate(rotY) translate3d(-cssX, -cssY, -cssZ)`
445
+ *
446
+ * (right-to-left for point transforms). For an orthographic camera the
447
+ * depth component drops out at projection time, so a screen position
448
+ * (mx, my) unprojects to a 3D RAY in world space — every depth value
449
+ * along that ray maps to the same screen position. Callers pick a
450
+ * specific 3D point by intersecting the ray with a surface they care
451
+ * about (here: a sphere).
452
+ *
453
+ * `screenToWorldOnSphere` is the common case: given a mouse position,
454
+ * find where on a fixed sphere (e.g. the draggable light marker's
455
+ * sphere of constant radius) the cursor is pointing. Returns null
456
+ * when the ray misses the sphere.
457
+ */
458
+
459
+ interface ScreenToWorldOptions {
460
+ /** Camera state — rotX/rotY/zoom/target, same shape `CameraHandle.state` exposes. */
461
+ camera: CameraState;
462
+ /** World-units → CSS-px factor. Pass `DEFAULT_TILE` from the renderer. */
463
+ tileSize: number;
464
+ /** Viewport center in CSS pixels (screen coordinates). The mouse position
465
+ * is interpreted relative to this point. */
466
+ viewportCenterX: number;
467
+ viewportCenterY: number;
468
+ /** Mouse screen position (CSS pixels). */
469
+ mouseX: number;
470
+ mouseY: number;
471
+ }
472
+ /**
473
+ * Unproject the cursor to a ray in world coordinates.
474
+ *
475
+ * `ray(t) = origin + t · direction`
476
+ *
477
+ * `origin` is the world point that screen-coincides with the mouse at
478
+ * depth t=0; `direction` is the camera-forward axis in world frame.
479
+ * For orthographic cameras the ray is parallel — every point along it
480
+ * projects to the same screen position.
481
+ */
482
+ declare function screenToWorldRay(opts: ScreenToWorldOptions): {
483
+ origin: Vec3;
484
+ direction: Vec3;
485
+ };
486
+ /**
487
+ * Unproject the cursor and intersect with a sphere in world coords.
488
+ * Returns the FRONT intersection (the one closer to the camera) when
489
+ * the ray hits the sphere. When the ray misses (cursor outside the
490
+ * projected silhouette), returns the point on the sphere's silhouette
491
+ * closest to the cursor — that way callers like draggable helpers keep
492
+ * tracking the cursor along the rim instead of freezing in place.
493
+ */
494
+ declare function screenToWorldOnSphere(opts: ScreenToWorldOptions & {
495
+ sphereCenter: Vec3;
496
+ sphereRadius: number;
497
+ }): Vec3 | null;
498
+
388
499
  interface ParsedColor {
389
500
  rgb: [number, number, number];
390
501
  alpha: number;
@@ -421,9 +532,8 @@ declare function computeShapeLighting(normal: Vec3, baseColor: string, direction
421
532
  /**
422
533
  * Merge coplanar same-color adjacent triangles into N-vertex polygons.
423
534
  *
424
- * Each polygon is rendered as one atlas-backed DOM element — so a mesh whose
425
- * triangles came from quads or pentagons collapses back into its original
426
- * face count.
535
+ * Each visible polygon renders as one DOM leaf — so a mesh whose triangles
536
+ * came from quads or pentagons collapses back into its original face count.
427
537
  *
428
538
  * - Geodesic spheres: ~half the triangles came from quad subdivisions
429
539
  * - OBJ imports: many were quads/n-gons fan-triangulated by the importer
@@ -454,10 +564,9 @@ declare function mergePolygons(input: Polygon[]): Polygon[];
454
564
  * Why this exists: modelers (and importers) often emit redundant geometry
455
565
  * for the same visible surface — a doubled face on a wall, an inner shell
456
566
  * coincident with an outer shell, or two N-gons that fan-triangulate the
457
- * same region. Each duplicate is a real `<i>` element at render time:
458
- * it costs DOM, Lambert math, atlas budget, AND it produces stacked
459
- * shadow leaves that visibly multiply on the receiver (overlapping dark
460
- * patches on the ground).
567
+ * same region. Each duplicate is a real render leaf at render time:
568
+ * it costs DOM, Lambert math, atlas budget, and redundant shadow projection
569
+ * work.
461
570
  *
462
571
  * This is a separate concern from `cullInteriorPolygons` (which removes
463
572
  * polygons fully *enclosed* by other geometry, conservative against
@@ -509,6 +618,10 @@ interface DedupeOverlappingPolygonsOptions {
509
618
  * containment ratios) for a pair to count as a duplicate.
510
619
  * Default 0.7. Lower (~0.4) is liberal; higher (~0.9) is strict. */
511
620
  overlapFraction?: number;
621
+ /** Preserve reverse-wound faces from authored double-sided materials.
622
+ * Geometry dedupe keeps these by default; shadow dedupe can disable it
623
+ * because coincident front/back casters produce stacked shadows. */
624
+ preserveDoubleSidedBackfaces?: boolean;
512
625
  }
513
626
  /** Identify polygons that are duplicates within tolerance. Returns the
514
627
  * set of indices into the input array that should be dropped (the
@@ -517,9 +630,8 @@ interface DedupeOverlappingPolygonsOptions {
517
630
  * larger area as a tiebreaker.
518
631
  *
519
632
  * Exposed for callers that want to act on the index set directly —
520
- * e.g. shadow casting can use a looser tolerance to skip shadow leaves
521
- * for redundant casters without removing them from the renderable
522
- * polygon set. */
633
+ * e.g. shadow casting can use a looser tolerance to skip redundant caster
634
+ * projections without removing them from the renderable polygon set. */
523
635
  declare function findOverlappingPolygonDuplicates(input: Polygon[], options?: DedupeOverlappingPolygonsOptions): Set<number>;
524
636
  declare function dedupeOverlappingPolygons(input: Polygon[], options?: DedupeOverlappingPolygonsOptions): Polygon[];
525
637
 
@@ -542,29 +654,271 @@ interface CoverPlanarPolygonsOptions {
542
654
  */
543
655
  declare function coverPlanarPolygons(input: Polygon[], options?: CoverPlanarPolygonsOptions): Polygon[];
544
656
 
545
- interface ApproximateMergeOptions {
546
- maxAngleDeg?: number;
547
- maxPlaneDisplacement?: number;
548
- maxBoundaryDisplacement?: number;
549
- isolatedPairs?: boolean;
657
+ interface SimplifyTriangleMeshPolygonsOptions {
658
+ /** Target triangle ratio per eligible connected material group. Default 0.7. */
659
+ ratio?: number;
660
+ /** Maximum accepted local plane displacement in scene units. Default 0.18. */
661
+ maxError?: number;
662
+ /** Reject collapses that rotate any affected face beyond this angle. Default 65. */
663
+ maxNormalAngleDeg?: number;
664
+ /** Only collapse vertices onto existing endpoint positions. Default true. */
665
+ preserveVertices?: boolean;
666
+ /** Use stricter importer source-vertex keys instead of relaxed seam keys. Default "relaxed". */
667
+ vertexKeyMode?: "relaxed" | "source";
668
+ /** Keep topological/material boundary vertices fixed. Default true. */
669
+ lockBoundary?: boolean;
670
+ /** Skip small groups where decimation is unlikely to repay the mutation cost. Default 80. */
671
+ minGroupTriangles?: number;
672
+ /** Hard cap for rebuild passes. Default 12. */
673
+ maxPasses?: number;
550
674
  }
675
+ /**
676
+ * Import-time triangle decimation for already-solid meshes.
677
+ *
678
+ * This is deliberately conservative: textured polygons and material/color
679
+ * boundaries are kept out of the collapse graph, endpoint-preserving collapses
680
+ * mirror meshoptimizer's index-buffer simplification shape by default, and
681
+ * callers can cheaply compare the returned candidate against their normal
682
+ * render-cost optimizer before accepting it.
683
+ */
684
+ declare function simplifyTriangleMeshPolygons(polygons: Polygon[], options?: SimplifyTriangleMeshPolygonsOptions): Polygon[];
685
+
551
686
  interface OptimizeMeshPolygonsOptions {
552
687
  /** Public quality/resolution intent. Defaults to "lossy". */
553
688
  meshResolution?: MeshResolution;
689
+ /**
690
+ * Return as soon as the optimizer finds a result with at most this many
691
+ * polygons. Useful for candidate comparisons where the caller already knows
692
+ * the maximum DOM leaf count it can accept.
693
+ */
694
+ stopAtPolygonCount?: number;
554
695
  /**
555
696
  * Run the planar cover pass as an exact candidate for untextured coplanar
556
697
  * regions. Defaults to true.
557
698
  */
558
699
  rectCover?: boolean | CoverPlanarPolygonsOptions;
700
+ }
701
+ declare function optimizeMeshPolygons(polygons: Polygon[], options?: OptimizeMeshPolygonsOptions): Polygon[];
702
+
703
+ /**
704
+ * Unified parser return type. All polygon-emitting parsers (parseObj,
705
+ * parseGltf, parseVox, parseStl, the loadMesh dispatcher) return this exact shape.
706
+ *
707
+ * The asymmetric helper `parseMtl` returns its own `MtlParseResult` (it
708
+ * emits materials, not polygons) — see parseMtl.ts for the rationale.
709
+ *
710
+ * Lifecycle contract: callers MUST call `dispose()` when the result is no
711
+ * longer needed. Idempotent — safe to call on unmount even if `objectUrls`
712
+ * is empty (e.g. `parseObj`, where it's a no-op).
713
+ */
714
+
715
+ interface PolyVoxelCell {
716
+ x: number;
717
+ y: number;
718
+ z: number;
719
+ color: string;
720
+ }
721
+ interface PolyVoxelSource {
722
+ kind: "magica-vox";
723
+ cells: PolyVoxelCell[];
724
+ rows: number;
725
+ cols: number;
726
+ depth: number;
727
+ scale: number;
728
+ sourceBytes: number;
729
+ }
730
+ interface ParseStlTopology {
731
+ componentCount: number;
732
+ repairedTriangleCount: number;
733
+ outwardComponentCount: number;
734
+ suppliedNormalComponentCount: number;
735
+ inconsistentSharedEdgeCount: number;
736
+ nonManifoldSharedEdgeCount: number;
737
+ }
738
+ interface ParseStlColor {
739
+ format: "magics";
740
+ defaultColor: string;
741
+ alpha: number;
742
+ coloredTriangleCount: number;
743
+ defaultColorTriangleCount: number;
744
+ }
745
+ interface ParseStlSolid {
746
+ name: string;
747
+ start: number;
748
+ count: number;
749
+ }
750
+ interface ParseAnimationClip {
751
+ /** Stable numeric index in the source file's animation array. */
752
+ index: number;
753
+ /** Human-readable clip name. Falls back to `animation_N` when omitted. */
754
+ name: string;
755
+ /** Clip duration in seconds, derived from its sampler input accessors. */
756
+ duration: number;
757
+ /** Number of glTF animation channels in the clip. */
758
+ channelCount: number;
759
+ }
760
+ interface ParseAnimationController {
761
+ /** Animation clips exposed by the parsed mesh. Empty when none are usable. */
762
+ clips: ParseAnimationClip[];
559
763
  /**
560
- * Lossy approximate merge settings. Ignored for lossless resolution.
561
- * When omitted, lossy evaluates isolated-pair and small plane-group
562
- * strategies, then chooses the lowest render-cost result with a near-cost
563
- * preference for candidates that reduce detected internal gaps.
764
+ * Sample a clip at `timeSeconds` and return a fresh polygon list.
765
+ * `clip` accepts either the clip index or its name. Time wraps by duration.
564
766
  */
565
- approximateMerge?: boolean | ApproximateMergeOptions;
767
+ sample: (clip: number | string, timeSeconds: number) => Polygon[];
566
768
  }
567
- declare function optimizeMeshPolygons(polygons: Polygon[], options?: OptimizeMeshPolygonsOptions): Polygon[];
769
+ interface ParseResult {
770
+ /** The mesh, as a flat polygon list. Already vertex-permuted to polycss space. */
771
+ polygons: Polygon[];
772
+ /** Optional raw voxel source for `.vox` fast paths; polygon fallback remains authoritative. */
773
+ voxelSource?: PolyVoxelSource;
774
+ /** Optional animation sampler for formats that carry timeline data. */
775
+ animation?: ParseAnimationController;
776
+ /**
777
+ * Blob/object URLs minted during parse (e.g. embedded GLB images). Pass-by-
778
+ * reference — the same array is exposed on the result for visibility, and
779
+ * `dispose()` revokes each one. Do NOT mutate this array externally.
780
+ */
781
+ objectUrls: string[];
782
+ /**
783
+ * Idempotent — revokes object URLs. Safe to call on unmount, safe to call
784
+ * twice. Parsers without minted URLs (parseObj, parseMtl) supply a no-op.
785
+ */
786
+ dispose: () => void;
787
+ /**
788
+ * Non-fatal warnings raised during parse. Empty for parsers that don't
789
+ * have a warning channel; populated when downstream `normalizePolygons`
790
+ * is invoked through the high-level pipeline.
791
+ */
792
+ warnings: string[];
793
+ /** Optional format-specific metadata. */
794
+ metadata?: {
795
+ /** Triangle count after fan-triangulation (parseObj) or post-triangulation (parseGltf). */
796
+ triangleCount?: number;
797
+ /** Mesh names from the file (for glTF, from doc.meshes[].name). */
798
+ meshes?: string[];
799
+ /** Material names (in first-seen order). */
800
+ materials?: string[];
801
+ /** Animation clips from the file, mirrored from `animation.clips`. */
802
+ animations?: ParseAnimationClip[];
803
+ /** Source file size in bytes (for diagnostics). */
804
+ sourceBytes?: number;
805
+ /** Voxel count for `.vox` sources. */
806
+ voxelCount?: number;
807
+ /** Printable binary STL header, trimmed. */
808
+ stlHeader?: string;
809
+ /** Binary STL color metadata when a supported color extension is present. */
810
+ stlColor?: ParseStlColor;
811
+ /** Consecutive ASCII `solid` groups after parse/filtering. */
812
+ stlSolids?: ParseStlSolid[];
813
+ /** STL winding/connectivity diagnostics. */
814
+ stlTopology?: ParseStlTopology;
815
+ };
816
+ }
817
+
818
+ interface OptimizeMeshParseResultOptions {
819
+ /** Render-cost optimization intent. Defaults to "lossy". */
820
+ meshResolution?: MeshResolution;
821
+ /** Parse result before solid texture baking, used to identify baked swatch faces. */
822
+ source?: ParseResult;
823
+ /** Merge near-identical baked swatch colors in lossy mode. Default 36. */
824
+ bakedTextureColorMergeDistance?: number;
825
+ /** Try static triangle simplification before final polygon optimization. Default true. */
826
+ simplifyTriangleMeshes?: boolean;
827
+ /** Options for the static triangle simplifier. */
828
+ simplifyTriangleMeshOptions?: SimplifyTriangleMeshPolygonsOptions;
829
+ /** Candidate optimizer early-stop drop target. Default 0.15. */
830
+ simplifyEarlyStopDropRatio?: number;
831
+ }
832
+ declare function optimizeMeshParseResult(result: ParseResult, options?: OptimizeMeshParseResultOptions): ParseResult;
833
+
834
+ type SeamOverlapCandidateKind = "true-gap" | "connected-facet" | "material-boundary";
835
+ interface SeamOverlapCandidate {
836
+ kind: SeamOverlapCandidateKind;
837
+ aPolygon: number;
838
+ aEdge: number;
839
+ bPolygon: number;
840
+ bEdge: number;
841
+ aColor?: string;
842
+ bColor?: string;
843
+ aMaterialKey: string;
844
+ bMaterialKey: string;
845
+ gapPx: number;
846
+ spanPx: number;
847
+ aStartPx: number;
848
+ aEndPx: number;
849
+ bStartPx: number;
850
+ bEndPx: number;
851
+ targetClosurePx: number;
852
+ appliedClosurePx: number;
853
+ residualGapPx: number;
854
+ residualTargetPx: number;
855
+ }
856
+ interface SeamOverlapDiagnostics {
857
+ exactPairs: number;
858
+ nearPairs: number;
859
+ patchedPolygons: number;
860
+ patchedEdges: number;
861
+ maxMeasuredGapPx: number;
862
+ maxAppliedAmountPx: number;
863
+ unclosedPairs: number;
864
+ maxResidualGapPx: number;
865
+ }
866
+ interface SeamOverlapOptions {
867
+ overlapPx?: number;
868
+ maxGapPx?: number;
869
+ capacityScale?: number;
870
+ }
871
+ interface SeamFacetSplitOptions {
872
+ rotX?: number;
873
+ rotY?: number;
874
+ viewAware?: boolean;
875
+ passes?: number;
876
+ budget?: number;
877
+ }
878
+ type SeamFacetSplitCandidateReason = "component-anchor" | "global-outlier" | "local-follow-up" | "shared-polygon" | "below-threshold";
879
+ interface SeamFacetSplitCandidate {
880
+ key: string;
881
+ aPolygon: number;
882
+ aEdge: number;
883
+ bPolygon: number;
884
+ bEdge: number;
885
+ color?: string;
886
+ materialKey: string;
887
+ lengthPx: number;
888
+ projectedLengthPx: number;
889
+ score: number;
890
+ normalRisk: number;
891
+ shapeRisk: number;
892
+ viewRisk: number;
893
+ component: number;
894
+ marginalCost: number;
895
+ selected: boolean;
896
+ reason: SeamFacetSplitCandidateReason;
897
+ }
898
+ interface SeamFacetSplitReport {
899
+ candidates: SeamFacetSplitCandidate[];
900
+ selectedPolygons: number;
901
+ selectedEdges: number;
902
+ addedPolygons: number;
903
+ }
904
+
905
+ declare const DEFAULT_SEAM_OVERLAP_OPTIONS: {
906
+ readonly overlapPx: 1.25;
907
+ readonly maxGapPx: 14;
908
+ readonly capacityScale: 1;
909
+ };
910
+ declare const DEFAULT_SEAM_FACET_SPLIT_OPTIONS: {
911
+ readonly budget: 40;
912
+ };
913
+ declare function seamFacetSplitPolygons(polygons: Polygon[], seamOptions?: number | SeamOverlapOptions, splitOptions?: SeamFacetSplitOptions): Polygon[];
914
+ declare function seamFacetSplitReport(polygons: Polygon[], seamOptions?: number | SeamOverlapOptions, splitOptions?: SeamFacetSplitOptions): SeamFacetSplitReport;
915
+ declare function seamOverlapPolygons(polygons: Polygon[], options?: number | SeamOverlapOptions): Polygon[];
916
+ declare function repairMeshSeams(polygons: Polygon[], seamOptions?: number | SeamOverlapOptions, splitOptions?: SeamFacetSplitOptions): Polygon[];
917
+ declare function seamOverlapDiagnostics(polygons: Polygon[], options?: number | SeamOverlapOptions): SeamOverlapDiagnostics;
918
+ declare function seamOverlapReport(polygons: Polygon[], options?: number | SeamOverlapOptions): {
919
+ diagnostics: SeamOverlapDiagnostics;
920
+ candidates: SeamOverlapCandidate[];
921
+ };
568
922
 
569
923
  /**
570
924
  * cullInteriorPolygons — remove polygons that are fully enclosed by other
@@ -588,11 +942,58 @@ declare function optimizeMeshPolygons(polygons: Polygon[], options?: OptimizeMes
588
942
  */
589
943
 
590
944
  interface CullInteriorOptions {
591
- /** Hemisphere ray samples per polygon. Higher = fewer false positives, slower. Default 12. */
945
+ /** Hemisphere ray samples per polygon. Higher = fewer false positives, slower. Default 8. */
592
946
  samples?: number;
947
+ /** Bypass the "large open topology" safety bail-out. By default a mesh
948
+ * with >20% boundary edges is treated as open (terrain, partial geometry)
949
+ * and culling is skipped to avoid false-positive removals on exposed faces.
950
+ * Buildings imported from OBJ commonly have windows/doors that push the
951
+ * boundary ratio above the threshold while STILL having clearly enclosed
952
+ * interior walls that should be culled — set this when the caller has
953
+ * another signal (e.g. mesh is known to be a building / room) that
954
+ * interior culling is desired. Default false. */
955
+ force?: boolean;
956
+ /** Fraction of sample directions that must find at least one escaping
957
+ * origin for the polygon to count as visible. Default `1 / samples`
958
+ * (the original "any escape keeps" behaviour). Raise to ~0.5 to
959
+ * classify polygons as interior when MOST hemisphere directions are
960
+ * blocked — catches interior panels of building meshes (the cottage's
961
+ * porch trim, back-of-wall door frames) that have a sliver of clear
962
+ * sky through windows/openings but are otherwise enclosed. */
963
+ minEscapeRatio?: number;
593
964
  }
594
965
  declare function cullInteriorPolygons(polygons: Polygon[], options?: CullInteriorOptions): Polygon[];
595
966
 
967
+ /**
968
+ * computeLightVisibility — for each polygon of a mesh, determines whether
969
+ * direct light from a given direction physically reaches it (ray from the
970
+ * polygon's centroid toward the light source is unblocked) or is occluded
971
+ * by other geometry of the same mesh.
972
+ *
973
+ * This is the CPU equivalent of one shadow-map sample per polygon from the
974
+ * light's POV. Used by the baked atlas pipeline so polygons in shadow get
975
+ * lit with ambient-only color, matching Three.js's depth-pass occlusion.
976
+ *
977
+ * Algorithm: Möller-Trumbore ray-triangle intersection per candidate, with
978
+ * a flat-array BVH for any-hit traversal. Cost: O(F log F) per mesh per
979
+ * light direction. Cottage at ~240 polys: ~5-10 ms. Caller should cache by
980
+ * (mesh geometry version, lightDir hash) and only recompute on change.
981
+ */
982
+
983
+ /**
984
+ * Returns a Set of polygon indices that are OCCLUDED from the light
985
+ * (a ray from the polygon's centroid in the +lightDir direction hits
986
+ * another polygon of the same mesh before escaping to infinity).
987
+ *
988
+ * `lightDir` is the direction TO the light source (matching the
989
+ * convention used by `shadePolygon`'s caller — points from the
990
+ * surface toward the light).
991
+ *
992
+ * Caller should cache by mesh-geometry version + lightDir hash;
993
+ * recompute only when either changes.
994
+ */
995
+ declare function computeLightVisibility(polygons: readonly Polygon[], lightDir: Vec3, skipIndices?: ReadonlySet<number>): Set<number>;
996
+
596
997
  declare const CAMERA_BACKFACE_CULL_EPS = 0.00001;
597
998
  declare const VOXEL_CAMERA_CULL_AXIS_EPS = 0.001;
598
999
  declare const VOXEL_CAMERA_CULL_NORMAL_LIMIT = 6;
@@ -621,7 +1022,7 @@ declare function cameraCullVisibleSignature(groups: readonly CameraCullNormalGro
621
1022
  * cuboids stretching along world-X, world-Y and world-Z. Mirrors the
622
1023
  * convention `red=X, green=Y, blue=Z`.
623
1024
  *
624
- * Returned polygons are in the standard polycss world-space convention
1025
+ * Returned polygons are in the standard PolyCSS world-space convention
625
1026
  * (`+X right, +Y forward, +Z up`). Wrap with the framework's PolyMesh /
626
1027
  * PolyScene equivalent to render.
627
1028
  */
@@ -650,7 +1051,7 @@ declare function axesHelperPolygons(options?: AxesHelperOptions): Polygon[];
650
1051
  /**
651
1052
  * Axis-aligned box/cuboid geometry as six quad polygons.
652
1053
  *
653
- * Returned polygons are in standard polycss world space:
1054
+ * Returned polygons are in standard PolyCSS world space:
654
1055
  * +X = right, +Y = front/forward, +Z = top/up.
655
1056
  */
656
1057
 
@@ -678,7 +1079,7 @@ declare function boxPolygons(options?: BoxPolygonsOptions): Polygon[];
678
1079
  * drag handle for `<TransformControls>` — same primitive recipe as
679
1080
  * `axesHelperPolygons`, plus an arrowhead.
680
1081
  *
681
- * Returned polygons are in standard polycss world space and intended
1082
+ * Returned polygons are in standard PolyCSS world space and intended
682
1083
  * to be wrapped in the framework's PolyMesh equivalent for rendering.
683
1084
  */
684
1085
 
@@ -717,7 +1118,7 @@ declare function arrowPolygons(options: ArrowPolygonsOptions): Polygon[];
717
1118
  * "rotation circle" and keeps the polygon count proportional to the
718
1119
  * `segments` knob.
719
1120
  *
720
- * Returned polygons are in standard polycss world space and intended
1121
+ * Returned polygons are in standard PolyCSS world space and intended
721
1122
  * to be wrapped in the framework's PolyMesh equivalent for rendering.
722
1123
  */
723
1124
 
@@ -775,7 +1176,7 @@ declare function ringQuadPolygons(options: RingQuadPolygonsOptions): Polygon[];
775
1176
  * attached mesh along two axes simultaneously (XY, XZ, or YZ), instead of
776
1177
  * the single-axis motion the arrow shafts provide.
777
1178
  *
778
- * The polygon lives in standard polycss world space; wrap it in the
1179
+ * The polygon lives in standard PolyCSS world space; wrap it in the
779
1180
  * framework's PolyMesh equivalent for rendering.
780
1181
  */
781
1182
 
@@ -817,92 +1218,569 @@ interface OctahedronPolygonsOptions {
817
1218
  declare function octahedronPolygons(options: OctahedronPolygonsOptions): Polygon[];
818
1219
 
819
1220
  /**
820
- * Unified parser return type. All polygon-emitting parsers (parseObj,
821
- * parseGltf, the loadMesh dispatcher) return this exact shape.
1221
+ * Icosphere (subdivided icosahedron) geometry approximates a sphere with
1222
+ * triangular faces. Each subdivision step quadruples the face count:
1223
+ * subdivisions 0 → 20, 1 → 80, 2 → 320, 3 → 1280 (capped).
822
1224
  *
823
- * The asymmetric helper `parseMtl` returns its own `MtlParseResult` (it
824
- * emits materials, not polygons) see parseMtl.ts for the rationale.
1225
+ * Vertex coordinates use PolyCSS world space: +X right, +Y forward, +Z up.
1226
+ * The sphere is centered at the origin; all vertices sit at distance `radius`.
1227
+ * Faces wind CCW from the outside (outward normal = away from origin).
1228
+ */
1229
+
1230
+ interface SpherePolygonsOptions {
1231
+ /** Radius of the sphere. Default 50. */
1232
+ radius?: number;
1233
+ /** Subdivision level (0 = bare icosahedron, 20 triangles; each +1 quadruples count). Default 1 → 80 triangles. Cap at 3 (1280 triangles). */
1234
+ subdivisions?: number;
1235
+ /** Fill color applied to all faces. */
1236
+ color?: string;
1237
+ }
1238
+ declare function spherePolygons(options?: SpherePolygonsOptions): Polygon[];
1239
+
1240
+ /**
1241
+ * Regular tetrahedron geometry — four equilateral triangular faces.
825
1242
  *
826
- * Lifecycle contract: callers MUST call `dispose()` when the result is no
827
- * longer needed. Idempotent safe to call on unmount even if `objectUrls`
828
- * is empty (e.g. `parseObj`, where it's a no-op).
1243
+ * Vertices are placed so the tetrahedron is centered at the origin.
1244
+ * Faces wind CCW from the outside.
1245
+ *
1246
+ * PolyCSS world space: +X right, +Y forward, +Z up.
829
1247
  */
830
1248
 
831
- interface PolyVoxelCell {
832
- x: number;
833
- y: number;
834
- z: number;
835
- color: string;
1249
+ interface TetrahedronPolygonsOptions {
1250
+ /** Circumradius: distance from center to each vertex. Default 100. */
1251
+ size?: number;
1252
+ /** Fill color applied to all four faces. */
1253
+ color?: string;
836
1254
  }
837
- interface PolyVoxelSource {
838
- kind: "magica-vox";
839
- cells: PolyVoxelCell[];
840
- rows: number;
841
- cols: number;
842
- depth: number;
843
- scale: number;
844
- gridShift: number;
845
- sourceBytes: number;
1255
+ declare function tetrahedronPolygons(options?: TetrahedronPolygonsOptions): Polygon[];
1256
+
1257
+ /**
1258
+ * Regular icosahedron geometry — 20 equilateral triangular faces.
1259
+ *
1260
+ * Vertices are placed on a sphere of radius `size` centered at the origin.
1261
+ * Faces wind CCW from the outside.
1262
+ *
1263
+ * PolyCSS world space: +X right, +Y forward, +Z up.
1264
+ */
1265
+
1266
+ interface IcosahedronPolygonsOptions {
1267
+ /** Circumradius: distance from center to each vertex. Default 100. */
1268
+ size?: number;
1269
+ /** Fill color applied to all 20 faces. */
1270
+ color?: string;
846
1271
  }
847
- interface ParseAnimationClip {
848
- /** Stable numeric index in the source file's animation array. */
849
- index: number;
850
- /** Human-readable clip name. Falls back to `animation_N` when omitted. */
851
- name: string;
852
- /** Clip duration in seconds, derived from its sampler input accessors. */
853
- duration: number;
854
- /** Number of glTF animation channels in the clip. */
855
- channelCount: number;
1272
+ declare function icosahedronPolygons(options?: IcosahedronPolygonsOptions): Polygon[];
1273
+
1274
+ /**
1275
+ * Regular dodecahedron geometry 12 regular pentagonal faces.
1276
+ *
1277
+ * Vertices are placed on a sphere of radius `size` centered at the origin.
1278
+ * Each face is a pentagon (5 vertices) wound CCW from the outside.
1279
+ *
1280
+ * All 12 pentagonal faces of a regular dodecahedron are truly planar — each
1281
+ * lies on a single tangent plane. No triangulation is needed. The renderer
1282
+ * uses <i> (border-shape) on Chromium and <s> elsewhere for non-quad polygons.
1283
+ *
1284
+ * PolyCSS world space: +X right, +Y forward, +Z up.
1285
+ */
1286
+
1287
+ interface DodecahedronPolygonsOptions {
1288
+ /** Circumradius: distance from center to each vertex. Default 100. */
1289
+ size?: number;
1290
+ /** Fill color applied to all 12 faces. */
1291
+ color?: string;
856
1292
  }
857
- interface ParseAnimationController {
858
- /** Animation clips exposed by the parsed mesh. Empty when none are usable. */
859
- clips: ParseAnimationClip[];
860
- /**
861
- * Sample a clip at `timeSeconds` and return a fresh polygon list.
862
- * `clip` accepts either the clip index or its name. Time wraps by duration.
863
- */
864
- sample: (clip: number | string, timeSeconds: number) => Polygon[];
1293
+ declare function dodecahedronPolygons(options?: DodecahedronPolygonsOptions): Polygon[];
1294
+
1295
+ /**
1296
+ * Z-axis cylinder geometry with optional radius taper.
1297
+ *
1298
+ * Geometry:
1299
+ * - `radialSegments` side faces (quads for cylinders/frustums, triangles
1300
+ * when one radius collapses to a cone tip).
1301
+ * - `radialSegments` bottom-cap triangles (fan from center).
1302
+ * - `radialSegments` top-cap triangles (fan from center), omitted when
1303
+ * radiusTop ≈ 0 (i.e. cone tip).
1304
+ *
1305
+ * The cylinder sits centered at the origin, spanning Z = −height/2 to
1306
+ * Z = +height/2. Side quads are axis-aligned in the cylinder's own local
1307
+ * frame, which maximises the chance of hitting the <b> quad fast-path.
1308
+ *
1309
+ * PolyCSS world space: +X right, +Y forward, +Z up. The cylinder axis is
1310
+ * the Z axis so a typical upright pillar stands without any extra rotation.
1311
+ */
1312
+
1313
+ interface CylinderPolygonsOptions {
1314
+ /** Bottom-cap radius. Default 50. */
1315
+ radius?: number;
1316
+ /** Top-cap radius. Defaults to `radius` (straight cylinder).
1317
+ * Set to 0 (or near 0) for a cone. */
1318
+ radiusTop?: number;
1319
+ /** Height along the Z axis. Default 100. */
1320
+ height?: number;
1321
+ /** Number of radial segments. Default 12. */
1322
+ radialSegments?: number;
1323
+ /** Fill color applied to all polygons. */
1324
+ color?: string;
865
1325
  }
866
- interface ParseResult {
867
- /** The mesh, as a flat polygon list. Already vertex-permuted to polycss space. */
868
- polygons: Polygon[];
869
- /** Optional raw voxel source for `.vox` fast paths; polygon fallback remains authoritative. */
870
- voxelSource?: PolyVoxelSource;
871
- /** Optional animation sampler for formats that carry timeline data. */
872
- animation?: ParseAnimationController;
873
- /**
874
- * Blob/object URLs minted during parse (e.g. embedded GLB images). Pass-by-
875
- * reference the same array is exposed on the result for visibility, and
876
- * `dispose()` revokes each one. Do NOT mutate this array externally.
877
- */
878
- objectUrls: string[];
879
- /**
880
- * Idempotent revokes object URLs. Safe to call on unmount, safe to call
881
- * twice. Parsers without minted URLs (parseObj, parseMtl) supply a no-op.
882
- */
883
- dispose: () => void;
884
- /**
885
- * Non-fatal warnings raised during parse. Empty for parsers that don't
886
- * have a warning channel; populated when downstream `normalizePolygons`
887
- * is invoked through the high-level pipeline.
888
- */
889
- warnings: string[];
890
- /** Optional format-specific metadata. */
891
- metadata?: {
892
- /** Triangle count after fan-triangulation (parseObj) or post-triangulation (parseGltf). */
893
- triangleCount?: number;
894
- /** Mesh names from the file (for glTF, from doc.meshes[].name). */
895
- meshes?: string[];
896
- /** Material names (in first-seen order). */
897
- materials?: string[];
898
- /** Animation clips from the file, mirrored from `animation.clips`. */
899
- animations?: ParseAnimationClip[];
900
- /** Source file size in bytes (for diagnostics). */
901
- sourceBytes?: number;
902
- /** Voxel count for `.vox` sources. */
903
- voxelCount?: number;
1326
+ declare function cylinderPolygons(options?: CylinderPolygonsOptions): Polygon[];
1327
+
1328
+ /**
1329
+ * Z-axis cone geometry a cylinder with `radiusTop: 0`.
1330
+ *
1331
+ * This is a thin wrapper around `cylinderPolygons` with `radiusTop` forced
1332
+ * to zero. The top cap is omitted (no area at the tip), and side faces are
1333
+ * emitted as triangles.
1334
+ *
1335
+ * PolyCSS world space: +X right, +Y forward, +Z up. Cone axis is Z; the apex
1336
+ * is at Z = +height/2 and the base at Z = -height/2.
1337
+ */
1338
+
1339
+ interface ConePolygonsOptions {
1340
+ /** Base radius. Default 50. */
1341
+ radius?: number;
1342
+ /** Height along the Z axis. Default 100. */
1343
+ height?: number;
1344
+ /** Number of radial segments. Default 12. */
1345
+ radialSegments?: number;
1346
+ /** Fill color applied to all polygons. */
1347
+ color?: string;
1348
+ }
1349
+ declare function conePolygons(options?: ConePolygonsOptions): Polygon[];
1350
+
1351
+ /**
1352
+ * Torus geometry — Z-axis ring plane.
1353
+ *
1354
+ * The torus is centered at the origin. The ring lies in the XY plane (the
1355
+ * ground plane in PolyCSS world space, where Z is up). The donut hole points
1356
+ * along the Z axis.
1357
+ *
1358
+ * Geometry: `radialSegments × tubularSegments` quads on the surface.
1359
+ *
1360
+ * DOM cost note: at default settings (12 × 16 = 192 quads) this is the
1361
+ * heaviest of the built-in primitives. Reduce radialSegments / tubularSegments
1362
+ * if render budget is tight.
1363
+ *
1364
+ * PolyCSS world space: +X right, +Y forward, +Z up.
1365
+ */
1366
+
1367
+ interface TorusPolygonsOptions {
1368
+ /** Distance from center of tube to center of torus. Default 50. */
1369
+ radius?: number;
1370
+ /** Radius of the tube. Default 15. */
1371
+ tube?: number;
1372
+ /** Number of segments around the main ring. Default 12. */
1373
+ radialSegments?: number;
1374
+ /** Number of segments around the tube cross-section. Default 16. */
1375
+ tubularSegments?: number;
1376
+ /** Fill color applied to all polygons. */
1377
+ color?: string;
1378
+ }
1379
+ declare function torusPolygons(options?: TorusPolygonsOptions): Polygon[];
1380
+
1381
+ /** Tiny non-zero scale collapsed into the projection's Z column to keep
1382
+ * the matrix invertible. Chromium skips elements whose composed
1383
+ * transform is singular (m22 = 0 would make this a true projection
1384
+ * matrix, but Chromium would refuse to paint it), so we crush Z to 1%
1385
+ * of its input instead of exactly zero. The result still looks flat
1386
+ * to the eye — sub-pixel drift on any realistic scene size. */
1387
+ declare const BAKED_SHADOW_Z_SQUASH = 0.01;
1388
+ /** Minimum absolute value of the up-axis light component before the
1389
+ * projection blows up (we divide by it). Matches the --clz clamp in
1390
+ * the dynamic-mode applyDynamicLightVars helper so baked + dynamic
1391
+ * behave identically when the light is near-horizontal. */
1392
+ declare const BAKED_SHADOW_MIN_UP = 0.01;
1393
+ /**
1394
+ * Build the CSS-space shadow projection matrix for a fixed light + ground
1395
+ * plane. The 16-element output mirrors the retained `--shadow-proj` CSS
1396
+ * custom property, but with literal numbers — ready to be formatted into a
1397
+ * single `matrix3d(...)`.
1398
+ *
1399
+ * `lightDir` is the direction the light TRAVELS (e.g. `[0, 0, -1]` is
1400
+ * straight down). PolyCSS world Z is up, and the world→CSS axis swap
1401
+ * leaves Z alone — see styles.ts for the full convention.
1402
+ *
1403
+ * `groundCssZ` is the receiver plane in CSS-Z (= world-Z) coordinates,
1404
+ * already in unit-less form (matrix3d entries must be dimensionless).
1405
+ */
1406
+ declare function buildBakedShadowProjectionMatrix(lightDir: Vec3, groundCssZ: number): number[];
1407
+ /**
1408
+ * Decides whether a polygon should cast a shadow given its outward
1409
+ * normal and the light's travel direction.
1410
+ *
1411
+ * True for polygons whose normals point in the same direction as the
1412
+ * light travels — i.e., on the far/dark side of the mesh from the
1413
+ * light's POV. Those define the silhouette of the cast shadow.
1414
+ *
1415
+ * False for front-facing polygons whose projection would land inside
1416
+ * the silhouette and only add overdraw. Dynamic mode hides these with
1417
+ * a Lambert opacity gate; baked mode skips the DOM emission entirely.
1418
+ */
1419
+ declare function isBakedShadowCaster(normal: Vec3, lightDir: Vec3): boolean;
1420
+ /**
1421
+ * 2D convex hull (Andrew's monotone chain, O(n log n)). Returns the
1422
+ * hull vertices in CCW order. Used to compute a receiver mesh's XY
1423
+ * footprint when subtracting it from the global ground shadow.
1424
+ */
1425
+ declare function convexHull2D(points: ReadonlyArray<readonly [number, number]>): Array<[number, number]>;
1426
+ /**
1427
+ * Signed area of a 2D polygon (positive for CCW vertex order, negative
1428
+ * for CW). Used by `ensureCcw2D` to normalize winding before concatenating
1429
+ * polygons into a compound SVG path under `fill-rule="nonzero"`: mixed
1430
+ * CCW/CW subpaths would cancel each other's winding in the overlap
1431
+ * region and paint an unintended hole.
1432
+ */
1433
+ declare function polygonSignedArea2D(vertices: ReadonlyArray<readonly [number, number]>): number;
1434
+ /**
1435
+ * Returns the polygon's vertices in CCW order, reversing if necessary.
1436
+ * Operates on a copy — input is left unmodified.
1437
+ */
1438
+ declare function ensureCcw2D(vertices: ReadonlyArray<readonly [number, number]>): Array<[number, number]>;
1439
+ /**
1440
+ * Projects a single CSS-3D vertex onto the shadow ground plane, returning
1441
+ * the resulting 2D point in CSS coordinates. Mirrors the retained
1442
+ * `--shadow-proj` matrix, but evaluated on the CPU for a fixed light + ground
1443
+ * so many projected vertices can be merged into one SVG shadow path.
1444
+ *
1445
+ * `cssVertex` is a 3D point that has already been through the world→CSS
1446
+ * axis swap and unit scale (so its components are dimensionless CSS-space
1447
+ * coordinates). `lightDir` follows the same `--clx/--cly/--clz` convention
1448
+ * as `buildBakedShadowProjectionMatrix`.
1449
+ */
1450
+ declare function projectCssVertexToGround(cssVertex: Vec3, lightDir: Vec3, groundCssZ: number): [number, number];
1451
+
1452
+ type Pt = readonly [number, number];
1453
+ /**
1454
+ * Clips `subject` against the convex polygon `clip`. Both polygons are
1455
+ * 2D, given in CCW vertex order. Returns the clipped polygon as a new
1456
+ * array of points; an empty array means `subject` lies entirely outside.
1457
+ */
1458
+ declare function clipPolygonToConvex2D(subject: ReadonlyArray<Pt>, clip: ReadonlyArray<Pt>): Array<[number, number]>;
1459
+
1460
+ /**
1461
+ * One coplanar group of receiver polygons projected into a 2D (u, v) basis on
1462
+ * the shared plane. Sutherland-Hodgman clips caster-projected shadows to this
1463
+ * group's outline; the per-member polygons let the renderer post-filter sub-
1464
+ * shadows that fall outside the actual surface union (concave-bridging air
1465
+ * gaps inside the convex hull).
1466
+ */
1467
+ type ReceiverPlaneGroup = {
1468
+ O: Vec3;
1469
+ n: Vec3;
1470
+ u: Vec3;
1471
+ v: Vec3;
1472
+ outlineUv: Array<[number, number]>;
1473
+ memberPolysUv: Array<Array<[number, number]>>;
1474
+ memberPolyIndices: number[];
1475
+ };
1476
+ /** World→CSS axis swap. World is `+X right, +Y forward, +Z up`; the renderer's
1477
+ * internal frame swaps X↔Y and scales by BASE_TILE (one world unit =
1478
+ * BASE_TILE CSS px). Same conversion every renderer applies at the boundary
1479
+ * for mesh positions, polygon vertices, and light directions. */
1480
+ declare function worldPositionToCss(p: Vec3): Vec3;
1481
+ /** World→CSS axis swap for directions (no scale; directions stay unit). The
1482
+ * polygon basis stores normals in the swapped CSS frame, so light vectors
1483
+ * must match before any dot product. */
1484
+ declare function worldDirectionToCss(d: Vec3): Vec3;
1485
+ /** Apply {@link worldDirectionToCss} to a directional-light object,
1486
+ * preserving the other fields. Used by atlas plan + buildBasisHints +
1487
+ * receiver-shadow callers so the light vector is in the same CSS-axis
1488
+ * frame as the polygon normals. Mirror of vanilla's
1489
+ * `worldDirectionalLightToCss` in `packages/polycss/src/api/scene/transforms.ts`. */
1490
+ declare function worldDirectionalLightToCss<T extends {
1491
+ direction?: Vec3;
1492
+ } | undefined>(light: T): T;
1493
+ /** Normalize a mesh `scale` value into a Vec3 (undefined → [1,1,1], number →
1494
+ * uniform, Vec3 → as-is with `?? 1` per axis). */
1495
+ declare function meshScaleVec3(scale: number | Vec3 | undefined | null): Vec3;
1496
+ /**
1497
+ * Build a `vert → CSS-frame world position` function for a mesh with the
1498
+ * given scale + position. Pivots scale from the mesh ORIGIN. Rotation is
1499
+ * intentionally not applied here — shadow geometry is computed once per
1500
+ * mesh-transform change and already lives in world coords after this
1501
+ * per-vertex transform; rotation lives on the wrapper.
1502
+ */
1503
+ declare function worldCssForMesh(scale: number | Vec3 | undefined | null): (vert: Vec3, pos: Vec3) => Vec3;
1504
+ /**
1505
+ * Minkowski expansion of a convex CCW polygon outward by `expand` units. Each
1506
+ * vertex moves along the bisector of its two adjacent edge outward-
1507
+ * perpendiculars, scaled so the edge offset distance equals `expand` (true
1508
+ * Minkowski sum with a disk of radius `expand`, evaluated at the vertex). For
1509
+ * convex inputs the result is a larger convex polygon with every edge offset
1510
+ * outward by exactly `expand`.
1511
+ */
1512
+ declare function expandConvexHullOutward(hullCcw: Array<[number, number]>, expand: number): Array<[number, number]>;
1513
+ /** Outward extension applied to each receiver face's convex outline (CSS px).
1514
+ * Adjacent receiver faces sharing an edge each expand by this amount, so the
1515
+ * two shadows overlap by ~2×EXPAND at the corner — eliminating the sub-pixel
1516
+ * light strip that used to appear where two wall faces meet. 0.5 CSS px stays
1517
+ * sub-pixel at typical zoom. */
1518
+ declare const RECEIVER_OUTLINE_EXPAND = 0.5;
1519
+ /** Plane-grouping tolerances. dot-product > 0.999 (~2.5° angular) catches
1520
+ * tessellation artifacts on flat surfaces without merging adjacent faces of
1521
+ * a low-poly curved mesh. Plane-offset tolerance is 0.5 CSS px — sub-pixel
1522
+ * coplanarity drift in glTF imports doesn't separate what should be a single
1523
+ * surface. */
1524
+ declare const RECEIVER_NORMAL_TOL = 0.001;
1525
+ declare const RECEIVER_OFFSET_TOL = 0.5;
1526
+ /**
1527
+ * Groups a receiver's polygons into shadow-receiving surfaces. Two passes:
1528
+ *
1529
+ * 1. Plane bucket — group by matching normal + plane offset within tolerance
1530
+ * (catches tessellated flat regions).
1531
+ * 2. Connected component — within each plane bucket, union-find on shared-
1532
+ * edge adjacency (faces sharing >= 2 vertices). Catches disjoint coplanar
1533
+ * walls where a convex hull of everything would bridge an air gap.
1534
+ *
1535
+ * Per group, output a convex hull in the group's (u, v) coords (Minkowski-
1536
+ * expanded by `RECEIVER_OUTLINE_EXPAND`).
1537
+ *
1538
+ * `worldCss(vert, pos)` is the per-vertex world→CSS conversion (built via
1539
+ * `worldCssForMesh` for the receiver's scale + position). `dedupDrop` is the
1540
+ * set of receiver polygon indices to skip.
1541
+ */
1542
+ declare function groupReceiverFaceGroups(polygons: readonly Polygon[], rpos: Vec3, worldCss: (vert: Vec3, pos: Vec3) => Vec3, dedupDrop: ReadonlySet<number>): ReceiverPlaneGroup[];
1543
+
1544
+ /**
1545
+ * Caster-mesh silhouette extraction for the receiver-shadow path.
1546
+ *
1547
+ * Given a closed (or near-closed) caster mesh and a directional light,
1548
+ * computes the silhouette LOOPS — the closed cycles of edges where
1549
+ * adjacent polygons disagree on whether they face the light. For a
1550
+ * closed manifold convex mesh that's one loop; for concave or
1551
+ * higher-genus meshes it may be several.
1552
+ *
1553
+ * The receiver-shadow algorithm normally projects every caster
1554
+ * polygon's outline independently, which collapses to a silhouette at
1555
+ * paint time via SVG fill-rule:nonzero. That makes the browser do the
1556
+ * union work AFTER we've already paid the DOM cost for hundreds or
1557
+ * thousands of triangle sub-paths. Extracting the silhouette here lets
1558
+ * us emit ONE outline per mesh per receiver, dropping path-d size by
1559
+ * 100× on heavy meshes like the teapot. See H9 in
1560
+ * `bench/notes/SHADOW_PERF_LOG.md` and `bench/notes/H9_SILHOUETTE_DESIGN.md`.
1561
+ *
1562
+ * Pure data; no DOM access. Used by `computeReceiverShadowFaces` once
1563
+ * H9 lands.
1564
+ */
1565
+
1566
+ /** Per-edge ownership record. `polyB === null` for boundary edges (open
1567
+ * meshes). Vertex coords are the canonical "from" vertices of the edge —
1568
+ * silhouette walking uses them to reconstruct loop geometry. */
1569
+ interface EdgeOwners {
1570
+ polyA: number;
1571
+ polyB: number | null;
1572
+ vertA: Vec3;
1573
+ vertB: Vec3;
1574
+ }
1575
+ /**
1576
+ * Build a map from canonical edge key → owning polygon(s). Mirrors the
1577
+ * vertex-quantization used by `buildSharedEdgeMap` so we agree on what
1578
+ * "same edge" means.
1579
+ *
1580
+ * Edge keys are orientation-independent (vertex-pair sorted by string),
1581
+ * so two polygons sharing an edge will report the same key. Polygons
1582
+ * are listed in encounter order; for manifold meshes each edge has
1583
+ * exactly 2 owners. Non-manifold edges (3+ owners) get truncated to
1584
+ * `polyB = null` so the caller treats them as boundaries — safer than
1585
+ * picking an arbitrary "second" polygon.
1586
+ */
1587
+ declare function buildEdgeOwners(polygons: readonly Polygon[]): Map<string, EdgeOwners>;
1588
+ /** Classify each polygon as facing the light. Convention: `n · L < 0`
1589
+ * means the polygon's outward face is toward the light source (light
1590
+ * TRAVELS in direction L, polygons are CCW-from-outside). Matches the
1591
+ * light-backface cull's sign convention. */
1592
+ declare function classifyFacing(planeNormals: Array<Vec3 | null>, lightDir: Vec3): boolean[];
1593
+ /**
1594
+ * Walk silhouette edges into closed loops. An edge is silhouette iff
1595
+ * its two adjacent polygons disagree on `facing`. Boundary edges
1596
+ * (polyB === null) are silhouette iff their owner is front-facing —
1597
+ * the boundary is where the lit side ends.
1598
+ *
1599
+ * Returns one closed loop per connected silhouette cycle, each as a
1600
+ * Vec3[] sequence (last vertex equals first vertex implicitly; not
1601
+ * duplicated). For non-manifold or open meshes some edges may not form
1602
+ * a clean cycle; those edges are dropped and the remaining cycles are
1603
+ * returned best-effort.
1604
+ */
1605
+ declare function extractSilhouetteLoops(edgeOwners: ReadonlyMap<string, EdgeOwners>, facing: boolean[]): Vec3[][];
1606
+
1607
+ /**
1608
+ * Per-receiver cached face geometry. One record per coplanar face group:
1609
+ * plane (O, n, u, v), outline polygon (Sutherland-Hodgman clip), bbox in
1610
+ * (u, v) for SVG sizing, and the pre-stringified matrix3d transform that
1611
+ * places an SVG on that face plane.
1612
+ *
1613
+ * All of this is invariant under light/caster changes. Per light tick the
1614
+ * caller just re-runs the per-tri SH and builds the path `d` — never
1615
+ * recompute groups or basis. Cache invalidated when the receiver's polygon
1616
+ * count or position changes.
1617
+ */
1618
+ interface ReceiverFacePlane {
1619
+ O: Vec3;
1620
+ n: Vec3;
1621
+ u: Vec3;
1622
+ v: Vec3;
1623
+ outlineUv: Array<[number, number]>;
1624
+ memberPolysUv: Array<Array<[number, number]>>;
1625
+ memberPolyIndices: number[];
1626
+ minU: number;
1627
+ minV: number;
1628
+ width: number;
1629
+ height: number;
1630
+ matrixCss: string;
1631
+ faceIndex: number;
1632
+ /** World-frame lift (already × BASE_TILE) along +n. Re-applied per-frame
1633
+ * when building a tight shadow SVG matrix so the SVG hovers over the face. */
1634
+ lift: number;
1635
+ }
1636
+ /**
1637
+ * Per-caster cached per-polygon data: world-space vertices + 3D AABB
1638
+ * corners + caster-polygon plane normal/offset. Invariant under light
1639
+ * direction; depends only on the caster mesh's geometry and position.
1640
+ */
1641
+ interface CasterPolyItem {
1642
+ wv: Vec3[];
1643
+ bboxCorners: Vec3[];
1644
+ planeN: Vec3 | null;
1645
+ planeOffset: number;
1646
+ polygonIndex: number;
1647
+ }
1648
+ /** A caster mesh's prepared items paired with a stable identifier the caller
1649
+ * can use for per-path attribution. */
1650
+ interface ReceiverCasterInput<T = unknown> {
1651
+ /** Caller-defined identifier (e.g. mesh ref, mesh shadow id). Echoed back
1652
+ * on each emitted path so the renderer can map a subpath to its source
1653
+ * caster mesh. */
1654
+ id: T;
1655
+ items: CasterPolyItem[];
1656
+ /** Self-shadow edge adjacency. When this caster is the same mesh as the
1657
+ * receiver, the renderer pre-computes a map polygonIndex →
1658
+ * set-of-other-polygonIndex that share at least one edge (within
1659
+ * EDGE_MATCH_EPS). The shadow algorithm skips projecting `polygonIndex`
1660
+ * onto any receiver face whose member set intersects the shared-edge
1661
+ * set — those projections are sliver shadows along seams (smooth-shaded
1662
+ * GLB meshes, subdivided spheres) that the user never wants to see. */
1663
+ selfShadowEdgeMap?: ReadonlyMap<number, ReadonlySet<number>>;
1664
+ /** Per-mesh edge ownership map for silhouette extraction. Cached by the
1665
+ * caller (WeakMap<Mesh, …>) and shared across receivers within a frame.
1666
+ * When present AND the caster is NOT the receiver AND items.length ≥
1667
+ * SILHOUETTE_MIN_POLYS, the shadow algorithm projects per-mesh
1668
+ * silhouette loops instead of every front-facing triangle — collapses
1669
+ * the N-triangle path to 1 outline per caster per receiver. See H9 in
1670
+ * `bench/notes/SHADOW_PERF_LOG.md`. */
1671
+ edgeOwners?: ReadonlyMap<string, EdgeOwners>;
1672
+ /** Total polygon count on the source caster mesh (NOT the filtered
1673
+ * `items` count). Needed by silhouette extraction so the `facing`
1674
+ * array is sized correctly even when atlas-plan / dedup filters drop
1675
+ * some polygons from `items`. */
1676
+ casterPolygonCount?: number;
1677
+ }
1678
+ /**
1679
+ * Build a polygon-adjacency map: polygonIndex → set of polygonIndex that
1680
+ * share at least one edge (vertex pair, orientation-independent). Used by
1681
+ * the receiver-shadow algorithm to cull sliver shadows along mesh seams.
1682
+ *
1683
+ * Edge match tolerance is small enough to dedupe vertex coordinates that
1684
+ * went through `optimizeMeshPolygons` snap-to-plane but not so loose it
1685
+ * connects geometrically distinct polygons.
1686
+ */
1687
+ declare function buildSharedEdgeMap(polygons: readonly Polygon[]): Map<number, Set<number>>;
1688
+ /** One contributing caster's shadow subpath on a single receiver face. */
1689
+ interface ReceiverShadowPath<T = unknown> {
1690
+ /** Caster id echoed from the input. */
1691
+ casterId: T;
1692
+ /** Path data string: `M…L…Z` subpaths in face-local (u, v) coordinates
1693
+ * pre-translated so the SVG's `viewBox` is `0 0 width height`. */
1694
+ d: string;
1695
+ /** Source polygon indices on the caster mesh in subpath order (one per
1696
+ * M…L…Z block). Used for DevTools attribution. */
1697
+ casterPolygonIndices: number[];
1698
+ }
1699
+ /** Per-receiver-face shadow spec. Renderer mounts one SVG per spec. */
1700
+ interface ReceiverShadowFaceSpec<T = unknown> {
1701
+ /** Index into the prepared `ReceiverFacePlane[]` list. Stable across
1702
+ * frames; used as the `data-poly-shadow-receiver-face` attr. */
1703
+ faceIndex: number;
1704
+ /** Receiver polygon indices that make up this coplanar group. */
1705
+ memberPolyIndices: number[];
1706
+ /** matrix3d(...) transform that places the SVG on the face plane. */
1707
+ matrixCss: string;
1708
+ width: number;
1709
+ height: number;
1710
+ /** Fill (and stroke) color resolved per-face: textured receivers get the
1711
+ * user's `shadow.color`; solid receivers get their own ambient-only
1712
+ * shadePolygon for byte-exact Three.js parity. */
1713
+ fill: string;
1714
+ /** Per-face opacity (already accounts for textured-darken Lambert ratio
1715
+ * if applicable). */
1716
+ opacity: number;
1717
+ paths: Array<ReceiverShadowPath<T>>;
1718
+ }
1719
+ /**
1720
+ * Build silhouette `edgeOwners` for a caster mesh in world-CSS frame.
1721
+ * Used by the silhouette path inside `computeReceiverShadowFaces` (H9).
1722
+ * Polygons are transformed through the same world-CSS pipeline as
1723
+ * `prepareCasterPolyItems` (worldCssForMesh + optional rotation around
1724
+ * the CSS-pivot) so the silhouette loop vertices land in the same world
1725
+ * frame as the receiver face plane and the light direction.
1726
+ *
1727
+ * Caller caches by (mesh, polygon-list-identity + position + scale +
1728
+ * rotation) — invalidates only when the caster's geometry or transform
1729
+ * actually changes. Light direction is NOT a bust key (silhouette
1730
+ * adjacency is per-mesh, facing is per-frame).
1731
+ */
1732
+ declare function prepareCasterEdgeOwners(polygons: readonly Polygon[], position: Vec3, scale: number | Vec3 | undefined | null, rotation?: Vec3 | null): ReadonlyMap<string, EdgeOwners>;
1733
+ /**
1734
+ * Build CasterPolyItem[] for a caster mesh. Pure: same inputs → same output.
1735
+ * The caller memoizes by mesh ref + bust key. `includePolygonIndex(idx)`
1736
+ * decides which polygons participate (e.g. dedup drop + atlas-plan filter
1737
+ * in vanilla; just dedup drop in React/Vue without an atlas-plan concept).
1738
+ */
1739
+ declare function prepareCasterPolyItems(polygons: readonly Polygon[], position: Vec3, scale: number | Vec3 | undefined | null, includePolygonIndex: (polygonIndex: number) => boolean, rotation?: Vec3 | null): CasterPolyItem[];
1740
+ /**
1741
+ * Build ReceiverFacePlane[] for a receiver mesh. Pure: groups coplanar
1742
+ * polygons, computes (u,v) basis + outline, applies interior occlusion cull
1743
+ * (drops face planes hidden behind a parallel face plane within wall-
1744
+ * thickness range).
1745
+ *
1746
+ * `shadowLift` is the world-unit lift applied along each face normal so the
1747
+ * shadow SVG composites above the surface without z-fighting (matches the
1748
+ * ground-shadow `shadow.lift` option).
1749
+ */
1750
+ declare function prepareReceiverFacePlanes(polygons: readonly Polygon[], position: Vec3, scale: number | Vec3 | undefined | null, dedupDrop: ReadonlySet<number>, shadowLift: number, rotation?: Vec3 | null): ReceiverFacePlane[];
1751
+ /** Input for `computeReceiverShadowFaces`. */
1752
+ interface ComputeReceiverShadowFacesInput<T = unknown> {
1753
+ /** Precomputed face planes from `prepareReceiverFacePlanes`. */
1754
+ receiverPlanes: ReceiverFacePlane[];
1755
+ /** Receiver's polygon list, used to look up per-face fill color. */
1756
+ receiverPolygons: readonly Polygon[];
1757
+ /** Whether the receiver mesh has any textured polygons. Drives the
1758
+ * textured-darken opacity calc vs solid-replace fill color. */
1759
+ receiverHasTexture: boolean;
1760
+ /** Per-caster items + caller id, in caller-defined order. */
1761
+ casters: ReceiverCasterInput<T>[];
1762
+ /** Directional light vector in CSS frame. */
1763
+ lightDir: Vec3;
1764
+ /** Camera cull rotation (rotX/rotY + receiver mesh rotation) so back-
1765
+ * facing receiver faces can be skipped. */
1766
+ cameraRot: CameraCullRotation;
1767
+ /** Ambient light (used for solid-receiver shadow tint via shadePolygon). */
1768
+ ambientLight?: PolyAmbientLight;
1769
+ /** Directional light (used for textured-darken opacity calc). */
1770
+ directionalLight?: PolyDirectionalLight;
1771
+ /** Scene shadow options. */
1772
+ shadow?: {
1773
+ color?: string;
1774
+ opacity?: number;
1775
+ maxExtend?: number;
904
1776
  };
905
1777
  }
1778
+ /**
1779
+ * The pure per-frame algorithm. Returns one ReceiverShadowFaceSpec for each
1780
+ * visible receiver face that catches at least one caster's shadow. Skips
1781
+ * back-facing faces. Caller mounts SVGs per spec.
1782
+ */
1783
+ declare function computeReceiverShadowFaces<T = unknown>(input: ComputeReceiverShadowFacesInput<T>): ReceiverShadowFaceSpec<T>[];
906
1784
 
907
1785
  /**
908
1786
  * PolyAnimationMixer — three.js-shaped animation API for polycss.
@@ -1008,6 +1886,12 @@ interface PolyAnimationMixer {
1008
1886
  }
1009
1887
  declare function createPolyAnimationMixer(root: PolyAnimationTarget, controller: ParseAnimationController): PolyAnimationMixer;
1010
1888
 
1889
+ interface OptimizeAnimatedMeshPolygonsOptions {
1890
+ /** Public quality/resolution intent. Defaults to "lossy". */
1891
+ meshResolution?: MeshResolution;
1892
+ }
1893
+ declare function optimizeAnimatedMeshPolygons(result: ParseResult, options?: OptimizeAnimatedMeshPolygonsOptions): ParseResult;
1894
+
1011
1895
  interface ObjParseOptions {
1012
1896
  /**
1013
1897
  * Largest mesh extent (in scene-space units). The mesh is uniformly
@@ -1015,10 +1899,20 @@ interface ObjParseOptions {
1015
1899
  */
1016
1900
  targetSize?: number;
1017
1901
  /**
1018
- * Padding added to the bbox of every emitted polygon so they don't land
1019
- * at coordinate "0". Default: 1.
1902
+ * Where to place the mesh-local origin relative to the parsed geometry.
1903
+ *
1904
+ * - `"min"` (default): bbox-min sits at local (0,0,0); geometry lives in
1905
+ * the +X+Y+Z quadrant. This is PolyCSS's historical behavior.
1906
+ * - `true` (or `"center"`): bbox-center sits at local (0,0,0); geometry
1907
+ * is centered around the origin. Pair with `scene.add(parse, {position,
1908
+ * rotation:[...]})` to get three.js-style rotate-in-place around the
1909
+ * centroid.
1910
+ *
1911
+ * Three.js's `GLTFLoader`/`OBJLoader` don't reposition vertices at all;
1912
+ * for byte-parity loading set this to a separate explicit `false` once
1913
+ * the no-offset option lands.
1020
1914
  */
1021
- gridShift?: number;
1915
+ center?: boolean | "min" | "center";
1022
1916
  /**
1023
1917
  * Color used for faces that have no `usemtl` in scope, or whose material
1024
1918
  * name doesn't resolve via `materialColors`. Default: "#888888".
@@ -1086,8 +1980,6 @@ declare function parseMtl(text: string): MtlParseResult;
1086
1980
  interface GltfParseOptions {
1087
1981
  /** Largest mesh extent (units). Mesh is uniformly scaled to fit. Default 60. */
1088
1982
  targetSize?: number;
1089
- /** Padding offset (avoids coordinate "0"). Default 1. */
1090
- gridShift?: number;
1091
1983
  /** Color used when a primitive has no material or no baseColorFactor. */
1092
1984
  defaultColor?: string;
1093
1985
  /**
@@ -1095,15 +1987,32 @@ interface GltfParseOptions {
1095
1987
  * material's `pbrMetallicRoughness.baseColorFactor` if not in this map.
1096
1988
  */
1097
1989
  materialColors?: Record<string, string>;
1990
+ /**
1991
+ * Override map: glTF material name → texture image URL. Takes priority over
1992
+ * `pbrMetallicRoughness.baseColorTexture`; useful for GLB/GLTF exports that
1993
+ * preserved UVs but dropped external image references.
1994
+ */
1995
+ materialTextures?: Record<string, string>;
1098
1996
  /**
1099
1997
  * Which axis is "up" in the source mesh.
1100
1998
  * - "y" (default, glTF spec): cyclic permutation (x,y,z) → (z,x,y) so
1101
- * +Y ends up on polycss's +Z (elevation).
1999
+ * +Y ends up on PolyCSS's +Z (elevation).
1102
2000
  * - "z" (Blender-style, FBX2glTF often emits this): identity, no swap.
1103
2001
  * Pick "z" if the model lands on its side / lies down instead of
1104
2002
  * standing.
1105
2003
  */
1106
2004
  upAxis?: "y" | "z";
2005
+ /**
2006
+ * Where to place the mesh-local origin relative to the parsed geometry.
2007
+ *
2008
+ * - `"min"` (default): bbox-min sits at local (0,0,0); geometry lives in
2009
+ * the +X+Y+Z quadrant. This is PolyCSS's historical behavior.
2010
+ * - `true` (or `"center"`): bbox-center sits at local (0,0,0); geometry
2011
+ * is centered around the origin. Pair with `scene.add(parse, {position,
2012
+ * rotation:[...]})` to get three.js-style rotate-in-place around the
2013
+ * centroid.
2014
+ */
2015
+ center?: boolean | "min" | "center";
1107
2016
  /**
1108
2017
  * For .gltf (non-binary) — resolve a glTF buffer URI to its bytes. The
1109
2018
  * built-in parser handles GLB binary chunks natively; .gltf files with
@@ -1115,7 +2024,7 @@ interface GltfParseOptions {
1115
2024
  * (`doc.images[i].uri = "Textures/foo.png"`) against the GLB/glTF's
1116
2025
  * location. Without this, relative URIs would resolve against the page,
1117
2026
  * which 404s. Pass the same URL you fetched the file from.
1118
- */
2027
+ */
1119
2028
  baseUrl?: string;
1120
2029
  }
1121
2030
  declare function parseGltf(input: ArrayBuffer | Uint8Array, options?: GltfParseOptions): ParseResult;
@@ -1142,58 +2051,36 @@ interface VoxParseOptions {
1142
2051
  */
1143
2052
  targetSize?: number;
1144
2053
  /**
1145
- * Per-coordinate offset added after scaling. Keeps coordinates away from
1146
- * zero (matching OBJ/glTF parsers). Default: 0 vox already starts at
1147
- * non-negative integers so zero makes sensible default.
2054
+ * Optional lossy palette simplification. When > 0, opaque, hue-compatible
2055
+ * palette colors within this RGB distance are folded into the most-used
2056
+ * nearby color before greedy voxel meshing. Default: disabled.
1148
2057
  */
1149
- gridShift?: number;
2058
+ paletteMergeDistance?: number;
2059
+ /**
2060
+ * Optional lossy local cleanup. When > 0, small face-plane color islands
2061
+ * and thin streaks are recolored to a neighboring dominant hue-compatible
2062
+ * color within this RGB distance before greedy voxel meshing. Default:
2063
+ * disabled.
2064
+ */
2065
+ colorRegionMergeDistance?: number;
1150
2066
  }
1151
2067
  declare function parseVox(buffer: ArrayBuffer, options?: VoxParseOptions): ParseResult;
1152
2068
 
1153
- type PlaneAxis = "x" | "y" | "z";
1154
- type PolyVoxelFace = "t" | "b" | "bl" | "br" | "fr" | "fl";
1155
- interface PolyVoxelWallsMask {
1156
- t: boolean;
1157
- b: boolean;
1158
- bl: boolean;
1159
- br: boolean;
1160
- fl: boolean;
1161
- fr: boolean;
1162
- }
1163
- interface FaceKey {
1164
- axis: PlaneAxis;
1165
- plane: number;
1166
- face: PolyVoxelFace;
1167
- }
1168
- interface FaceBuffer {
1169
- width: number;
1170
- height: number;
1171
- minRow: number;
1172
- minCol: number;
1173
- ids: Uint32Array;
1174
- mask: Uint8Array;
1175
- filledCount: number;
1176
- palette: string[];
1177
- }
1178
- interface FaceData {
1179
- key: FaceKey;
1180
- buffer: FaceBuffer;
1181
- }
1182
- type Brush = {
1183
- r0: number;
1184
- c0: number;
1185
- r1: number;
1186
- c1: number;
1187
- baseColor: string;
1188
- };
1189
- type SlicePlan = {
1190
- key: FaceKey;
1191
- buffer: FaceBuffer;
1192
- brushes: Brush[];
1193
- };
1194
- declare const NEXT_LAYER_STEP: Record<PolyVoxelFace, number>;
1195
- declare const buildSlicePlan: (faceData: FaceData, nextLayer: FaceBuffer | null) => SlicePlan;
1196
- declare const buildFaceDataFromVoxelSource: (source: PolyVoxelSource) => FaceData[];
2069
+ interface StlParseOptions {
2070
+ /** Largest mesh extent (units). Mesh is uniformly scaled to fit. Default: 60. */
2071
+ targetSize?: number;
2072
+ /** Padding offset added after scaling. Default: 1. */
2073
+ gridShift?: number;
2074
+ /** Solid color assigned to every STL triangle. Default: "#888888". */
2075
+ defaultColor?: string;
2076
+ /**
2077
+ * Which axis is "up" in the source mesh.
2078
+ * - "z" (default): identity axes, matching common CAD/3D-print STL exports.
2079
+ * - "y": cyclic permutation (x,y,z) → (z,x,y), matching OBJ/glTF's +Y-up path.
2080
+ */
2081
+ upAxis?: "z" | "y";
2082
+ }
2083
+ declare function parseStl(source: ArrayBuffer | Uint8Array | string, options?: StlParseOptions): ParseResult;
1197
2084
 
1198
2085
  /**
1199
2086
  * loadMesh — high-level fetch+parse dispatcher. Picks the parser by file
@@ -1205,11 +2092,12 @@ declare const buildFaceDataFromVoxelSource: (source: PolyVoxelSource) => FaceDat
1205
2092
  * - `.glb` → ArrayBuffer fetch + `parseGltf`
1206
2093
  * - `.gltf` → ArrayBuffer fetch + `parseGltf` (caller may pass `baseUrl`)
1207
2094
  * - `.vox` → ArrayBuffer fetch + `parseVox`
2095
+ * - `.stl` → ArrayBuffer fetch + `parseStl`
1208
2096
  *
1209
2097
  * `.mtl` is rejected — it's a material file, not a mesh. Use `parseMtl`
1210
2098
  * directly if you want to read materials.
1211
2099
  *
1212
- * Other extensions throw. Future formats (STL, PLY) plug in here.
2100
+ * Other extensions throw. Future formats (PLY, 3MF) plug in here.
1213
2101
  */
1214
2102
 
1215
2103
  interface LoadMeshOptions {
@@ -1233,6 +2121,8 @@ interface LoadMeshOptions {
1233
2121
  gltfOptions?: GltfParseOptions;
1234
2122
  /** Forwarded to `parseVox`. */
1235
2123
  voxOptions?: VoxParseOptions;
2124
+ /** Forwarded to `parseStl`. */
2125
+ stlOptions?: StlParseOptions;
1236
2126
  /**
1237
2127
  * Converts texture-backed faces whose UV samples are a uniform color into
1238
2128
  * solid-color polygons before culling/merging. This avoids atlas sprites for
@@ -1241,10 +2131,578 @@ interface LoadMeshOptions {
1241
2131
  solidTextureSamples?: boolean | SolidTextureSampleOptions;
1242
2132
  /**
1243
2133
  * Mesh optimization intent. Defaults to "lossy"; set "lossless" to keep
1244
- * exact planar candidates only.
2134
+ * exact planar candidates only. STL imports use the conservative lossless
2135
+ * optimizer path in both modes.
1245
2136
  */
1246
2137
  meshResolution?: MeshResolution;
1247
2138
  }
1248
2139
  declare function loadMesh(url: string, options?: LoadMeshOptions): Promise<ParseResult>;
1249
2140
 
1250
- export { type ApproximateMergeOptions, type ArrowPolygonsOptions, type AutoRotateConfig, type AutoRotateOption, type AxesHelperOptions, BASE_TILE, type BoxFace, type BoxFaceOptions, type BoxPolygonsOptions, CAMERA_BACKFACE_CULL_EPS, type CameraCullNormalGroup, type CameraCullRotation, type CameraHandle, type CameraState, type CameraStyleInput, type CoverPlanarPolygonsOptions, type CullInteriorOptions, DEFAULT_CAMERA_STATE, DEFAULT_PROJECTION, type DedupeOverlappingPolygonsOptions, type GltfParseOptions, type LoadMeshOptions, type LoopMode, LoopOnce, LoopPingPong, LoopRepeat, type MeshResolution, type MtlParseResult, type NormalizeResult, type ObjParseOptions, type OctahedronPolygonsOptions, type OptimizeMeshPolygonsOptions, NEXT_LAYER_STEP as POLY_VOXEL_NEXT_LAYER_STEP, type ParseAnimationClip, type ParseAnimationController, type ParseResult, type ParsedColor, type PlanePolygonsOptions, type PolyAmbientLight, type PolyAnimationAction, type ParseAnimationClip as PolyAnimationClip, type PolyAnimationMixer, type PolyAnimationTarget, type PolyDirectionalLight, type PolyMaterial, type PolyTextureLightingMode, type Brush as PolyVoxelBrush, type PolyVoxelCell, type PolyVoxelFace, type FaceBuffer as PolyVoxelFaceBuffer, type FaceData as PolyVoxelFaceData, type FaceKey as PolyVoxelFaceKey, type PlaneAxis as PolyVoxelPlaneAxis, type SlicePlan as PolyVoxelSlicePlan, type PolyVoxelSource, type PolyVoxelWallsMask, type Polygon, type PolygonFace, QUAT_IDENTITY, type Quat, type RingPolygonsOptions, type RingQuadPolygonsOptions, type SceneBbox, type SceneContext, type SceneContextBuildArgs, type SceneContextBuildResult, type SolidTextureSampleOptions, type TexturePaintMetrics, type TexturePaintMetricsOptions, type TextureTriangle, VOXEL_CAMERA_CULL_AXIS_EPS, VOXEL_CAMERA_CULL_NORMAL_LIMIT, type Vec2, type Vec3, type VoxParseOptions, arrowPolygons, axesHelperPolygons, bakeSolidTextureSampledPolygons, bakeSolidTextureSamples, boxPolygons, buildFaceDataFromVoxelSource as buildPolyVoxelFaceData, buildSlicePlan as buildPolyVoxelSlicePlan, buildSceneContext, cameraCullNormalGroups, cameraCullNormalGroupsFromPolygons, cameraCullNormalKey, cameraCullVisibleSignature, cameraFacingDepth, clampChannel, computeSceneBbox, computeShapeLighting, computeTexturePaintMetrics, coverPlanarPolygons, createIsometricCamera, createPolyAnimationMixer, cullInteriorPolygons, dedupeOverlappingPolygons, eulerXYZFromQuat, findOverlappingPolygonDuplicates, formatColor, inverseRotateVec3, isAxisAlignedSurfaceNormal, isVoxelCameraCullableNormalGroups, loadMesh, mergePolygons, normalFacesCamera, normalizeInvertMultiplier, normalizePolygons, octahedronPolygons, optimizeMeshPolygons, parseColor, parseGltf, parseHexColor, parseMtl, parseObj, parsePureColor, parseRgbColor, parseVox, planePolygons, polygonCssSurfaceNormal, polygonFaces, polygonFacesCamera, quatFromAxisAngle, quatFromEulerXYZ, quatMultiply, ringPolygons, ringQuadPolygons, rotateVec3, shadeColor };
2141
+ declare const DEFAULT_TILE = 50;
2142
+ declare const DEFAULT_LIGHT_DIR: Vec3;
2143
+ declare const DEFAULT_LIGHT_COLOR = "#ffffff";
2144
+ declare const DEFAULT_LIGHT_INTENSITY = 1;
2145
+ declare const DEFAULT_AMBIENT_COLOR = "#ffffff";
2146
+ declare const DEFAULT_AMBIENT_INTENSITY = 0.4;
2147
+ declare const ATLAS_MAX_SIZE = 4096;
2148
+ declare const ATLAS_PADDING = 1;
2149
+ declare const MIN_ATLAS_SCALE = 0.1;
2150
+ declare const MAX_ATLAS_SCALE = 1;
2151
+ declare const AUTO_ATLAS_LOW_AREA: number;
2152
+ declare const AUTO_ATLAS_MEDIUM_AREA: number;
2153
+ declare const AUTO_ATLAS_MAX_BITMAP_SIDE = 2048;
2154
+ declare const AUTO_ATLAS_MAX_DECODED_BYTES_MOBILE: number;
2155
+ declare const AUTO_ATLAS_MAX_DECODED_BYTES_DESKTOP: number;
2156
+ declare const AUTO_ATLAS_SCALE_GUARD = 0.995;
2157
+ declare const COLOR_PARSE_CACHE_MAX = 512;
2158
+ declare const ASYNC_RENDER_BUDGET_MS = 12;
2159
+ declare const RECT_EPS = 0.001;
2160
+ declare const BASIS_EPS = 1e-9;
2161
+ declare const SURFACE_NORMAL_EPS = 0.0001;
2162
+ declare const SURFACE_DISTANCE_EPS = 0.1;
2163
+ declare const SEAM_LIGHT_EPS = 0.01;
2164
+ declare const TEXTURE_TRIANGLE_BLEED = 0.75;
2165
+ declare const TEXTURE_EDGE_REPAIR_ALPHA_MIN = 1;
2166
+ declare const TEXTURE_EDGE_REPAIR_SOURCE_ALPHA_MIN = 250;
2167
+ declare const TEXTURE_EDGE_REPAIR_RADIUS = 1.5;
2168
+ declare const SOLID_TRIANGLE_BLEED = 0.75;
2169
+ declare const DEFAULT_MATRIX_DECIMALS = 3;
2170
+ declare const DEFAULT_BORDER_SHAPE_DECIMALS = 2;
2171
+ declare const DEFAULT_ATLAS_CSS_DECIMALS = 4;
2172
+ declare const DECIMAL_SCALES: number[];
2173
+ declare const SOLID_QUAD_CANONICAL_SIZE = 64;
2174
+ declare const SOLID_TRIANGLE_CANONICAL_SIZE = 32;
2175
+ declare const SOLID_TRIANGLE_LARGE_BORDER_CANONICAL_SIZE = 96;
2176
+ declare const ATLAS_CANONICAL_SIZE_EXPLICIT = 64;
2177
+ declare const ATLAS_CANONICAL_SIZE_AUTO_DESKTOP = 128;
2178
+ declare const BORDER_SHAPE_CENTER_PERCENT = 50;
2179
+ declare const BORDER_SHAPE_POINT_EPS = 1e-7;
2180
+ declare const BORDER_SHAPE_CANONICAL_SIZE = 16;
2181
+ declare const BORDER_SHAPE_BLEED = 0.9;
2182
+ declare const CORNER_SHAPE_POINT_EPS = 0.75;
2183
+ declare const CORNER_SHAPE_DUPLICATE_EPS = 0.2;
2184
+ declare const PROJECTIVE_QUAD_DENOM_EPS = 0.05;
2185
+ declare const PROJECTIVE_QUAD_MAX_WEIGHT_RATIO = 256;
2186
+ declare const PROJECTIVE_QUAD_BLEED = 0.6;
2187
+ declare const DEFAULT_SEAM_BLEED = 1.5;
2188
+ /** Clamp the `seamBleed` ratio. `undefined` → 1 (full default), 0 → no
2189
+ * bleed, values outside [0,1] are clamped. Single source of truth for
2190
+ * how the public ratio maps to per-strategy bleed multipliers. */
2191
+ declare function resolveBleedRatio(seamBleed: number | "auto" | undefined): number;
2192
+
2193
+ interface RGB {
2194
+ r: number;
2195
+ g: number;
2196
+ b: number;
2197
+ }
2198
+ interface RGBFactors {
2199
+ r: number;
2200
+ g: number;
2201
+ b: number;
2202
+ }
2203
+ interface UvAffine {
2204
+ a: number;
2205
+ b: number;
2206
+ c: number;
2207
+ d: number;
2208
+ e: number;
2209
+ f: number;
2210
+ }
2211
+ interface UvSampleRect {
2212
+ minU: number;
2213
+ minV: number;
2214
+ maxU: number;
2215
+ maxV: number;
2216
+ }
2217
+ interface TextureTrianglePlan {
2218
+ screenPts: number[];
2219
+ uvAffine: UvAffine | null;
2220
+ uvSampleRect: UvSampleRect | null;
2221
+ }
2222
+ interface TextureAtlasPlan {
2223
+ index: number;
2224
+ polygon: Polygon;
2225
+ texture?: string;
2226
+ tileSize: number;
2227
+ layerElevation: number;
2228
+ matrix: string;
2229
+ canonicalMatrix: string;
2230
+ atlasMatrix: string;
2231
+ atlasCanonicalSize?: number;
2232
+ projectiveMatrix: string | null;
2233
+ canvasW: number;
2234
+ canvasH: number;
2235
+ screenPts: number[];
2236
+ uvAffine: UvAffine | null;
2237
+ uvSampleRect: UvSampleRect | null;
2238
+ textureTriangles: TextureTrianglePlan[] | null;
2239
+ textureEdgeRepairEdges: Set<number> | null;
2240
+ textureEdgeRepair: boolean;
2241
+ seamBleed?: number;
2242
+ seamBleedEdges?: Set<number>;
2243
+ seamBleedEdgeAmounts?: Map<number, number>;
2244
+ seamBleedInsets?: SeamBleedInsets;
2245
+ /** Resolved per-strategy bleed multiplier (0..1, default 1). Populated
2246
+ * at plan construction from `options.seamBleed` via `resolveBleedRatio`.
2247
+ * Downstream emitters (borderShapeGeometryForPlan, projective-quad
2248
+ * rasteriser, etc.) read this and multiply their hardcoded per-strategy
2249
+ * bleed constants by it. Single knob for "scale down all my bleeds". */
2250
+ bleedRatio?: number;
2251
+ /** World-space surface normal — stable across light changes, used by dynamic mode. */
2252
+ normal: Vec3;
2253
+ textureTint: RGBFactors;
2254
+ shadedColor: string;
2255
+ }
2256
+ interface BorderShapeBounds {
2257
+ minX: number;
2258
+ minY: number;
2259
+ width: number;
2260
+ height: number;
2261
+ }
2262
+ interface BorderShapeGeometry {
2263
+ bounds: BorderShapeBounds;
2264
+ points: Array<[number, number]>;
2265
+ }
2266
+ type CornerShapeCorner = "topLeft" | "topRight" | "bottomRight" | "bottomLeft";
2267
+ type CornerShapeSide = "left" | "right" | "top" | "bottom";
2268
+ interface CornerShapeRadius {
2269
+ x: number;
2270
+ y: number;
2271
+ }
2272
+ interface CornerShapeGeometry {
2273
+ bounds: BorderShapeBounds;
2274
+ radii: Partial<Record<CornerShapeCorner, CornerShapeRadius>>;
2275
+ }
2276
+ type TextureQuality = number | "auto";
2277
+ type PolySeamBleed = number | "auto";
2278
+ type PolySeamBleedEdgeValue = ReadonlySet<number> | ReadonlyMap<number, number>;
2279
+ type PolySeamBleedEdges = ReadonlyMap<number, PolySeamBleedEdgeValue> | readonly (PolySeamBleedEdgeValue | undefined)[];
2280
+ type PolyRenderStrategy = "b" | "i" | "u";
2281
+ type SolidTrianglePrimitive = "border" | "border-large" | "corner-bevel";
2282
+ interface PolyRenderStrategiesOption {
2283
+ /** Strategies to skip; polygons that would normally use them fall through
2284
+ * the chain (b → i → s, u → i → s, i → s). `<s>` is the universal
2285
+ * fallback and cannot be disabled — textured polys have no other path. */
2286
+ disable?: readonly PolyRenderStrategy[];
2287
+ }
2288
+ interface SeamBleedInsets {
2289
+ left: number;
2290
+ right: number;
2291
+ top: number;
2292
+ bottom: number;
2293
+ }
2294
+ interface PackedTextureAtlasEntry extends TextureAtlasPlan {
2295
+ pageIndex: number;
2296
+ x: number;
2297
+ y: number;
2298
+ }
2299
+ interface PackedPage {
2300
+ width: number;
2301
+ height: number;
2302
+ entries: PackedTextureAtlasEntry[];
2303
+ }
2304
+ interface PackingShelf {
2305
+ x: number;
2306
+ y: number;
2307
+ height: number;
2308
+ }
2309
+ interface PackingPage extends PackedPage {
2310
+ shelves: PackingShelf[];
2311
+ sealed?: boolean;
2312
+ }
2313
+ interface PackedAtlas {
2314
+ entries: Array<PackedTextureAtlasEntry | null>;
2315
+ pages: PackedPage[];
2316
+ }
2317
+ interface SolidTriangleBasis {
2318
+ a: number;
2319
+ b: number;
2320
+ c: number;
2321
+ }
2322
+ interface SolidTriangleColorPlan {
2323
+ index: number;
2324
+ polygon: Polygon;
2325
+ colorComputed: boolean;
2326
+ bakedColor?: string;
2327
+ bakedRgb?: RGB;
2328
+ bakedAlpha?: number;
2329
+ dynamicVars?: string;
2330
+ }
2331
+ interface SolidTrianglePlan extends SolidTriangleColorPlan {
2332
+ styleText: string;
2333
+ transformText: string;
2334
+ basis: SolidTriangleBasis;
2335
+ primitive: SolidTrianglePrimitive;
2336
+ }
2337
+ interface SolidTriangleComputeOptions {
2338
+ basis?: SolidTriangleBasis;
2339
+ includeColor?: boolean;
2340
+ matrixDecimals?: number;
2341
+ color?: string;
2342
+ primitive?: SolidTrianglePrimitive;
2343
+ /** Pre-resolved primitive used when `primitive` is not set — replaces the
2344
+ * browser-global resolution that formerly happened inside the function. */
2345
+ resolvedPrimitive?: SolidTrianglePrimitive | null;
2346
+ }
2347
+ interface StableTriangleColorState {
2348
+ updatesDisabled: boolean;
2349
+ freezeFrames: number;
2350
+ colorFrame: number;
2351
+ maxStep: number;
2352
+ }
2353
+ interface SolidTriangleFrame {
2354
+ polygonCount: number;
2355
+ vertices: ArrayLike<number>;
2356
+ colors?: readonly (string | undefined)[];
2357
+ }
2358
+ interface SolidPaintDefaults {
2359
+ paintColor?: string;
2360
+ dynamicColor?: {
2361
+ r: number;
2362
+ g: number;
2363
+ b: number;
2364
+ };
2365
+ dynamicColorKey?: string;
2366
+ }
2367
+ interface TextureAtlasPage {
2368
+ width: number;
2369
+ height: number;
2370
+ url: string | null;
2371
+ }
2372
+ interface RectBrush {
2373
+ left: number;
2374
+ top: number;
2375
+ width: number;
2376
+ height: number;
2377
+ }
2378
+ interface LocalBasis {
2379
+ xAxis: Vec3;
2380
+ yAxis: Vec3;
2381
+ local2D: Vec2[];
2382
+ shiftX: number;
2383
+ shiftY: number;
2384
+ canvasW: number;
2385
+ canvasH: number;
2386
+ pixelArea: number;
2387
+ rawArea: number;
2388
+ }
2389
+ interface BasisOptions {
2390
+ optimize: boolean;
2391
+ fixedXAxis?: Vec3;
2392
+ boundsOrigin?: Vec3;
2393
+ snapBounds?: boolean;
2394
+ seamEdges?: Set<number>;
2395
+ }
2396
+ interface BasisHint {
2397
+ xAxis?: Vec3;
2398
+ boundsOrigin?: Vec3;
2399
+ seamEdges: Set<number>;
2400
+ textureEdgeRepairEdges?: Set<number>;
2401
+ }
2402
+ interface PolygonBasisInfo {
2403
+ pts: Vec3[];
2404
+ normal: Vec3;
2405
+ planeD: number;
2406
+ optimizable: boolean;
2407
+ }
2408
+ interface ProjectiveQuadGuardSettings {
2409
+ denomEps: number;
2410
+ maxWeightRatio: number;
2411
+ bleed: number;
2412
+ disableGuards: boolean;
2413
+ }
2414
+ interface ProjectiveQuadGuardOverrides {
2415
+ denomEps?: number;
2416
+ maxWeightRatio?: number;
2417
+ bleed?: number;
2418
+ disableGuards?: boolean;
2419
+ }
2420
+ interface ProjectiveQuadGuardGlobal {
2421
+ __polycssProjectiveQuadGuards?: ProjectiveQuadGuardOverrides;
2422
+ }
2423
+ interface ProjectiveQuadCoefficients {
2424
+ g: number;
2425
+ h: number;
2426
+ w1: number;
2427
+ w3: number;
2428
+ }
2429
+ interface StablePlanBasis {
2430
+ normal: Vec3;
2431
+ xAxis: Vec3;
2432
+ yAxis: Vec3;
2433
+ tx: number;
2434
+ ty: number;
2435
+ tz: number;
2436
+ }
2437
+ /** Options for solidTrianglePlan computation — the pure-math subset of
2438
+ * RenderTextureAtlasOptions with no DOM reference. */
2439
+ interface SolidTrianglePlanOptions {
2440
+ tileSize?: number;
2441
+ layerElevation?: number;
2442
+ directionalLight?: PolyDirectionalLight;
2443
+ ambientLight?: PolyAmbientLight;
2444
+ textureLighting?: PolyTextureLightingMode;
2445
+ solidPaintDefaults?: SolidPaintDefaults;
2446
+ strategies?: PolyRenderStrategiesOption;
2447
+ seamBleed?: PolySeamBleed;
2448
+ seamEdges?: Set<number>;
2449
+ /** Per-strategy bleed multiplier (0..1, default 1). Scales the
2450
+ * hardcoded SOLID_TRIANGLE_BLEED used as the seamBleed fallback when
2451
+ * no shared-edge bleed is present. Populated upstream from
2452
+ * `resolveBleedRatio(publicOptions.seamBleed)`. */
2453
+ bleedRatio?: number;
2454
+ /**
2455
+ * Indices (into the polygon array being planned) of polygons that the
2456
+ * directional light cannot physically reach because another polygon of
2457
+ * the same mesh is between them and the light source. Per-polygon
2458
+ * directScale is forced to 0 for indices in this set, so they receive
2459
+ * ambient lighting only — matching what a shadow-map-equivalent pass
2460
+ * would produce.
2461
+ */
2462
+ lightOccludedPolyIndices?: ReadonlySet<number>;
2463
+ }
2464
+ /** Internal solid-triangle plan options (extends SolidTrianglePlanOptions). */
2465
+ interface InternalSolidTrianglePlanOptions extends SolidTrianglePlanOptions {
2466
+ optimizeStableTriangleStyle?: boolean;
2467
+ stableTriangleColorSteps?: number;
2468
+ stableTriangleMatrixDecimals?: number;
2469
+ }
2470
+ /** Options accepted by the public {@link computeTextureAtlasPlanPublic} wrapper. */
2471
+ interface ComputeTextureAtlasPlanOptions {
2472
+ tileSize?: number;
2473
+ layerElevation?: number;
2474
+ directionalLight?: PolyDirectionalLight;
2475
+ ambientLight?: PolyAmbientLight;
2476
+ /** Shared-edge set returned by {@link buildTextureEdgeRepairSets}. */
2477
+ textureEdgeRepairEdges?: Set<number>;
2478
+ seamBleed?: PolySeamBleed;
2479
+ seamEdges?: Set<number>;
2480
+ /** Indices of polygons that the directional light cannot reach because
2481
+ * another polygon of the same mesh occludes them (precomputed via
2482
+ * {@link import("../cull/lightVisibility").computeLightVisibility}). When
2483
+ * `index ∈ lightOccludedPolyIndices`, the polygon's direct lighting term
2484
+ * is forced to zero and only ambient remains. Matches the vanilla
2485
+ * renderer's self-shadow path. */
2486
+ lightOccludedPolyIndices?: ReadonlySet<number>;
2487
+ }
2488
+
2489
+ declare function roundDecimal(value: number, decimals: number): string;
2490
+ declare function formatCssLength(value: number, decimals?: number): string;
2491
+ declare function formatMatrix3dValues(values: readonly number[], decimals?: number): string;
2492
+ declare function formatAffineMatrix3dColumns(xCol: Vec3, yCol: Vec3, zCol: Vec3, txCol: Vec3, decimals?: number): string;
2493
+ declare function formatAffineMatrix3dScalars(x0: number, x1: number, x2: number, y0: number, y1: number, y2: number, z0: number, z1: number, z2: number, tx0: number, tx1: number, tx2: number, decimals?: number): string;
2494
+ declare function formatAffineMatrix3dTransformScalars(x0: number, x1: number, x2: number, y0: number, y1: number, y2: number, z0: number, z1: number, z2: number, tx0: number, tx1: number, tx2: number, decimals?: number): string;
2495
+ declare function formatScaledMatrixFromPlan(entry: TextureAtlasPlan, scaleX: number, scaleY: number, offsetX?: number, offsetY?: number): string;
2496
+ declare function formatBorderShapeMatrix(entry: TextureAtlasPlan, bounds: BorderShapeBounds): string;
2497
+ declare function formatSolidQuadMatrix(entry: TextureAtlasPlan): string;
2498
+ declare function formatAtlasMatrix(entry: TextureAtlasPlan, atlasCanonicalSize: number): string;
2499
+ declare function formatPercent(value: number, decimals?: number): string;
2500
+ /** Format a raw comma-separated matrix3d value string with rounded decimals. */
2501
+ declare function formatMatrix3d(matrix: string, decimals?: number): string;
2502
+ /** Format a pixel CSS length value. */
2503
+ declare function formatCssLengthPx(value: number, decimals?: number): string;
2504
+ /**
2505
+ * Produce the CSS matrix3d transform for a solid-quad (`<b>`) leaf, including
2506
+ * the canonical primitive scale.
2507
+ */
2508
+ declare function formatSolidQuadEntryMatrix(entry: TextureAtlasPlan): string;
2509
+
2510
+ declare function buildTextureEdgeRepairSets(polygons: Polygon[]): Array<Set<number> | undefined>;
2511
+ declare function resolveSeamBleed(value: unknown, fallback: number): number;
2512
+ declare function normalizedSeamBleed(value: unknown): number | undefined;
2513
+ declare function safePlanSeamBleedAmount(screenPts: number[], edgeIndex: number, requested: number): number;
2514
+ declare function computePlanSeamBleedEdgeAmounts(screenPts: number[], seamEdges: ReadonlySet<number> | undefined, seamBleed: number | undefined): Map<number, number> | undefined;
2515
+ declare function seamBleedAmountArray(vertexCount: number, edgeAmounts: ReadonlyMap<number, number> | undefined): number[] | null;
2516
+ declare function computeSeamBleedInsets(screenPts: number[], edgeAmounts: ReadonlyMap<number, number> | undefined): SeamBleedInsets | undefined;
2517
+ interface SeamBleedDetectionOptions {
2518
+ tileSize?: number;
2519
+ layerElevation?: number;
2520
+ directionalLight?: unknown;
2521
+ ambientLight?: unknown;
2522
+ }
2523
+ declare function buildSeamBleedPolygonSet(polygons: Polygon[], options?: SeamBleedDetectionOptions): Set<number>;
2524
+ declare function buildSeamBleedPolygonEdges(polygons: Polygon[], options?: SeamBleedDetectionOptions): Map<number, Set<number>>;
2525
+
2526
+ type PureColorParseResult = ReturnType<typeof parsePureColor>;
2527
+ declare function cachedParsePureColor(input: string): PureColorParseResult;
2528
+ declare function parseHex(hex: string): RGB;
2529
+ declare function rgbKey({ r, g, b }: RGB): string;
2530
+ /** Returns the parsed alpha for a color string (1.0 default). */
2531
+ declare function parseAlpha(input: string): number;
2532
+ declare function rgbToHex({ r, g, b }: RGB): string;
2533
+ /**
2534
+ * Tint factors for a textured polygon, in LINEAR light space.
2535
+ *
2536
+ * Returns the per-channel multiplier that the rasterizer should apply to the
2537
+ * texture pixels' LINEAR values — matching Three.js MeshLambertMaterial:
2538
+ * lit_linear = albedo_linear × tint
2539
+ * tint = (lightColor × lambert × I + ambientColor × I_amb) / π
2540
+ *
2541
+ * Light + ambient colors are interpreted as sRGB and converted to linear.
2542
+ * The rasterizer is responsible for decoding the texture sample from sRGB
2543
+ * to linear before multiplying by these factors, then re-encoding for paint
2544
+ * (see applyTextureTint in the renderer). `directScale` is already
2545
+ * `intensity × max(n·L, 0)` (computed by the caller).
2546
+ */
2547
+ declare function textureTintFactors(directScale: number, lightColor: string, ambientColor: string, ambientIntensity: number): RGBFactors;
2548
+ declare function tintToCss({ r, g, b }: RGBFactors): string;
2549
+ declare function shadePolygon(baseColor: string, directScale: number, lightColor: string, ambientColor: string, ambientIntensity: number): string;
2550
+ declare function quantizeCssColor(input: string, steps: number): string;
2551
+ declare function rgbEqual(a: RGB | undefined, b: RGB | undefined): boolean;
2552
+ declare function stepRgbToward(current: RGB, target: RGB, maxStep: number): RGB;
2553
+ declare function rgbToCss(rgb: RGB, alpha?: number): string;
2554
+ declare function colorErrorScore(current: string | undefined, next: string): number;
2555
+
2556
+ declare function fullRectBounds(entry: TextureAtlasPlan): {
2557
+ left: number;
2558
+ top: number;
2559
+ width: number;
2560
+ height: number;
2561
+ } | null;
2562
+ declare function isFullRectSolid(entry: TextureAtlasPlan): boolean;
2563
+ declare function isSolidTrianglePlan(entry: TextureAtlasPlan): boolean;
2564
+ declare function isProjectiveQuadPlan(entry: TextureAtlasPlan): entry is TextureAtlasPlan & {
2565
+ projectiveMatrix: string;
2566
+ };
2567
+ declare function safariCssProjectiveUnsupported(userAgent: string): boolean;
2568
+ declare function incrementCount(map: Map<string, number>, key: string): void;
2569
+ declare function dominantCountKey(map: Map<string, number>): string | undefined;
2570
+ interface FilterAtlasPlansEnv {
2571
+ solidTriangleSupported: boolean;
2572
+ projectiveQuadSupported: boolean;
2573
+ borderShapeSupported: boolean;
2574
+ /** When true, non-triangle non-rect non-projective polys whose plan has
2575
+ * cornerShapeGeometryForPlan != null are excluded from the atlas (they
2576
+ * render as <u> via corner-*-shape: bevel CSS — matches vanilla's
2577
+ * createCornerShapeSolidElement path). Falsy / undefined preserves the
2578
+ * earlier core behaviour (those polys stay in atlas as <s> fallback). */
2579
+ cornerShapeSupported?: boolean;
2580
+ }
2581
+ /**
2582
+ * Filter a plan array to the subset that needs atlas packing, given the active
2583
+ * render strategies and texture-lighting mode. Plans excluded from the atlas
2584
+ * will be rendered via `<b>`, `<i>`, or `<u>` by the framework components.
2585
+ */
2586
+ declare function filterAtlasPlans(plans: Array<TextureAtlasPlan | null>, textureLighting: PolyTextureLightingMode, disabled: ReadonlySet<PolyRenderStrategy>, env: FilterAtlasPlansEnv): Array<TextureAtlasPlan | null>;
2587
+ interface GetSolidPaintDefaultsEnv {
2588
+ solidTriangleSupported: boolean;
2589
+ projectiveQuadSupported: boolean;
2590
+ cornerShapeSupported: boolean;
2591
+ borderShapeSupported: boolean;
2592
+ }
2593
+ declare function getSolidPaintDefaultsForPlansCore(plans: Array<TextureAtlasPlan | null>, textureLighting: PolyTextureLightingMode, disabled: ReadonlySet<PolyRenderStrategy>, env: GetSolidPaintDefaultsEnv, parseHexFn: (color: string) => RGB, rgbKeyFn: (rgb: RGB) => string, cornerShapeGeometryForPlanFn?: (plan: TextureAtlasPlan) => unknown): {
2594
+ paintColor?: string;
2595
+ dynamicColorKey?: string;
2596
+ dynamicColor?: RGB;
2597
+ };
2598
+
2599
+ declare function cssPoints(vertices: Vec3[], tile: number, elev: number): Vec3[];
2600
+ declare function computeSurfaceNormal(pts: Vec3[]): Vec3 | null;
2601
+ declare function isConvexPolygonPoints(points: Array<[number, number]>): boolean;
2602
+ declare function signedArea2D(points: Array<[number, number]>): number;
2603
+ declare function intersect2DLines(a0: [number, number], a1: [number, number], b0: [number, number], b1: [number, number]): [number, number] | null;
2604
+ declare function intersect2DLinesRaw(a0x: number, a0y: number, a1x: number, a1y: number, b0x: number, b0y: number, b1x: number, b1y: number): Vec2 | null;
2605
+ declare function expandClipPoints(points: number[], amount: number): number[];
2606
+ declare function offsetConvexPolygonPoints(points: number[], amount: number): number[];
2607
+ declare function offsetConvexPolygonPointsByEdgeAmounts(points: number[], amounts: readonly number[]): number[];
2608
+ declare function offsetTrianglePoints(x0: number, y0: number, x1: number, y1: number, x2: number, y2: number, amount: number): number[];
2609
+ declare function offsetStableTrianglePoints(left: number, right: number, height: number, amount: number): number[];
2610
+ declare function stableBasisFromPlan(source: TextureAtlasPlan, polygon: Polygon): StablePlanBasis | null;
2611
+ declare function stableTriangleMatrixDecimals(matrixDecimals: number | undefined): number;
2612
+
2613
+ declare function polygonContainsPoint(points: Array<[number, number]>, px?: number, py?: number): boolean;
2614
+ declare function borderShapeBoundsFromPoints(points: number[], fallbackWidth: number, fallbackHeight: number): BorderShapeBounds;
2615
+ /** Reads `entry.bleedRatio` (defaulted to 1) and scales BORDER_SHAPE_BLEED
2616
+ * accordingly. Plans are tagged with the ratio at construction time
2617
+ * (see computeTextureAtlasPlan) so every consumer gets the same value. */
2618
+ declare function borderShapeGeometryForPlan(entry: TextureAtlasPlan): BorderShapeGeometry;
2619
+ declare function simplifyCornerShapePoints(points: Array<[number, number]>): Array<[number, number]>;
2620
+ declare function cornerShapePointSides([x, y]: [number, number]): Set<CornerShapeSide> | null;
2621
+ declare function sharedCornerShapeSide(a: Set<CornerShapeSide>, b: Set<CornerShapeSide>): boolean;
2622
+ declare function cornerShapeDiagonal(aPoint: [number, number], aSides: Set<CornerShapeSide>, bPoint: [number, number], bSides: Set<CornerShapeSide>): [CornerShapeCorner, CornerShapeRadius] | null;
2623
+ declare function cornerShapeGeometryForPlan(entry: TextureAtlasPlan): CornerShapeGeometry | null;
2624
+ declare function cssBorderShapeForGeometry(points: Array<[number, number]>): string;
2625
+ declare function cssBorderShapeForPlan(entry: TextureAtlasPlan): string;
2626
+ declare function formatBorderShapeEntryMatrix(entry: TextureAtlasPlan): string;
2627
+ declare function formatBorderShapeElementStyle(entry: TextureAtlasPlan): string;
2628
+ declare function formatCornerShapeElementStyle(entry: TextureAtlasPlan, geometry: CornerShapeGeometry): string;
2629
+
2630
+ declare function computeSolidTriangleColorPlanFromNormal(polygon: Polygon, index: number, nx: number, ny: number, nz: number, options: SolidTrianglePlanOptions, includeColor: boolean, colorOverride?: string): SolidTriangleColorPlan;
2631
+ declare function computeSolidTriangleColorPlan(polygon: Polygon, index: number, options: SolidTrianglePlanOptions): SolidTriangleColorPlan | null;
2632
+ declare function computeSolidTrianglePlan(polygon: Polygon, index: number, options: SolidTrianglePlanOptions, computeOptions?: SolidTriangleComputeOptions): SolidTrianglePlan | null;
2633
+ declare function computeSolidTrianglePlanFromCssPoints(polygon: Polygon, index: number, options: SolidTrianglePlanOptions, computeOptions: SolidTriangleComputeOptions, p0x: number, p0y: number, p0z: number, p1x: number, p1y: number, p1z: number, p2x: number, p2y: number, p2z: number): SolidTrianglePlan | null;
2634
+
2635
+ declare function resolveProjectiveQuadGuards(overrides: ProjectiveQuadGuardOverrides | undefined): ProjectiveQuadGuardSettings;
2636
+ declare function computeProjectiveQuadCoefficients(q: Array<[number, number]>, guards: ProjectiveQuadGuardSettings): ProjectiveQuadCoefficients | null;
2637
+ declare function computeProjectiveQuadMatrix(screenPts: number[], xAxis: Vec3, yAxis: Vec3, normal: Vec3, tx: number, ty: number, tz: number, guards: ProjectiveQuadGuardSettings, seamBleedEdgeAmounts?: ReadonlyMap<number, number>): string | null;
2638
+ declare function dotVec(a: Vec3, b: Vec3): number;
2639
+ declare function crossVec(a: Vec3, b: Vec3): Vec3;
2640
+ declare function isBasisOptimizable(polygon: Polygon): boolean;
2641
+ declare function getPolygonBasisInfo(polygon: Polygon, tile: number, elev: number): PolygonBasisInfo | null;
2642
+ declare function compatibleSurface(a: PolygonBasisInfo | null, b: PolygonBasisInfo | null): boolean;
2643
+ declare function compatibleBleedSurface(a: PolygonBasisInfo | null, b: PolygonBasisInfo | null): boolean;
2644
+ declare function seamLightBrightness(info: PolygonBasisInfo | null, options: SolidTrianglePlanOptions): number | null;
2645
+ declare function basisAxisKey(axis: Vec3): string;
2646
+ declare function makeLocalBasis(pts: Vec3[], origin: Vec3, normal: Vec3, rawXAxis: Vec3, options?: {
2647
+ boundsOrigin?: Vec3;
2648
+ snapBounds?: boolean;
2649
+ }): LocalBasis | null;
2650
+ declare function evaluateIslandAxis(component: number[], infos: Array<PolygonBasisInfo | null>, axis: Vec3, boundsOrigin: Vec3): {
2651
+ pixelArea: number;
2652
+ rawArea: number;
2653
+ } | null;
2654
+ declare function chooseIslandXAxis(component: number[], infos: Array<PolygonBasisInfo | null>): BasisHint | null;
2655
+ declare function buildBasisHints(polygons: Polygon[], options: SolidTrianglePlanOptions): Array<BasisHint | undefined>;
2656
+ declare function chooseLocalBasis(pts: Vec3[], origin: Vec3, normal: Vec3, options: BasisOptions): LocalBasis | null;
2657
+ declare function isFullRectBasis(basis: LocalBasis): boolean;
2658
+ declare function computeUvAffine(points: Vec2[], uvs: Vec2[]): UvAffine | null;
2659
+ declare function computeUvSampleRect(uvs: Vec2[]): UvSampleRect | null;
2660
+ declare function projectTextureTriangle(triangle: TextureTriangle, tile: number, elev: number, origin: Vec3, xAxis: Vec3, yAxis: Vec3, shiftX: number, shiftY: number): TextureTrianglePlan | null;
2661
+ declare function computeTextureAtlasPlan(polygon: Polygon, index: number, options: SolidTrianglePlanOptions, projectiveQuadGuards: ProjectiveQuadGuardSettings, basisHint?: BasisHint): TextureAtlasPlan | null;
2662
+ /**
2663
+ * Compute the per-polygon layout plan for one polygon in isolation.
2664
+ *
2665
+ * This is the public single-polygon variant used by React and Vue components.
2666
+ * It does not run the cross-polygon basis-optimisation or seam-detection that
2667
+ * the full `renderPolygonsWithTextureAtlas` pipeline performs, but the
2668
+ * strategy selection (projective-quad, rect, etc.) is identical to the
2669
+ * canonical renderer.
2670
+ *
2671
+ * The `projectiveQuadOverrides` parameter is the pre-resolved override bag
2672
+ * formerly obtained from `doc.defaultView.__polycssProjectiveQuadGuards`.
2673
+ * Callers that have a Document should extract it before calling; callers in
2674
+ * browser-free environments can pass `undefined` for the default guards.
2675
+ */
2676
+ declare function computeTextureAtlasPlanPublic(polygon: Polygon, index: number, options?: ComputeTextureAtlasPlanOptions, projectiveQuadOverrides?: ProjectiveQuadGuardOverrides,
2677
+ /** Cross-polygon basis hint pre-computed via {@link buildBasisHints} on
2678
+ * the full polygon array. When supplied, it overrides the per-polygon
2679
+ * textureEdgeRepairEdges fallback below. Vanilla's renderer always passes
2680
+ * this from {@link buildBasisHints}; React/Vue mirror that path. */
2681
+ basisHintOverride?: BasisHint): TextureAtlasPlan | null;
2682
+
2683
+ declare function normalizeAtlasScale(scale: number | string | undefined): number;
2684
+ declare function atlasArea(pages: PackedPage[]): number;
2685
+ declare function autoAtlasScaleCap(pages: PackedPage[], maxDecodedBytes: number): number;
2686
+ declare function autoAtlasScale(pages: PackedPage[], maxDecodedBytes: number): number;
2687
+ declare function atlasBitmapMaxSide(pages: PackedPage[], atlasScale: number): number;
2688
+ declare function atlasDecodedBytes(pages: PackedPage[], atlasScale: number): number;
2689
+ declare function autoAtlasBudgetFactor(pages: PackedPage[], atlasScale: number, maxDecodedBytes: number): number;
2690
+ /** Returns the max decoded-bytes budget for the given device class. */
2691
+ declare function autoAtlasMaxDecodedBytes(isMobile: boolean): number;
2692
+ /** Returns the atlas canonical size for the given texture quality and device class. */
2693
+ declare function atlasCanonicalSizeForTextureQuality(textureQualityInput: TextureQuality | undefined, isMobile: boolean): number;
2694
+ declare function applyPackedAtlasCanonicalSize(packed: PackedAtlas, atlasCanonicalSize: number): PackedAtlas;
2695
+ declare function atlasCanonicalSizeForEntry(entry: TextureAtlasPlan): number;
2696
+ declare function atlasPadding(atlasScale: number): number;
2697
+ declare function packTextureAtlasPlans(plans: Array<TextureAtlasPlan | null>, atlasScale?: number): PackedAtlas;
2698
+ /**
2699
+ * Pack atlas plans and resolve atlas scale, accepting a pre-resolved isMobile
2700
+ * boolean instead of a Document reference.
2701
+ */
2702
+ declare function packTextureAtlasPlansWithScaleCore(plans: Array<TextureAtlasPlan | null>, textureQualityInput: TextureQuality | undefined, isMobile: boolean): {
2703
+ packed: PackedAtlas;
2704
+ atlasScale: number;
2705
+ atlasCanonicalSize: number;
2706
+ };
2707
+
2708
+ export { ASYNC_RENDER_BUDGET_MS, ATLAS_CANONICAL_SIZE_AUTO_DESKTOP, ATLAS_CANONICAL_SIZE_EXPLICIT, ATLAS_MAX_SIZE, ATLAS_PADDING, AUTO_ATLAS_LOW_AREA, AUTO_ATLAS_MAX_BITMAP_SIDE, AUTO_ATLAS_MAX_DECODED_BYTES_DESKTOP, AUTO_ATLAS_MAX_DECODED_BYTES_MOBILE, AUTO_ATLAS_MEDIUM_AREA, AUTO_ATLAS_SCALE_GUARD, type ArrowPolygonsOptions, type AutoRotateConfig, type AutoRotateOption, type AxesHelperOptions, BAKED_SHADOW_MIN_UP, BAKED_SHADOW_Z_SQUASH, BASE_TILE, BASIS_EPS, BORDER_SHAPE_BLEED, BORDER_SHAPE_CANONICAL_SIZE, BORDER_SHAPE_CENTER_PERCENT, BORDER_SHAPE_POINT_EPS, type BasisHint, type BasisOptions, type BorderShapeBounds, type BorderShapeGeometry, type BoxFace, type BoxFaceOptions, type BoxPolygonsOptions, CAMERA_BACKFACE_CULL_EPS, COLOR_PARSE_CACHE_MAX, CORNER_SHAPE_DUPLICATE_EPS, CORNER_SHAPE_POINT_EPS, type CameraCullNormalGroup, type CameraCullRotation, type CameraHandle, type CameraState, type CameraStyleInput, type CasterPolyItem, type ComputeReceiverShadowFacesInput, type ComputeTextureAtlasPlanOptions, type ConePolygonsOptions, type CornerShapeCorner, type CornerShapeGeometry, type CornerShapeRadius, type CornerShapeSide, type CoverPlanarPolygonsOptions, type CullInteriorOptions, type CylinderPolygonsOptions, DECIMAL_SCALES, DEFAULT_AMBIENT_COLOR, DEFAULT_AMBIENT_INTENSITY, DEFAULT_ATLAS_CSS_DECIMALS, DEFAULT_BORDER_SHAPE_DECIMALS, DEFAULT_CAMERA_STATE, DEFAULT_LIGHT_COLOR, DEFAULT_LIGHT_DIR, DEFAULT_LIGHT_INTENSITY, DEFAULT_MATRIX_DECIMALS, DEFAULT_PROJECTION, DEFAULT_SEAM_BLEED, DEFAULT_SEAM_FACET_SPLIT_OPTIONS, DEFAULT_SEAM_OVERLAP_OPTIONS, DEFAULT_TILE, type DedupeOverlappingPolygonsOptions, type DodecahedronPolygonsOptions, type EdgeOwners, type FilterAtlasPlansEnv, type GetSolidPaintDefaultsEnv, type GltfParseOptions, type IcosahedronPolygonsOptions, type InternalSolidTrianglePlanOptions, type LoadMeshOptions, type LocalBasis, type LoopMode, LoopOnce, LoopPingPong, LoopRepeat, MAX_ATLAS_SCALE, MIN_ATLAS_SCALE, type MeshResolution, type MtlParseResult, type NormalizeResult, type ObjParseOptions, type OctahedronPolygonsOptions, type OptimizeAnimatedMeshPolygonsOptions, type OptimizeMeshParseResultOptions, type OptimizeMeshPolygonsOptions, PROJECTIVE_QUAD_BLEED, PROJECTIVE_QUAD_DENOM_EPS, PROJECTIVE_QUAD_MAX_WEIGHT_RATIO, type PackedAtlas, type PackedPage, type PackedTextureAtlasEntry, type PackingPage, type PackingShelf, type ParseAnimationClip, type ParseAnimationController, type ParseResult, type ParseStlColor, type ParseStlSolid, type ParseStlTopology, type ParsedColor, type PlanePolygonsOptions, type PolyAmbientLight, type PolyAnimationAction, type ParseAnimationClip as PolyAnimationClip, type PolyAnimationMixer, type PolyAnimationTarget, type PolyDirectionalLight, type PolyMaterial, type PolyRenderStrategiesOption, type PolyRenderStrategy, type PolySeamBleed, type PolySeamBleedEdgeValue, type PolySeamBleedEdges, type PolyTextureAlphaMode, type PolyTextureLightingMode, type PolyTextureWrap, type PolyTextureWrapMode, type PolyVoxelCell, type PolyVoxelSource, type Polygon, type PolygonBasisInfo, type PolygonFace, type ProjectiveQuadCoefficients, type ProjectiveQuadGuardGlobal, type ProjectiveQuadGuardOverrides, type ProjectiveQuadGuardSettings, QUAT_IDENTITY, type Quat, RECEIVER_NORMAL_TOL, RECEIVER_OFFSET_TOL, RECEIVER_OUTLINE_EXPAND, RECT_EPS, type RGB, type RGBFactors, type ReceiverCasterInput, type ReceiverFacePlane, type ReceiverPlaneGroup, type ReceiverShadowFaceSpec, type ReceiverShadowPath, type RectBrush, type RingPolygonsOptions, type RingQuadPolygonsOptions, SEAM_LIGHT_EPS, SOLID_QUAD_CANONICAL_SIZE, SOLID_TRIANGLE_BLEED, SOLID_TRIANGLE_CANONICAL_SIZE, SOLID_TRIANGLE_LARGE_BORDER_CANONICAL_SIZE, SURFACE_DISTANCE_EPS, SURFACE_NORMAL_EPS, type SceneBbox, type SceneContext, type SceneContextBuildArgs, type SceneContextBuildResult, type ScreenToWorldOptions, type SeamBleedInsets, type SeamFacetSplitCandidate, type SeamFacetSplitCandidateReason, type SeamFacetSplitOptions, type SeamFacetSplitReport, type SeamOverlapCandidate, type SeamOverlapCandidateKind, type SeamOverlapDiagnostics, type SeamOverlapOptions, type SimplifyTriangleMeshPolygonsOptions, type SolidPaintDefaults, type SolidTextureSampleOptions, type SolidTriangleBasis, type SolidTriangleColorPlan, type SolidTriangleComputeOptions, type SolidTriangleFrame, type SolidTrianglePlan, type SolidTrianglePlanOptions, type SolidTrianglePrimitive, type SpherePolygonsOptions, type StablePlanBasis, type StableTriangleColorState, type StlParseOptions, TEXTURE_EDGE_REPAIR_ALPHA_MIN, TEXTURE_EDGE_REPAIR_RADIUS, TEXTURE_EDGE_REPAIR_SOURCE_ALPHA_MIN, TEXTURE_TRIANGLE_BLEED, type TetrahedronPolygonsOptions, type TextureAtlasPage, type TextureAtlasPlan, type TexturePaintMetrics, type TexturePaintMetricsOptions, type TextureQuality, type TextureTriangle, type TextureTrianglePlan, type TorusPolygonsOptions, type UvAffine, type UvSampleRect, VOXEL_CAMERA_CULL_AXIS_EPS, VOXEL_CAMERA_CULL_NORMAL_LIMIT, type Vec2, type Vec3, type VoxParseOptions, applyPackedAtlasCanonicalSize, arrowPolygons, atlasArea, atlasBitmapMaxSide, atlasCanonicalSizeForEntry, atlasCanonicalSizeForTextureQuality, atlasDecodedBytes, atlasPadding, autoAtlasBudgetFactor, autoAtlasMaxDecodedBytes, autoAtlasScale, autoAtlasScaleCap, axesHelperPolygons, bakeSolidTextureSampledPolygons, bakeSolidTextureSamples, basisAxisKey, borderShapeBoundsFromPoints, borderShapeGeometryForPlan, boxPolygons, buildBakedShadowProjectionMatrix, buildBasisHints, buildEdgeOwners, buildSceneContext, buildSeamBleedPolygonEdges, buildSeamBleedPolygonSet, buildSharedEdgeMap, buildTextureEdgeRepairSets, cachedParsePureColor, cameraCullNormalGroups, cameraCullNormalGroupsFromPolygons, cameraCullNormalKey, cameraCullVisibleSignature, cameraFacingDepth, chooseIslandXAxis, chooseLocalBasis, clampChannel, classifyFacing, clipPolygonToConvex2D, colorErrorScore, compatibleBleedSurface, compatibleSurface, computeLightVisibility, computePlanSeamBleedEdgeAmounts, computeProjectiveQuadCoefficients, computeProjectiveQuadMatrix, computeReceiverShadowFaces, computeSceneBbox, computeSeamBleedInsets, computeShapeLighting, computeSolidTriangleColorPlan, computeSolidTriangleColorPlanFromNormal, computeSolidTrianglePlan, computeSolidTrianglePlanFromCssPoints, computeSurfaceNormal, computeTextureAtlasPlan, computeTextureAtlasPlanPublic, computeTexturePaintMetrics, computeUvAffine, computeUvSampleRect, conePolygons, convexHull2D, cornerShapeDiagonal, cornerShapeGeometryForPlan, cornerShapePointSides, coverPlanarPolygons, createIsometricCamera, createPolyAnimationMixer, crossVec, cssBorderShapeForGeometry, cssBorderShapeForPlan, cssPoints, cullInteriorPolygons, cylinderPolygons, dedupeOverlappingPolygons, dodecahedronPolygons, dominantCountKey, dotVec, ensureCcw2D, eulerXYZFromQuat, evaluateIslandAxis, expandClipPoints, expandConvexHullOutward, extractSilhouetteLoops, filterAtlasPlans, findOverlappingPolygonDuplicates, formatAffineMatrix3dColumns, formatAffineMatrix3dScalars, formatAffineMatrix3dTransformScalars, formatAtlasMatrix, formatBorderShapeElementStyle, formatBorderShapeEntryMatrix, formatBorderShapeMatrix, formatColor, formatCornerShapeElementStyle, formatCssLength, formatCssLengthPx, formatMatrix3d, formatMatrix3dValues, formatPercent, formatScaledMatrixFromPlan, formatSolidQuadEntryMatrix, formatSolidQuadMatrix, fullRectBounds, getPolygonBasisInfo, getSolidPaintDefaultsForPlansCore, groupReceiverFaceGroups, icosahedronPolygons, incrementCount, intersect2DLines, intersect2DLinesRaw, inverseRotateVec3, isAxisAlignedSurfaceNormal, isBakedShadowCaster, isBasisOptimizable, isConvexPolygonPoints, isFullRectBasis, isFullRectSolid, isProjectiveQuadPlan, isSolidTrianglePlan, isVoxelCameraCullableNormalGroups, loadMesh, makeLocalBasis, mergePolygons, meshScaleVec3, normalFacesCamera, normalizeAtlasScale, normalizeInvertMultiplier, normalizePolygons, normalizedSeamBleed, octahedronPolygons, offsetConvexPolygonPoints, offsetConvexPolygonPointsByEdgeAmounts, offsetStableTrianglePoints, offsetTrianglePoints, optimizeAnimatedMeshPolygons, optimizeMeshParseResult, optimizeMeshPolygons, packTextureAtlasPlans, packTextureAtlasPlansWithScaleCore, parseAlpha, parseColor, parseGltf, parseHex, parseHexColor, parseMtl, parseObj, parsePureColor, parseRgbColor, parseStl, parseVox, planePolygons, polygonContainsPoint, polygonCssSurfaceNormal, polygonFaces, polygonFacesCamera, polygonSignedArea2D, prepareCasterEdgeOwners, prepareCasterPolyItems, prepareReceiverFacePlanes, projectCssVertexToGround, projectTextureTriangle, quantizeCssColor, quatFromAxisAngle, quatFromEulerXYZ, quatMultiply, repairMeshSeams, resolveBleedRatio, resolveProjectiveQuadGuards, resolveSeamBleed, rgbEqual, rgbKey, rgbToCss, rgbToHex, ringPolygons, ringQuadPolygons, rotateVec3, rotateVec3InWrapperCssFrame, roundDecimal, safariCssProjectiveUnsupported, safePlanSeamBleedAmount, screenToWorldOnSphere, screenToWorldRay, seamBleedAmountArray, seamFacetSplitPolygons, seamFacetSplitReport, seamLightBrightness, seamOverlapDiagnostics, seamOverlapPolygons, seamOverlapReport, shadeColor, shadePolygon, sharedCornerShapeSide, signedArea2D, simplifyCornerShapePoints, simplifyTriangleMeshPolygons, spherePolygons, stableBasisFromPlan, stableTriangleMatrixDecimals, stepRgbToward, tetrahedronPolygons, textureTintFactors, tintToCss, torusPolygons, worldCssForMesh, worldDirectionToCss, worldDirectionalLightToCss, worldPositionToCss };