@driftengine/core 4.5.0 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/CHANGELOG.json +51 -0
  2. package/README.md +1 -1
  3. package/dist/geometry/boxCsg.d.ts +28 -0
  4. package/dist/geometry/boxCsg.js +139 -0
  5. package/dist/geometry/csg.d.ts +36 -0
  6. package/dist/geometry/csg.js +329 -0
  7. package/dist/geometry/meshBuilder.js +13 -0
  8. package/dist/geometry/meshConcat.js +1 -0
  9. package/dist/geometry/pixelFont.js +2 -0
  10. package/dist/geometry/solid.d.ts +61 -0
  11. package/dist/geometry/solid.js +152 -0
  12. package/dist/geometry/solidFlat.d.ts +35 -0
  13. package/dist/geometry/solidFlat.js +428 -0
  14. package/dist/geometry/solidHarness.d.ts +19 -0
  15. package/dist/geometry/solidHarness.js +66 -0
  16. package/dist/geometry/solidNormals.d.ts +17 -0
  17. package/dist/geometry/solidNormals.js +86 -0
  18. package/dist/geometry/solidRound.d.ts +41 -0
  19. package/dist/geometry/solidRound.js +323 -0
  20. package/dist/geometry/solidWriter.d.ts +16 -0
  21. package/dist/geometry/solidWriter.js +42 -0
  22. package/dist/index.d.ts +23 -2
  23. package/dist/index.js +13 -1
  24. package/dist/render/backend/api.d.ts +17 -3
  25. package/dist/render/backend/drawFog.d.ts +26 -0
  26. package/dist/render/backend/drawFog.js +30 -0
  27. package/dist/render/backend/materialChanges.d.ts +2 -1
  28. package/dist/render/backend/materialChanges.js +6 -1
  29. package/dist/render/backend/webgl2/instanced.d.ts +24 -5
  30. package/dist/render/backend/webgl2/instanced.js +72 -15
  31. package/dist/render/backend/webgl2/renderer.d.ts +122 -8
  32. package/dist/render/backend/webgl2/renderer.js +238 -121
  33. package/dist/render/backend/webgpu/buffers.d.ts +10 -0
  34. package/dist/render/backend/webgpu/buffers.js +34 -3
  35. package/dist/render/backend/webgpu/depthPass.js +5 -1
  36. package/dist/render/backend/webgpu/drawCommand.d.ts +5 -0
  37. package/dist/render/backend/webgpu/drawCommand.js +1 -0
  38. package/dist/render/backend/webgpu/flatPass.d.ts +4 -1
  39. package/dist/render/backend/webgpu/flatPass.js +38 -9
  40. package/dist/render/backend/webgpu/glassTintPass.js +2 -1
  41. package/dist/render/backend/webgpu/instanceCullPass.d.ts +34 -0
  42. package/dist/render/backend/webgpu/instanceCullPass.js +144 -0
  43. package/dist/render/backend/webgpu/instanced.d.ts +11 -1
  44. package/dist/render/backend/webgpu/instanced.js +21 -3
  45. package/dist/render/backend/webgpu/latePass.d.ts +6 -2
  46. package/dist/render/backend/webgpu/latePass.js +11 -4
  47. package/dist/render/backend/webgpu/postPass.d.ts +1 -1
  48. package/dist/render/backend/webgpu/renderer.d.ts +100 -10
  49. package/dist/render/backend/webgpu/renderer.js +462 -201
  50. package/dist/render/backend/webgpu/sdfTextPass.js +6 -1
  51. package/dist/render/backend/webgpu/shaders/instanceCull.wgsl.d.ts +18 -0
  52. package/dist/render/backend/webgpu/shaders/instanceCull.wgsl.js +59 -0
  53. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +17 -3
  54. package/dist/render/backend/webgpu/surfaceTexturePass.js +77 -38
  55. package/dist/render/driftLight/bake.d.ts +7 -0
  56. package/dist/render/driftLight/bake.js +61 -56
  57. package/dist/render/driftLight/denseField.d.ts +46 -0
  58. package/dist/render/driftLight/denseField.js +109 -0
  59. package/dist/render/driftLight/lightField.d.ts +10 -5
  60. package/dist/render/driftLight/lightField.js +25 -19
  61. package/dist/render/driftLight/presence.d.ts +50 -0
  62. package/dist/render/driftLight/presence.js +43 -0
  63. package/dist/render/driftLight/uniforms.d.ts +3 -3
  64. package/dist/render/driftLight/uniforms.js +5 -6
  65. package/dist/render/driftLight/worldLightField.d.ts +41 -0
  66. package/dist/render/driftLight/worldLightField.js +66 -0
  67. package/dist/render/fixtureAtlas.d.ts +20 -0
  68. package/dist/render/fixtureAtlas.js +49 -0
  69. package/dist/render/instanceCull.d.ts +63 -0
  70. package/dist/render/instanceCull.js +160 -0
  71. package/dist/render/instances.d.ts +15 -0
  72. package/dist/render/lightBudget.d.ts +22 -33
  73. package/dist/render/lightBudget.js +24 -35
  74. package/dist/render/lightGrid.d.ts +42 -0
  75. package/dist/render/lightGrid.js +87 -0
  76. package/dist/render/mesh.d.ts +18 -24
  77. package/dist/render/mesh.js +51 -68
  78. package/dist/render/occlusion.d.ts +10 -0
  79. package/dist/render/occlusion.js +36 -7
  80. package/dist/render/pointLightSelection.d.ts +7 -0
  81. package/dist/render/pointLightSelection.js +29 -4
  82. package/dist/render/rainField.d.ts +13 -0
  83. package/dist/render/rainField.js +27 -5
  84. package/dist/render/recon/lateDepth.d.ts +11 -0
  85. package/dist/render/recon/lateDepth.js +70 -0
  86. package/dist/render/recon/resolve.d.ts +34 -4
  87. package/dist/render/recon/resolve.js +161 -20
  88. package/dist/render/rendererHarness.js +4 -0
  89. package/dist/render/sceneTarget.d.ts +2 -0
  90. package/dist/render/sceneTarget.js +2 -0
  91. package/dist/render/shaders/depth.js +12 -9
  92. package/dist/render/shaders/flat/driftLight.d.ts +1 -1
  93. package/dist/render/shaders/flat/driftLight.js +32 -7
  94. package/dist/render/shaders/flat/index.js +5 -3
  95. package/dist/render/shaders/flat/main.d.ts +1 -1
  96. package/dist/render/shaders/flat/main.js +75 -23
  97. package/dist/render/shaders/flat/preamble.js +75 -20
  98. package/dist/render/shaders/flat/surfaceEffects.d.ts +29 -0
  99. package/dist/render/shaders/flat/surfaceEffects.js +152 -0
  100. package/dist/render/shaders/generated/depth.wgsl.d.ts +8 -8
  101. package/dist/render/shaders/generated/depth.wgsl.js +8 -8
  102. package/dist/render/shaders/generated/flat.wgsl.d.ts +1320 -1320
  103. package/dist/render/shaders/generated/flat.wgsl.js +1359 -1345
  104. package/dist/render/shaders/generated/lightVolume.wgsl.js +18 -4
  105. package/dist/render/shaders/generated/rush.wgsl.d.ts +21 -16
  106. package/dist/render/shaders/generated/rush.wgsl.js +21 -16
  107. package/dist/render/shaders/generated/sdfText.wgsl.d.ts +2 -2
  108. package/dist/render/shaders/generated/sdfText.wgsl.js +2 -2
  109. package/dist/render/shaders/generated/text.wgsl.d.ts +1 -1
  110. package/dist/render/shaders/generated/text.wgsl.js +1 -1
  111. package/dist/render/shaders/gi/driftLight.wgsl.d.ts +1 -1
  112. package/dist/render/shaders/gi/driftLight.wgsl.js +27 -4
  113. package/dist/render/shaders/recon/lateDepth.wgsl.js +38 -13
  114. package/dist/render/shaders/recon/resolve.wgsl.d.ts +8 -7
  115. package/dist/render/shaders/recon/resolve.wgsl.js +161 -27
  116. package/dist/render/shaders/rush.js +11 -1
  117. package/dist/render/shaders/sdfText.d.ts +1 -1
  118. package/dist/render/shaders/sdfText.js +3 -2
  119. package/dist/render/shaders/text.d.ts +1 -1
  120. package/dist/render/shaders/text.js +18 -9
  121. package/dist/render/surfaceEffects.d.ts +83 -0
  122. package/dist/render/surfaceEffects.js +75 -0
  123. package/dist/render/surfaceScene.d.ts +8 -0
  124. package/dist/render/surfaceScene.js +8 -0
  125. package/dist/render/surfaceTexture.d.ts +18 -2
  126. package/dist/render/surfaceTexture.js +68 -15
  127. package/dist/render/temporalAa.d.ts +25 -6
  128. package/dist/render/temporalAa.js +45 -15
  129. package/dist/render/textureSource.d.ts +30 -0
  130. package/dist/render/textureSource.js +53 -0
  131. package/dist/render/vertexDefaults.js +2 -0
  132. package/dist/world/hlod.d.ts +104 -0
  133. package/dist/world/hlod.js +214 -0
  134. package/package.json +6 -6
  135. package/src/geometry/boxCsg.ts +186 -0
  136. package/src/geometry/csg.ts +384 -0
  137. package/src/geometry/meshBuilder.ts +15 -0
  138. package/src/geometry/meshConcat.ts +1 -0
  139. package/src/geometry/pixelFont.ts +2 -0
  140. package/src/geometry/solid.ts +189 -0
  141. package/src/geometry/solidFlat.ts +522 -0
  142. package/src/geometry/solidHarness.ts +83 -0
  143. package/src/geometry/solidNormals.ts +103 -0
  144. package/src/geometry/solidRound.ts +443 -0
  145. package/src/geometry/solidWriter.ts +58 -0
  146. package/src/index.ts +51 -1
  147. package/src/render/backend/api.ts +21 -2
  148. package/src/render/backend/drawFog.ts +31 -0
  149. package/src/render/backend/materialChanges.ts +9 -2
  150. package/src/render/backend/webgl2/instanced.ts +80 -14
  151. package/src/render/backend/webgl2/renderer.ts +300 -143
  152. package/src/render/backend/webgpu/buffers.ts +42 -3
  153. package/src/render/backend/webgpu/depthPass.ts +5 -1
  154. package/src/render/backend/webgpu/drawCommand.ts +6 -0
  155. package/src/render/backend/webgpu/flatPass.ts +41 -11
  156. package/src/render/backend/webgpu/glassTintPass.ts +2 -1
  157. package/src/render/backend/webgpu/instanceCullPass.ts +165 -0
  158. package/src/render/backend/webgpu/instanced.ts +21 -3
  159. package/src/render/backend/webgpu/latePass.ts +12 -3
  160. package/src/render/backend/webgpu/renderer.ts +540 -226
  161. package/src/render/backend/webgpu/sdfTextPass.ts +6 -1
  162. package/src/render/backend/webgpu/shaders/instanceCull.wgsl.ts +61 -0
  163. package/src/render/backend/webgpu/surfaceTexturePass.ts +90 -50
  164. package/src/render/driftLight/bake.ts +85 -62
  165. package/src/render/driftLight/denseField.ts +149 -0
  166. package/src/render/driftLight/lightField.ts +33 -18
  167. package/src/render/driftLight/presence.ts +69 -0
  168. package/src/render/driftLight/uniforms.ts +8 -9
  169. package/src/render/driftLight/worldLightField.ts +83 -0
  170. package/src/render/fixtureAtlas.ts +63 -0
  171. package/src/render/instanceCull.ts +205 -0
  172. package/src/render/instances.ts +16 -0
  173. package/src/render/lightBudget.ts +25 -36
  174. package/src/render/lightGrid.ts +130 -0
  175. package/src/render/mesh.ts +59 -72
  176. package/src/render/occlusion.ts +51 -7
  177. package/src/render/pointLightSelection.ts +72 -4
  178. package/src/render/rainField.ts +39 -5
  179. package/src/render/recon/lateDepth.ts +83 -0
  180. package/src/render/recon/resolve.ts +206 -20
  181. package/src/render/rendererHarness.ts +4 -0
  182. package/src/render/sceneTarget.ts +4 -0
  183. package/src/render/shaders/depth.ts +12 -9
  184. package/src/render/shaders/flat/driftLight.ts +32 -7
  185. package/src/render/shaders/flat/index.ts +5 -3
  186. package/src/render/shaders/flat/main.ts +75 -23
  187. package/src/render/shaders/flat/preamble.ts +75 -20
  188. package/src/render/shaders/flat/surfaceEffects.ts +152 -0
  189. package/src/render/shaders/generated/depth.wgsl.ts +8 -8
  190. package/src/render/shaders/generated/flat.wgsl.ts +1361 -1345
  191. package/src/render/shaders/generated/lightVolume.wgsl.ts +20 -4
  192. package/src/render/shaders/generated/rush.wgsl.ts +21 -16
  193. package/src/render/shaders/generated/sdfText.wgsl.ts +2 -2
  194. package/src/render/shaders/generated/text.wgsl.ts +1 -1
  195. package/src/render/shaders/gi/driftLight.wgsl.ts +27 -4
  196. package/src/render/shaders/recon/lateDepth.wgsl.ts +38 -13
  197. package/src/render/shaders/recon/resolve.wgsl.ts +161 -27
  198. package/src/render/shaders/rush.ts +11 -1
  199. package/src/render/shaders/sdfText.ts +3 -2
  200. package/src/render/shaders/text.ts +18 -9
  201. package/src/render/surfaceEffects.ts +132 -0
  202. package/src/render/surfaceScene.ts +16 -0
  203. package/src/render/surfaceTexture.ts +110 -19
  204. package/src/render/temporalAa.ts +78 -14
  205. package/src/render/textureSource.ts +72 -0
  206. package/src/render/vertexDefaults.ts +2 -0
  207. package/src/world/hlod.ts +290 -0
@@ -0,0 +1,61 @@
1
+ /**
2
+ * A solid: an indexed triangle surface with normals and texture coordinates, and the operations
3
+ * every procedural builder needs on one — place it, join several, measure it, and hand it to the
4
+ * renderer as a mesh.
5
+ *
6
+ * **Why a type of its own rather than `MeshData`.** A mesh carries a dozen per-vertex channels a
7
+ * renderer reads (colour, emissive, roughness, grain, joints…) and a solid is what exists before any
8
+ * of them are decided: shape alone. Booleans, smoothing and transforms are defined on shape, and
9
+ * dragging eleven channels through a boolean would mean deciding how each one interpolates across a
10
+ * cut. The caller paints a finished solid once, with `solidToMesh` or its own writer.
11
+ *
12
+ * **Named `Solid` because `*Shape` is taken**: the physics colliders the core barrel re-exports are
13
+ * `boxShape`, `cylinderShape` and the rest, and a second meaning for the word would read as the
14
+ * same thing. Every primitive here is closed except `solidQuad`, a single-sided plane.
15
+ *
16
+ * Built at load or bake time, never per frame, so these functions allocate their results.
17
+ */
18
+ import type { MeshData } from '../render/mesh.ts';
19
+ import type { Vec3 } from '../math/color.ts';
20
+ export interface Solid {
21
+ /** Three floats a vertex. */
22
+ readonly positions: Float32Array;
23
+ /** Three floats a vertex, unit length, outward. */
24
+ readonly normals: Float32Array;
25
+ /**
26
+ * Two floats a vertex, parametric: 0..1 across each face of a box, around and along a round
27
+ * primitive. Tiling in metres is the caller's, who knows the sizes.
28
+ */
29
+ readonly uvs: Float32Array;
30
+ /** Counter-clockwise triangles seen from outside. */
31
+ readonly indices: Uint32Array;
32
+ }
33
+ export declare function emptySolid(): Solid;
34
+ /**
35
+ * `solid` moved by a column-major 4×4 model matrix.
36
+ *
37
+ * **Normals go through the inverse transpose of the upper 3×3**, then are renormalised. Rotating
38
+ * them by the matrix itself is right only for rotations and uniform scales; under a non-uniform one
39
+ * — a unit cylinder scaled to a post, a sphere to an ellipsoid — it leaves every normal leaning
40
+ * toward the stretched axis, and the surface is lit as if it faced somewhere else, which reads as a
41
+ * shading bug rather than a maths one.
42
+ *
43
+ * **A mirror reverses the winding**, because a negative determinant turns every triangle inside
44
+ * out; without the reversal a mirrored solid is culled from outside and its volume goes negative.
45
+ */
46
+ export declare function transformSolid(solid: Solid, model: ArrayLike<number>): Solid;
47
+ /** Several solids as one, each one's indices offset past the vertices before it. */
48
+ export declare function mergeSolids(solids: readonly Solid[]): Solid;
49
+ /**
50
+ * The enclosed volume by the divergence theorem: the sum of each triangle's signed tetrahedron
51
+ * with the origin. Positive for a closed, outward-wound solid; meaningless for an open one.
52
+ *
53
+ * Exact for any closed surface, T-junctions included, which is why the CSG tests can use it on a
54
+ * boolean's output where an edge-pairing check cannot.
55
+ */
56
+ export declare function solidVolume(solid: Solid): number;
57
+ /**
58
+ * A solid painted one colour, as a mesh the renderer draws. `emissive` is the usual scalar: the
59
+ * surface's own colour scaled, gated by the environment's night factor like every emissive.
60
+ */
61
+ export declare function solidToMesh(solid: Solid, color: Vec3, emissive?: number): MeshData;
@@ -0,0 +1,152 @@
1
+ export function emptySolid() {
2
+ return {
3
+ positions: new Float32Array(0),
4
+ normals: new Float32Array(0),
5
+ uvs: new Float32Array(0),
6
+ indices: new Uint32Array(0),
7
+ };
8
+ }
9
+ /**
10
+ * `solid` moved by a column-major 4×4 model matrix.
11
+ *
12
+ * **Normals go through the inverse transpose of the upper 3×3**, then are renormalised. Rotating
13
+ * them by the matrix itself is right only for rotations and uniform scales; under a non-uniform one
14
+ * — a unit cylinder scaled to a post, a sphere to an ellipsoid — it leaves every normal leaning
15
+ * toward the stretched axis, and the surface is lit as if it faced somewhere else, which reads as a
16
+ * shading bug rather than a maths one.
17
+ *
18
+ * **A mirror reverses the winding**, because a negative determinant turns every triangle inside
19
+ * out; without the reversal a mirrored solid is culled from outside and its volume goes negative.
20
+ */
21
+ export function transformSolid(solid, model) {
22
+ const m = (i) => model[i] ?? 0;
23
+ const a = m(0);
24
+ const b = m(4);
25
+ const c = m(8);
26
+ const d = m(1);
27
+ const e = m(5);
28
+ const f = m(9);
29
+ const g = m(2);
30
+ const h = m(6);
31
+ const k = m(10);
32
+ /* Cofactors of the upper 3×3 in row-major (a b c / d e f / g h k): the inverse transpose up to
33
+ the determinant's scale, which renormalising removes — except its sign, kept below. */
34
+ const c00 = e * k - f * h;
35
+ const c01 = f * g - d * k;
36
+ const c02 = d * h - e * g;
37
+ const c10 = c * h - b * k;
38
+ const c11 = a * k - c * g;
39
+ const c12 = b * g - a * h;
40
+ const c20 = b * f - c * e;
41
+ const c21 = c * d - a * f;
42
+ const c22 = a * e - b * d;
43
+ const det = a * c00 + b * c01 + c * c02;
44
+ const sign = det < 0 ? -1 : 1;
45
+ const count = solid.positions.length / 3;
46
+ const positions = new Float32Array(count * 3);
47
+ const normals = new Float32Array(count * 3);
48
+ for (let v = 0; v < count; v++) {
49
+ const x = solid.positions[v * 3] ?? 0;
50
+ const y = solid.positions[v * 3 + 1] ?? 0;
51
+ const z = solid.positions[v * 3 + 2] ?? 0;
52
+ positions[v * 3] = a * x + b * y + c * z + m(12);
53
+ positions[v * 3 + 1] = d * x + e * y + f * z + m(13);
54
+ positions[v * 3 + 2] = g * x + h * y + k * z + m(14);
55
+ const nx = solid.normals[v * 3] ?? 0;
56
+ const ny = solid.normals[v * 3 + 1] ?? 0;
57
+ const nz = solid.normals[v * 3 + 2] ?? 0;
58
+ const tx = (c00 * nx + c01 * ny + c02 * nz) * sign;
59
+ const ty = (c10 * nx + c11 * ny + c12 * nz) * sign;
60
+ const tz = (c20 * nx + c21 * ny + c22 * nz) * sign;
61
+ const len = Math.hypot(tx, ty, tz) || 1;
62
+ normals[v * 3] = tx / len;
63
+ normals[v * 3 + 1] = ty / len;
64
+ normals[v * 3 + 2] = tz / len;
65
+ }
66
+ let indices = solid.indices;
67
+ if (det < 0) {
68
+ indices = new Uint32Array(solid.indices.length);
69
+ for (let t = 0; t < indices.length; t += 3) {
70
+ indices[t] = solid.indices[t] ?? 0;
71
+ indices[t + 1] = solid.indices[t + 2] ?? 0;
72
+ indices[t + 2] = solid.indices[t + 1] ?? 0;
73
+ }
74
+ }
75
+ return { positions, normals, uvs: solid.uvs, indices };
76
+ }
77
+ /** Several solids as one, each one's indices offset past the vertices before it. */
78
+ export function mergeSolids(solids) {
79
+ if (solids.length === 1 && solids[0] !== undefined)
80
+ return solids[0];
81
+ let vertices = 0;
82
+ let triangles = 0;
83
+ for (const s of solids) {
84
+ vertices += s.positions.length / 3;
85
+ triangles += s.indices.length;
86
+ }
87
+ const positions = new Float32Array(vertices * 3);
88
+ const normals = new Float32Array(vertices * 3);
89
+ const uvs = new Float32Array(vertices * 2);
90
+ const indices = new Uint32Array(triangles);
91
+ let v = 0;
92
+ let t = 0;
93
+ for (const s of solids) {
94
+ positions.set(s.positions, v * 3);
95
+ normals.set(s.normals, v * 3);
96
+ uvs.set(s.uvs, v * 2);
97
+ for (let i = 0; i < s.indices.length; i++)
98
+ indices[t + i] = (s.indices[i] ?? 0) + v;
99
+ v += s.positions.length / 3;
100
+ t += s.indices.length;
101
+ }
102
+ return { positions, normals, uvs, indices };
103
+ }
104
+ /**
105
+ * The enclosed volume by the divergence theorem: the sum of each triangle's signed tetrahedron
106
+ * with the origin. Positive for a closed, outward-wound solid; meaningless for an open one.
107
+ *
108
+ * Exact for any closed surface, T-junctions included, which is why the CSG tests can use it on a
109
+ * boolean's output where an edge-pairing check cannot.
110
+ */
111
+ export function solidVolume(solid) {
112
+ const p = solid.positions;
113
+ const idx = solid.indices;
114
+ let sum = 0;
115
+ for (let t = 0; t < idx.length; t += 3) {
116
+ const a = (idx[t] ?? 0) * 3;
117
+ const b = (idx[t + 1] ?? 0) * 3;
118
+ const c = (idx[t + 2] ?? 0) * 3;
119
+ const ax = p[a] ?? 0;
120
+ const ay = p[a + 1] ?? 0;
121
+ const az = p[a + 2] ?? 0;
122
+ const bx = p[b] ?? 0;
123
+ const by = p[b + 1] ?? 0;
124
+ const bz = p[b + 2] ?? 0;
125
+ const cx = p[c] ?? 0;
126
+ const cy = p[c + 1] ?? 0;
127
+ const cz = p[c + 2] ?? 0;
128
+ sum += ax * (by * cz - bz * cy) - ay * (bx * cz - bz * cx) + az * (bx * cy - by * cx);
129
+ }
130
+ return sum / 6;
131
+ }
132
+ /**
133
+ * A solid painted one colour, as a mesh the renderer draws. `emissive` is the usual scalar: the
134
+ * surface's own colour scaled, gated by the environment's night factor like every emissive.
135
+ */
136
+ export function solidToMesh(solid, color, emissive = 0) {
137
+ const count = solid.positions.length / 3;
138
+ const colors = new Float32Array(count * 3);
139
+ for (let v = 0; v < count; v++) {
140
+ colors[v * 3] = color[0];
141
+ colors[v * 3 + 1] = color[1];
142
+ colors[v * 3 + 2] = color[2];
143
+ }
144
+ return {
145
+ positions: solid.positions,
146
+ normals: solid.normals,
147
+ colors,
148
+ emissive: new Float32Array(count).fill(emissive),
149
+ uvs: solid.uvs,
150
+ indices: solid.indices,
151
+ };
152
+ }
@@ -0,0 +1,35 @@
1
+ import type { Solid } from './solid.ts';
2
+ import type { Vec3 } from '../math/color.ts';
3
+ /** A box of full extents `x, y, z`, faceted: four vertices a face, texture 0..1 across each. */
4
+ export declare function solidBox(x: number, y: number, z: number): Solid;
5
+ /** A single-sided plane of `x` by `y` in XY, facing +Z. The one open solid. */
6
+ export declare function solidQuad(x: number, y: number): Solid;
7
+ /**
8
+ * A box of full extents with every edge rounded to `radius`, `segments` steps per rounded band.
9
+ *
10
+ * Built as a Minkowski sum: each face is a grid whose points are pushed out from the inner box —
11
+ * the box shrunk by the radius — along the direction from their clamped inner point. Grid lines
12
+ * fall on the inner box's edges exactly, so the flat middle of each face is one quad and adjacent
13
+ * faces meet on identical seams.
14
+ */
15
+ export declare function solidRoundedBox(x: number, y: number, z: number, radius: number, segments: number): Solid;
16
+ /**
17
+ * A triangular prism: the section in XY — base `x` wide at −y/2, apex at +y/2 — extruded `z` along
18
+ * Z, centred. `right` puts the apex over the base's left end, a right angle there.
19
+ */
20
+ export declare function solidPrism(x: number, y: number, z: number, right?: boolean): Solid;
21
+ /**
22
+ * A polygon in XZ (`x, z` pairs, either winding, convex or not) extruded along +Y from 0 to
23
+ * `height`, or centred on the origin when `centre` is set.
24
+ */
25
+ export declare function solidExtrude(profile: ArrayLike<number>, height: number, centre?: boolean): Solid;
26
+ /**
27
+ * A profile (`x, y` pairs in the plane across the path) swept along a polyline path (`x, y, z`
28
+ * triples). At each point the frame is right = up × tangent, up' = tangent × right, so profile x
29
+ * runs right and profile y runs up; `up` defaults to +Y. Sides are faceted; `capEnds` closes both
30
+ * ends. Texture u runs around the profile and v along the path, both 0..1.
31
+ *
32
+ * Corners are not mitred: a sharp turn pinches the section. A path that turns sharply wants more
33
+ * points.
34
+ */
35
+ export declare function solidSweep(profile: ArrayLike<number>, path: ArrayLike<number>, capEnds: boolean, up?: Vec3): Solid;
@@ -0,0 +1,428 @@
1
+ /**
2
+ * The straight-sided solids: box, rounded box, quad, triangular prism, a polygon extruded, and a
3
+ * profile swept along a path.
4
+ *
5
+ * Conventions, shared with `solidRound.ts`: centred on the origin unless stated, full extents rather
6
+ * than half, parametric texture coordinates (0..1 across each face), counter-clockwise outward
7
+ * faces. A polygon handed in may be wound either way — it is oriented here — and may be concave, so
8
+ * caps are ear-clipped rather than fanned.
9
+ */
10
+ import { mergeSolids, transformSolid } from './solid.js';
11
+ import { SolidWriter } from './solidWriter.js';
12
+ /**
13
+ * The six faces of an axis-aligned box as (normal, u axis, v axis), with u × v = normal so the
14
+ * corners (−,−) (+,−) (+,+) (−,+) run counter-clockwise seen from outside. The u and v axes also
15
+ * orient each face's texture: v is up on the four sides.
16
+ */
17
+ const FACES = [
18
+ [
19
+ [1, 0, 0],
20
+ [0, 0, -1],
21
+ [0, 1, 0],
22
+ ],
23
+ [
24
+ [-1, 0, 0],
25
+ [0, 0, 1],
26
+ [0, 1, 0],
27
+ ],
28
+ [
29
+ [0, 1, 0],
30
+ [1, 0, 0],
31
+ [0, 0, -1],
32
+ ],
33
+ [
34
+ [0, -1, 0],
35
+ [1, 0, 0],
36
+ [0, 0, 1],
37
+ ],
38
+ [
39
+ [0, 0, 1],
40
+ [1, 0, 0],
41
+ [0, 1, 0],
42
+ ],
43
+ [
44
+ [0, 0, -1],
45
+ [-1, 0, 0],
46
+ [0, 1, 0],
47
+ ],
48
+ ];
49
+ const dot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
50
+ const abs3 = (a) => [Math.abs(a[0]), Math.abs(a[1]), Math.abs(a[2])];
51
+ /** A box of full extents `x, y, z`, faceted: four vertices a face, texture 0..1 across each. */
52
+ export function solidBox(x, y, z) {
53
+ const half = [x / 2, y / 2, z / 2];
54
+ const out = new SolidWriter();
55
+ for (const [n, u, v] of FACES) {
56
+ const hn = dot(abs3(n), half);
57
+ const hu = dot(abs3(u), half);
58
+ const hv = dot(abs3(v), half);
59
+ const base = out.vertexCount;
60
+ for (const [su, sv] of CORNERS) {
61
+ out.vertex(n[0] * hn + u[0] * hu * su + v[0] * hv * sv, n[1] * hn + u[1] * hu * su + v[1] * hv * sv, n[2] * hn + u[2] * hu * su + v[2] * hv * sv, n[0], n[1], n[2], (su + 1) / 2, (sv + 1) / 2);
62
+ }
63
+ out.quad(base, base + 1, base + 2, base + 3);
64
+ }
65
+ return out.finish();
66
+ }
67
+ const CORNERS = [
68
+ [-1, -1],
69
+ [1, -1],
70
+ [1, 1],
71
+ [-1, 1],
72
+ ];
73
+ /** A single-sided plane of `x` by `y` in XY, facing +Z. The one open solid. */
74
+ export function solidQuad(x, y) {
75
+ const out = new SolidWriter();
76
+ for (const [su, sv] of CORNERS) {
77
+ out.vertex((su * x) / 2, (sv * y) / 2, 0, 0, 0, 1, (su + 1) / 2, (sv + 1) / 2);
78
+ }
79
+ out.quad(0, 1, 2, 3);
80
+ return out.finish();
81
+ }
82
+ /**
83
+ * A box of full extents with every edge rounded to `radius`, `segments` steps per rounded band.
84
+ *
85
+ * Built as a Minkowski sum: each face is a grid whose points are pushed out from the inner box —
86
+ * the box shrunk by the radius — along the direction from their clamped inner point. Grid lines
87
+ * fall on the inner box's edges exactly, so the flat middle of each face is one quad and adjacent
88
+ * faces meet on identical seams.
89
+ */
90
+ export function solidRoundedBox(x, y, z, radius, segments) {
91
+ const half = [x / 2, y / 2, z / 2];
92
+ const r = Math.max(0, Math.min(radius, half[0], half[1], half[2]));
93
+ if (r === 0)
94
+ return solidBox(x, y, z);
95
+ const inner = [half[0] - r, half[1] - r, half[2] - r];
96
+ const steps = Math.max(1, Math.floor(segments));
97
+ const out = new SolidWriter();
98
+ for (const [n, u, v] of FACES) {
99
+ const hn = dot(abs3(n), half);
100
+ const us = bandCoordinates(dot(abs3(u), half), dot(abs3(u), inner), steps);
101
+ const vs = bandCoordinates(dot(abs3(v), half), dot(abs3(v), inner), steps);
102
+ const base = out.vertexCount;
103
+ for (const b of vs) {
104
+ for (const a of us) {
105
+ const p = [
106
+ n[0] * hn + u[0] * a + v[0] * b,
107
+ n[1] * hn + u[1] * a + v[1] * b,
108
+ n[2] * hn + u[2] * a + v[2] * b,
109
+ ];
110
+ const q = [clamp(p[0], inner[0]), clamp(p[1], inner[1]), clamp(p[2], inner[2])];
111
+ let dx = p[0] - q[0];
112
+ let dy = p[1] - q[1];
113
+ let dz = p[2] - q[2];
114
+ const len = Math.hypot(dx, dy, dz);
115
+ if (len < 1e-12) {
116
+ [dx, dy, dz] = n;
117
+ }
118
+ else {
119
+ dx /= len;
120
+ dy /= len;
121
+ dz /= len;
122
+ }
123
+ const uHalf = us[us.length - 1] ?? 1;
124
+ const vHalf = vs[vs.length - 1] ?? 1;
125
+ out.vertex(q[0] + dx * r, q[1] + dy * r, q[2] + dz * r, dx, dy, dz, (a / uHalf + 1) / 2, (b / vHalf + 1) / 2);
126
+ }
127
+ }
128
+ const row = us.length;
129
+ for (let j = 0; j < vs.length - 1; j++) {
130
+ for (let i = 0; i < row - 1; i++) {
131
+ const a = base + j * row + i;
132
+ out.quad(a, a + 1, a + 1 + row, a + row);
133
+ }
134
+ }
135
+ }
136
+ return out.finish();
137
+ }
138
+ const clamp = (value, limit) => Math.max(-limit, Math.min(limit, value));
139
+ /** Coordinates across one face axis: `steps` into each rounded band, one step across the flat. */
140
+ function bandCoordinates(outer, inner, steps) {
141
+ const coords = [];
142
+ for (let s = 0; s <= steps; s++)
143
+ coords.push(-outer + ((outer - inner) * s) / steps);
144
+ if (inner > 0)
145
+ for (let s = 0; s <= steps; s++)
146
+ coords.push(inner + ((outer - inner) * s) / steps);
147
+ else
148
+ for (let s = 1; s <= steps; s++)
149
+ coords.push(inner + ((outer - inner) * s) / steps);
150
+ return coords;
151
+ }
152
+ /**
153
+ * A triangular prism: the section in XY — base `x` wide at −y/2, apex at +y/2 — extruded `z` along
154
+ * Z, centred. `right` puts the apex over the base's left end, a right angle there.
155
+ */
156
+ export function solidPrism(x, y, z, right = false) {
157
+ const apex = right ? -x / 2 : 0;
158
+ return extrudeCanonical([-x / 2, -y / 2, x / 2, -y / 2, apex, y / 2], -z / 2, z / 2);
159
+ }
160
+ /**
161
+ * A polygon in XZ (`x, z` pairs, either winding, convex or not) extruded along +Y from 0 to
162
+ * `height`, or centred on the origin when `centre` is set.
163
+ */
164
+ export function solidExtrude(profile, height, centre = false) {
165
+ const w0 = centre ? -height / 2 : 0;
166
+ /* Built with the polygon in (s, t) and depth along w, then mapped s→x, t→z, w→y: a swap of two
167
+ axes, whose negative determinant `transformSolid` answers by reversing the winding. */
168
+ const canonical = extrudeCanonical(Array.from(profile), w0, w0 + height);
169
+ return transformSolid(canonical, [1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1]);
170
+ }
171
+ /**
172
+ * A profile (`x, y` pairs in the plane across the path) swept along a polyline path (`x, y, z`
173
+ * triples). At each point the frame is right = up × tangent, up' = tangent × right, so profile x
174
+ * runs right and profile y runs up; `up` defaults to +Y. Sides are faceted; `capEnds` closes both
175
+ * ends. Texture u runs around the profile and v along the path, both 0..1.
176
+ *
177
+ * Corners are not mitred: a sharp turn pinches the section. A path that turns sharply wants more
178
+ * points.
179
+ */
180
+ export function solidSweep(profile, path, capEnds, up = [0, 1, 0]) {
181
+ const poly = counterClockwise(Array.from(profile));
182
+ const k = poly.length / 2;
183
+ const n = Math.floor(path.length / 3);
184
+ const rings = [];
185
+ const along = [0];
186
+ for (let i = 0; i < n; i++) {
187
+ const prev = Math.max(0, i - 1);
188
+ const next = Math.min(n - 1, i + 1);
189
+ let tx = (path[next * 3] ?? 0) - (path[prev * 3] ?? 0);
190
+ let ty = (path[next * 3 + 1] ?? 0) - (path[prev * 3 + 1] ?? 0);
191
+ let tz = (path[next * 3 + 2] ?? 0) - (path[prev * 3 + 2] ?? 0);
192
+ const tl = Math.hypot(tx, ty, tz) || 1;
193
+ tx /= tl;
194
+ ty /= tl;
195
+ tz /= tl;
196
+ let rx = up[1] * tz - up[2] * ty;
197
+ let ry = up[2] * tx - up[0] * tz;
198
+ let rz = up[0] * ty - up[1] * tx;
199
+ const rl = Math.hypot(rx, ry, rz) || 1;
200
+ rx /= rl;
201
+ ry /= rl;
202
+ rz /= rl;
203
+ const ux = ty * rz - tz * ry;
204
+ const uy = tz * rx - tx * rz;
205
+ const uz = tx * ry - ty * rx;
206
+ const ring = [];
207
+ for (let p = 0; p < k; p++) {
208
+ const px = poly[p * 2] ?? 0;
209
+ const py = poly[p * 2 + 1] ?? 0;
210
+ ring.push((path[i * 3] ?? 0) + rx * px + ux * py, (path[i * 3 + 1] ?? 0) + ry * px + uy * py, (path[i * 3 + 2] ?? 0) + rz * px + uz * py);
211
+ }
212
+ rings.push(ring);
213
+ if (i > 0) {
214
+ along.push((along[i - 1] ?? 0) +
215
+ Math.hypot((path[i * 3] ?? 0) - (path[(i - 1) * 3] ?? 0), (path[i * 3 + 1] ?? 0) - (path[(i - 1) * 3 + 1] ?? 0), (path[i * 3 + 2] ?? 0) - (path[(i - 1) * 3 + 2] ?? 0)));
216
+ }
217
+ }
218
+ const total = along[n - 1] || 1;
219
+ const perimeter = perimeterFractions(poly);
220
+ const out = new SolidWriter();
221
+ for (let i = 0; i < n - 1; i++) {
222
+ const a = rings[i] ?? [];
223
+ const b = rings[i + 1] ?? [];
224
+ for (let p = 0; p < k; p++) {
225
+ const q = (p + 1) % k;
226
+ writeQuad(out, [a[p * 3] ?? 0, a[p * 3 + 1] ?? 0, a[p * 3 + 2] ?? 0], [a[q * 3] ?? 0, a[q * 3 + 1] ?? 0, a[q * 3 + 2] ?? 0], [b[q * 3] ?? 0, b[q * 3 + 1] ?? 0, b[q * 3 + 2] ?? 0], [b[p * 3] ?? 0, b[p * 3 + 1] ?? 0, b[p * 3 + 2] ?? 0], perimeter[p] ?? 0, perimeter[p + 1] ?? 1, (along[i] ?? 0) / total, (along[i + 1] ?? 0) / total);
227
+ }
228
+ }
229
+ if (!capEnds || n < 2)
230
+ return out.finish();
231
+ const sides = out.finish();
232
+ const caps = [
233
+ capFromRing(rings[0] ?? [], poly, true),
234
+ capFromRing(rings[n - 1] ?? [], poly, false),
235
+ ];
236
+ return mergeSolids([sides, ...caps]);
237
+ }
238
+ /** One faceted quad with its own four vertices and the normal its corners imply. */
239
+ function writeQuad(out, a, b, c, d, u0, u1, v0, v1) {
240
+ const ux = b[0] - a[0];
241
+ const uy = b[1] - a[1];
242
+ const uz = b[2] - a[2];
243
+ const vx = d[0] - a[0];
244
+ const vy = d[1] - a[1];
245
+ const vz = d[2] - a[2];
246
+ let nx = uy * vz - uz * vy;
247
+ let ny = uz * vx - ux * vz;
248
+ let nz = ux * vy - uy * vx;
249
+ const len = Math.hypot(nx, ny, nz) || 1;
250
+ nx /= len;
251
+ ny /= len;
252
+ nz /= len;
253
+ const base = out.vertexCount;
254
+ out.vertex(a[0], a[1], a[2], nx, ny, nz, u0, v0);
255
+ out.vertex(b[0], b[1], b[2], nx, ny, nz, u1, v0);
256
+ out.vertex(c[0], c[1], c[2], nx, ny, nz, u1, v1);
257
+ out.vertex(d[0], d[1], d[2], nx, ny, nz, u0, v1);
258
+ out.quad(base, base + 1, base + 2, base + 3);
259
+ }
260
+ /** A cap over one ring of a sweep: the profile's triangulation, reversed at the start. */
261
+ function capFromRing(ring, poly, start) {
262
+ const k = poly.length / 2;
263
+ const tris = triangulate(poly);
264
+ const out = new SolidWriter();
265
+ const p0 = [ring[0] ?? 0, ring[1] ?? 0, ring[2] ?? 0];
266
+ let nx = 0;
267
+ let ny = 0;
268
+ let nz = 0;
269
+ for (let t = 0; t < tris.length; t += 3) {
270
+ const [a, b, c] = [tris[t] ?? 0, tris[t + 1] ?? 0, tris[t + 2] ?? 0];
271
+ const ux = (ring[b * 3] ?? 0) - (ring[a * 3] ?? 0);
272
+ const uy = (ring[b * 3 + 1] ?? 0) - (ring[a * 3 + 1] ?? 0);
273
+ const uz = (ring[b * 3 + 2] ?? 0) - (ring[a * 3 + 2] ?? 0);
274
+ const vx = (ring[c * 3] ?? 0) - (ring[a * 3] ?? 0);
275
+ const vy = (ring[c * 3 + 1] ?? 0) - (ring[a * 3 + 1] ?? 0);
276
+ const vz = (ring[c * 3 + 2] ?? 0) - (ring[a * 3 + 2] ?? 0);
277
+ nx += uy * vz - uz * vy;
278
+ ny += uz * vx - ux * vz;
279
+ nz += ux * vy - uy * vx;
280
+ }
281
+ const len = Math.hypot(nx, ny, nz) || 1;
282
+ const s = start ? -1 : 1;
283
+ const [bx, by] = bounds(poly);
284
+ for (let p = 0; p < k; p++) {
285
+ out.vertex(ring[p * 3] ?? p0[0], ring[p * 3 + 1] ?? p0[1], ring[p * 3 + 2] ?? p0[2], (s * nx) / len, (s * ny) / len, (s * nz) / len, ((poly[p * 2] ?? 0) - bx[0]) / (bx[1] - bx[0] || 1), ((poly[p * 2 + 1] ?? 0) - by[0]) / (by[1] - by[0] || 1));
286
+ }
287
+ for (let t = 0; t < tris.length; t += 3) {
288
+ if (start)
289
+ out.triangle(tris[t] ?? 0, tris[t + 2] ?? 0, tris[t + 1] ?? 0);
290
+ else
291
+ out.triangle(tris[t] ?? 0, tris[t + 1] ?? 0, tris[t + 2] ?? 0);
292
+ }
293
+ return out.finish();
294
+ }
295
+ /**
296
+ * A polygon in (s, t) extruded along w from `w0` to `w1`, in a frame where s × t = w: caps
297
+ * ear-clipped, sides faceted, texture u around the perimeter and v along w. The frame is XYZ
298
+ * itself, so a caller wanting another axis maps it with `transformSolid`.
299
+ */
300
+ function extrudeCanonical(profile, w0, w1) {
301
+ const poly = counterClockwise(profile);
302
+ const k = poly.length / 2;
303
+ const tris = triangulate(poly);
304
+ const [bs, bt] = bounds(poly);
305
+ const perimeter = perimeterFractions(poly);
306
+ const out = new SolidWriter();
307
+ for (const [w, sign] of [
308
+ [w1, 1],
309
+ [w0, -1],
310
+ ]) {
311
+ const base = out.vertexCount;
312
+ for (let p = 0; p < k; p++) {
313
+ const s = poly[p * 2] ?? 0;
314
+ const t = poly[p * 2 + 1] ?? 0;
315
+ out.vertex(s, t, w, 0, 0, sign, (s - bs[0]) / (bs[1] - bs[0] || 1), (t - bt[0]) / (bt[1] - bt[0] || 1));
316
+ }
317
+ for (let i = 0; i < tris.length; i += 3) {
318
+ const a = base + (tris[i] ?? 0);
319
+ const b = base + (tris[i + 1] ?? 0);
320
+ const c = base + (tris[i + 2] ?? 0);
321
+ if (sign > 0)
322
+ out.triangle(a, b, c);
323
+ else
324
+ out.triangle(a, c, b);
325
+ }
326
+ }
327
+ for (let p = 0; p < k; p++) {
328
+ const q = (p + 1) % k;
329
+ const a = [poly[p * 2] ?? 0, poly[p * 2 + 1] ?? 0, w0];
330
+ const b = [poly[q * 2] ?? 0, poly[q * 2 + 1] ?? 0, w0];
331
+ writeQuad(out, a, b, [b[0], b[1], w1], [a[0], a[1], w1], perimeter[p] ?? 0, perimeter[p + 1] ?? 1, 0, 1);
332
+ }
333
+ return out.finish();
334
+ }
335
+ function signedArea(poly) {
336
+ let sum = 0;
337
+ const k = poly.length / 2;
338
+ for (let p = 0; p < k; p++) {
339
+ const q = (p + 1) % k;
340
+ sum +=
341
+ (poly[p * 2] ?? 0) * (poly[q * 2 + 1] ?? 0) - (poly[q * 2] ?? 0) * (poly[p * 2 + 1] ?? 0);
342
+ }
343
+ return sum / 2;
344
+ }
345
+ /** The polygon wound counter-clockwise, reversed if it came the other way. */
346
+ function counterClockwise(poly) {
347
+ if (signedArea(poly) >= 0)
348
+ return poly;
349
+ const out = [];
350
+ for (let p = poly.length / 2 - 1; p >= 0; p--)
351
+ out.push(poly[p * 2] ?? 0, poly[p * 2 + 1] ?? 0);
352
+ return out;
353
+ }
354
+ function bounds(poly) {
355
+ let s0 = Infinity;
356
+ let s1 = -Infinity;
357
+ let t0 = Infinity;
358
+ let t1 = -Infinity;
359
+ for (let p = 0; p < poly.length; p += 2) {
360
+ s0 = Math.min(s0, poly[p] ?? 0);
361
+ s1 = Math.max(s1, poly[p] ?? 0);
362
+ t0 = Math.min(t0, poly[p + 1] ?? 0);
363
+ t1 = Math.max(t1, poly[p + 1] ?? 0);
364
+ }
365
+ return [
366
+ [s0, s1],
367
+ [t0, t1],
368
+ ];
369
+ }
370
+ /** How far around the perimeter each corner sits, 0 at the first and 1 back at it. */
371
+ function perimeterFractions(poly) {
372
+ const k = poly.length / 2;
373
+ const at = [0];
374
+ for (let p = 0; p < k; p++) {
375
+ const q = (p + 1) % k;
376
+ const edge = Math.hypot((poly[q * 2] ?? 0) - (poly[p * 2] ?? 0), (poly[q * 2 + 1] ?? 0) - (poly[p * 2 + 1] ?? 0));
377
+ at.push((at[p] ?? 0) + edge);
378
+ }
379
+ const total = at[k] || 1;
380
+ return at.map((d) => d / total);
381
+ }
382
+ /**
383
+ * Ear clipping over a counter-clockwise simple polygon: corner indices, three a triangle.
384
+ * Quadratic, which is right for the dozens of corners a profile has.
385
+ */
386
+ function triangulate(poly) {
387
+ const k = poly.length / 2;
388
+ const remaining = [];
389
+ for (let p = 0; p < k; p++)
390
+ remaining.push(p);
391
+ const tris = [];
392
+ const x = (i) => poly[i * 2] ?? 0;
393
+ const y = (i) => poly[i * 2 + 1] ?? 0;
394
+ const cross = (a, b, c) => (x(b) - x(a)) * (y(c) - y(a)) - (y(b) - y(a)) * (x(c) - x(a));
395
+ let guard = k * k + 8;
396
+ while (remaining.length > 3 && guard-- > 0) {
397
+ let clipped = false;
398
+ for (let r = 0; r < remaining.length; r++) {
399
+ const a = remaining[(r + remaining.length - 1) % remaining.length] ?? 0;
400
+ const b = remaining[r] ?? 0;
401
+ const c = remaining[(r + 1) % remaining.length] ?? 0;
402
+ if (cross(a, b, c) <= 1e-12)
403
+ continue;
404
+ let inside = false;
405
+ for (const o of remaining) {
406
+ if (o === a || o === b || o === c)
407
+ continue;
408
+ if (cross(a, b, o) >= 0 && cross(b, c, o) >= 0 && cross(c, a, o) >= 0) {
409
+ inside = true;
410
+ break;
411
+ }
412
+ }
413
+ if (inside)
414
+ continue;
415
+ tris.push(a, b, c);
416
+ remaining.splice(r, 1);
417
+ clipped = true;
418
+ break;
419
+ }
420
+ if (!clipped) {
421
+ /* Only collinear corners remain unclipped: drop the middle one, which spans no area. */
422
+ remaining.splice(1, 1);
423
+ }
424
+ }
425
+ if (remaining.length === 3)
426
+ tris.push(remaining[0] ?? 0, remaining[1] ?? 0, remaining[2] ?? 0);
427
+ return tris;
428
+ }