@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,443 @@
1
+ /**
2
+ * The round solids: cylinder, cone, frustum, tube, sphere, hemisphere, icosphere, capsule, torus
3
+ * and lathe — all but the icosphere one revolve of a profile around +Y.
4
+ *
5
+ * A profile is `(r, y)` points. Revolving it, the vertex at angle θ sits at (r·cos θ, y, r·sin θ),
6
+ * and a quad is wound so that the profile's direction crossed with the direction of increasing θ
7
+ * points outward: a profile climbing on its outer side, or running outward along a bottom cap,
8
+ * faces out. Where the profile touches the axis a quad collapses to a triangle and only the
9
+ * triangle is written, so no edge is counted twice.
10
+ *
11
+ * `smooth` shares vertices around the axis with normals that vary around it; the default,
12
+ * faceted, gives every quad its own four vertices and its own normal. Texture u runs around (0..1,
13
+ * the seam duplicated) and v along the profile (0..1 by length).
14
+ */
15
+ import type { Solid } from './solid.ts';
16
+ import { SolidWriter } from './solidWriter.ts';
17
+
18
+ /** A profile point with the in-plane normal it wants when smooth: (nr, ny) across the profile. */
19
+ interface ProfilePoint {
20
+ readonly r: number;
21
+ readonly y: number;
22
+ readonly nr: number;
23
+ readonly ny: number;
24
+ }
25
+
26
+ /**
27
+ * Revolve one or more strips — runs of profile points whose normals are continuous — around +Y.
28
+ * A strip's normals are used when smooth; faceted quads take the normal their corners imply.
29
+ */
30
+ function revolve(
31
+ strips: readonly (readonly ProfilePoint[])[],
32
+ segments: number,
33
+ smooth: boolean,
34
+ ): Solid {
35
+ const n = Math.max(3, Math.floor(segments));
36
+ const out = new SolidWriter();
37
+ let total = 0;
38
+ for (const strip of strips) {
39
+ for (let i = 0; i + 1 < strip.length; i++) total += span(strip[i], strip[i + 1]);
40
+ }
41
+ let walked = 0;
42
+ for (const strip of strips) {
43
+ const v: number[] = [];
44
+ for (let i = 0; i < strip.length; i++) {
45
+ if (i > 0) walked += span(strip[i - 1], strip[i]);
46
+ v.push(total > 0 ? walked / total : 0);
47
+ }
48
+ if (smooth) revolveSmooth(out, strip, v, n);
49
+ else revolveFaceted(out, strip, v, n);
50
+ }
51
+ return out.finish();
52
+ }
53
+
54
+ function span(a: ProfilePoint | undefined, b: ProfilePoint | undefined): number {
55
+ return a && b ? Math.hypot(b.r - a.r, b.y - a.y) : 0;
56
+ }
57
+
58
+ function revolveSmooth(
59
+ out: SolidWriter,
60
+ strip: readonly ProfilePoint[],
61
+ v: number[],
62
+ n: number,
63
+ ): void {
64
+ const base = out.vertexCount;
65
+ const row = n + 1;
66
+ for (let i = 0; i < strip.length; i++) {
67
+ const p = strip[i];
68
+ if (!p) continue;
69
+ for (let j = 0; j <= n; j++) {
70
+ const t = (2 * Math.PI * (j % n)) / n;
71
+ const c = Math.cos(t);
72
+ const s = Math.sin(t);
73
+ out.vertex(p.r * c, p.y, p.r * s, p.nr * c, p.ny, p.nr * s, j / n, v[i] ?? 0);
74
+ }
75
+ }
76
+ for (let i = 0; i + 1 < strip.length; i++) {
77
+ const r0 = strip[i]?.r ?? 0;
78
+ const r1 = strip[i + 1]?.r ?? 0;
79
+ for (let j = 0; j < n; j++) {
80
+ const a = base + i * row + j;
81
+ const b = a + row;
82
+ const c = b + 1;
83
+ const d = a + 1;
84
+ emit(out, a, b, c, d, r0, r1);
85
+ }
86
+ }
87
+ }
88
+
89
+ function revolveFaceted(
90
+ out: SolidWriter,
91
+ strip: readonly ProfilePoint[],
92
+ v: number[],
93
+ n: number,
94
+ ): void {
95
+ for (let i = 0; i + 1 < strip.length; i++) {
96
+ const p = strip[i];
97
+ const q = strip[i + 1];
98
+ if (!p || !q) continue;
99
+ for (let j = 0; j < n; j++) {
100
+ const t0 = (2 * Math.PI * j) / n;
101
+ const t1 = (2 * Math.PI * ((j + 1) % n)) / n;
102
+ const c0 = Math.cos(t0);
103
+ const s0 = Math.sin(t0);
104
+ const c1 = Math.cos(t1);
105
+ const s1 = Math.sin(t1);
106
+ const ax = p.r * c0;
107
+ const az = p.r * s0;
108
+ const bx = q.r * c0;
109
+ const bz = q.r * s0;
110
+ const cx = q.r * c1;
111
+ const cz = q.r * s1;
112
+ const dx = p.r * c1;
113
+ const dz = p.r * s1;
114
+ /* The normal from the diagonals, (c − a) × (d − b): right for a quad and for the triangle a
115
+ quad collapses to at the axis, where the edge-based cross product is zero. */
116
+ const ex = cx - ax;
117
+ const ey = q.y - p.y;
118
+ const ez = cz - az;
119
+ const fx = dx - bx;
120
+ const fy = p.y - q.y;
121
+ const fz = dz - bz;
122
+ let nx = ey * fz - ez * fy;
123
+ let ny = ez * fx - ex * fz;
124
+ let nz = ex * fy - ey * fx;
125
+ const len = Math.hypot(nx, ny, nz) || 1;
126
+ nx /= len;
127
+ ny /= len;
128
+ nz /= len;
129
+ const u0 = j / n;
130
+ const u1 = (j + 1) / n;
131
+ const a = out.vertex(ax, p.y, az, nx, ny, nz, u0, v[i] ?? 0);
132
+ const b = out.vertex(bx, q.y, bz, nx, ny, nz, u0, v[i + 1] ?? 0);
133
+ const c = out.vertex(cx, q.y, cz, nx, ny, nz, u1, v[i + 1] ?? 0);
134
+ const d = out.vertex(dx, p.y, dz, nx, ny, nz, u1, v[i] ?? 0);
135
+ emit(out, a, b, c, d, p.r, q.r);
136
+ }
137
+ }
138
+ }
139
+
140
+ /** Quad `a b c d`, or the one triangle left of it where either end of the profile step is on the axis. */
141
+ function emit(
142
+ out: SolidWriter,
143
+ a: number,
144
+ b: number,
145
+ c: number,
146
+ d: number,
147
+ r0: number,
148
+ r1: number,
149
+ ): void {
150
+ if (r0 <= 1e-12 && r1 <= 1e-12) return;
151
+ if (r0 <= 1e-12) out.triangle(a, b, c);
152
+ else if (r1 <= 1e-12) out.triangle(a, b, d);
153
+ else out.quad(a, b, c, d);
154
+ }
155
+
156
+ /** Points along a polyline profile, each segment its own strip so every corner creases. */
157
+ function creasedStrips(points: readonly (readonly [number, number])[]): ProfilePoint[][] {
158
+ const strips: ProfilePoint[][] = [];
159
+ for (let i = 0; i + 1 < points.length; i++) {
160
+ const [r0, y0] = points[i] ?? [0, 0];
161
+ const [r1, y1] = points[i + 1] ?? [0, 0];
162
+ const len = Math.hypot(r1 - r0, y1 - y0) || 1;
163
+ const nr = (y1 - y0) / len;
164
+ const ny = -(r1 - r0) / len;
165
+ strips.push([
166
+ { r: r0, y: y0, nr, ny },
167
+ { r: r1, y: y1, nr, ny },
168
+ ]);
169
+ }
170
+ return strips;
171
+ }
172
+
173
+ /** An arc of the circle centred (cr, cy), radius `radius`, from angle `a0` to `a1` in `steps`. */
174
+ function arc(
175
+ cr: number,
176
+ cy: number,
177
+ radius: number,
178
+ a0: number,
179
+ a1: number,
180
+ steps: number,
181
+ ): ProfilePoint[] {
182
+ const points: ProfilePoint[] = [];
183
+ for (let s = 0; s <= steps; s++) {
184
+ const a = a0 + ((a1 - a0) * s) / steps;
185
+ const c = Math.cos(a);
186
+ const sn = Math.sin(a);
187
+ points.push({ r: Math.max(0, cr + radius * c), y: cy + radius * sn, nr: c, ny: sn });
188
+ }
189
+ return points;
190
+ }
191
+
192
+ /** A cylinder of `radius` and `length` along Y, centred, capped. */
193
+ export function solidCylinder(
194
+ radius: number,
195
+ length: number,
196
+ segments: number,
197
+ smooth = false,
198
+ ): Solid {
199
+ return solidFrustum(radius, radius, length, segments, smooth);
200
+ }
201
+
202
+ /** A cone: base `radius` at −length/2, apex at +length/2. */
203
+ export function solidCone(radius: number, length: number, segments: number, smooth = false): Solid {
204
+ return solidFrustum(radius, 0, length, segments, smooth);
205
+ }
206
+
207
+ /** A frustum with explicit radii at its bottom (−length/2) and top (+length/2), capped. */
208
+ export function solidFrustum(
209
+ radiusBottom: number,
210
+ radiusTop: number,
211
+ length: number,
212
+ segments: number,
213
+ smooth = false,
214
+ ): Solid {
215
+ const h = length / 2;
216
+ return revolve(
217
+ creasedStrips([
218
+ [0, -h],
219
+ [radiusBottom, -h],
220
+ [radiusTop, h],
221
+ [0, h],
222
+ ]),
223
+ segments,
224
+ smooth,
225
+ );
226
+ }
227
+
228
+ /** A hollow cylinder: outer `radius`, wall `thickness`, `length` along Y, centred. Four segments make a square frame. */
229
+ export function solidTube(
230
+ radius: number,
231
+ thickness: number,
232
+ length: number,
233
+ segments: number,
234
+ smooth = false,
235
+ ): Solid {
236
+ const h = length / 2;
237
+ const inner = Math.max(0, radius - thickness);
238
+ return revolve(
239
+ creasedStrips([
240
+ [inner, -h],
241
+ [radius, -h],
242
+ [radius, h],
243
+ [inner, h],
244
+ [inner, -h],
245
+ ]),
246
+ segments,
247
+ smooth,
248
+ );
249
+ }
250
+
251
+ /** A sphere of `radius`: `segments` around, half as many from pole to pole. */
252
+ export function solidSphere(radius: number, segments: number, smooth = false): Solid {
253
+ const rings = Math.max(2, Math.floor(segments / 2));
254
+ return revolve([arc(0, 0, radius, -Math.PI / 2, Math.PI / 2, rings)], segments, smooth);
255
+ }
256
+
257
+ /** A dome toward +Y on a flat base at the origin. */
258
+ export function solidHemisphere(radius: number, segments: number, smooth = false): Solid {
259
+ const rings = Math.max(1, Math.floor(segments / 4));
260
+ const base: ProfilePoint[] = [
261
+ { r: 0, y: 0, nr: 0, ny: -1 },
262
+ { r: radius, y: 0, nr: 0, ny: -1 },
263
+ ];
264
+ return revolve([base, arc(0, 0, radius, 0, Math.PI / 2, rings)], segments, smooth);
265
+ }
266
+
267
+ /** A capsule: a cylinder of `length` (excluding the caps) with hemispherical ends, along Y. */
268
+ export function solidCapsule(
269
+ radius: number,
270
+ length: number,
271
+ segments: number,
272
+ smooth = false,
273
+ ): Solid {
274
+ const rings = Math.max(1, Math.floor(segments / 4));
275
+ const h = length / 2;
276
+ return revolve(
277
+ [
278
+ arc(0, -h, radius, -Math.PI / 2, 0, rings),
279
+ [
280
+ { r: radius, y: -h, nr: 1, ny: 0 },
281
+ { r: radius, y: h, nr: 1, ny: 0 },
282
+ ],
283
+ arc(0, h, radius, 0, Math.PI / 2, rings),
284
+ ],
285
+ segments,
286
+ smooth,
287
+ );
288
+ }
289
+
290
+ /** A torus around +Y: ring radius `major`, tube radius `minor`, `segments` around and `sides` across. */
291
+ export function solidTorus(
292
+ major: number,
293
+ minor: number,
294
+ segments: number,
295
+ sides: number,
296
+ smooth = false,
297
+ ): Solid {
298
+ return revolve(
299
+ [arc(major, 0, minor, 0, 2 * Math.PI, Math.max(3, Math.floor(sides)))],
300
+ segments,
301
+ smooth,
302
+ );
303
+ }
304
+
305
+ /**
306
+ * A profile (`r, y` pairs, bottom to top) revolved around +Y. An end off the axis is closed with a
307
+ * flat cap, so any profile makes a solid. Every profile corner creases, faceted or smooth.
308
+ */
309
+ export function solidLathe(profile: ArrayLike<number>, segments: number, smooth = false): Solid {
310
+ const points: [number, number][] = [];
311
+ for (let i = 0; i + 1 < profile.length; i += 2)
312
+ points.push([profile[i] ?? 0, profile[i + 1] ?? 0]);
313
+ const first = points[0];
314
+ const last = points[points.length - 1];
315
+ if (first && first[0] > 1e-12) points.unshift([0, first[1]]);
316
+ if (last && last[0] > 1e-12) points.push([0, last[1]]);
317
+ return revolve(creasedStrips(points), segments, smooth);
318
+ }
319
+
320
+ /**
321
+ * An icosahedron subdivided `level` times onto a sphere of `radius`. Faceted by default — every
322
+ * triangle its own vertices and face normal — or smooth, sharing vertices with radial normals.
323
+ */
324
+ export function solidIcosphere(radius: number, level: number, smooth = false): Solid {
325
+ const t = (1 + Math.sqrt(5)) / 2;
326
+ const verts: number[][] = [
327
+ [-1, t, 0],
328
+ [1, t, 0],
329
+ [-1, -t, 0],
330
+ [1, -t, 0],
331
+ [0, -1, t],
332
+ [0, 1, t],
333
+ [0, -1, -t],
334
+ [0, 1, -t],
335
+ [t, 0, -1],
336
+ [t, 0, 1],
337
+ [-t, 0, -1],
338
+ [-t, 0, 1],
339
+ ].map(unit);
340
+ let faces: number[][] = [
341
+ [0, 11, 5],
342
+ [0, 5, 1],
343
+ [0, 1, 7],
344
+ [0, 7, 10],
345
+ [0, 10, 11],
346
+ [1, 5, 9],
347
+ [5, 11, 4],
348
+ [11, 10, 2],
349
+ [10, 7, 6],
350
+ [7, 1, 8],
351
+ [3, 9, 4],
352
+ [3, 4, 2],
353
+ [3, 2, 6],
354
+ [3, 6, 8],
355
+ [3, 8, 9],
356
+ [4, 9, 5],
357
+ [2, 4, 11],
358
+ [6, 2, 10],
359
+ [8, 6, 7],
360
+ [9, 8, 1],
361
+ ];
362
+ for (let l = 0; l < Math.floor(level); l++) {
363
+ const cache = new Map<string, number>();
364
+ const mid = (a: number, b: number): number => {
365
+ const key = a < b ? `${a},${b}` : `${b},${a}`;
366
+ const hit = cache.get(key);
367
+ if (hit !== undefined) return hit;
368
+ const va = verts[a] ?? [0, 0, 0];
369
+ const vb = verts[b] ?? [0, 0, 0];
370
+ verts.push(
371
+ unit([
372
+ (va[0] ?? 0) + (vb[0] ?? 0),
373
+ (va[1] ?? 0) + (vb[1] ?? 0),
374
+ (va[2] ?? 0) + (vb[2] ?? 0),
375
+ ]),
376
+ );
377
+ cache.set(key, verts.length - 1);
378
+ return verts.length - 1;
379
+ };
380
+ const next: number[][] = [];
381
+ for (const [a = 0, b = 0, c = 0] of faces) {
382
+ const ab = mid(a, b);
383
+ const bc = mid(b, c);
384
+ const ca = mid(c, a);
385
+ next.push([a, ab, ca], [b, bc, ab], [c, ca, bc], [ab, bc, ca]);
386
+ }
387
+ faces = next;
388
+ }
389
+ const out = new SolidWriter();
390
+ const uv = (p: number[]): [number, number] => [
391
+ 0.5 + Math.atan2(p[2] ?? 0, p[0] ?? 0) / (2 * Math.PI),
392
+ 0.5 + Math.asin(Math.max(-1, Math.min(1, p[1] ?? 0))) / Math.PI,
393
+ ];
394
+ if (smooth) {
395
+ for (const p of verts) {
396
+ const [u, v] = uv(p);
397
+ out.vertex(
398
+ (p[0] ?? 0) * radius,
399
+ (p[1] ?? 0) * radius,
400
+ (p[2] ?? 0) * radius,
401
+ p[0] ?? 0,
402
+ p[1] ?? 0,
403
+ p[2] ?? 0,
404
+ u,
405
+ v,
406
+ );
407
+ }
408
+ /* The table's faces run counter-clockwise seen from outside, as every face here does. */
409
+ for (const [a = 0, b = 0, c = 0] of faces) out.triangle(a, b, c);
410
+ return out.finish();
411
+ }
412
+ for (const [a = 0, b = 0, c = 0] of faces) {
413
+ const pa = verts[a] ?? [0, 0, 0];
414
+ const pb = verts[b] ?? [0, 0, 0];
415
+ const pc = verts[c] ?? [0, 0, 0];
416
+ const n = unit([
417
+ ((pa[0] ?? 0) + (pb[0] ?? 0) + (pc[0] ?? 0)) / 3,
418
+ ((pa[1] ?? 0) + (pb[1] ?? 0) + (pc[1] ?? 0)) / 3,
419
+ ((pa[2] ?? 0) + (pb[2] ?? 0) + (pc[2] ?? 0)) / 3,
420
+ ]);
421
+ const base = out.vertexCount;
422
+ for (const p of [pa, pb, pc]) {
423
+ const [u, v] = uv(p);
424
+ out.vertex(
425
+ (p[0] ?? 0) * radius,
426
+ (p[1] ?? 0) * radius,
427
+ (p[2] ?? 0) * radius,
428
+ n[0] ?? 0,
429
+ n[1] ?? 0,
430
+ n[2] ?? 0,
431
+ u,
432
+ v,
433
+ );
434
+ }
435
+ out.triangle(base, base + 1, base + 2);
436
+ }
437
+ return out.finish();
438
+ }
439
+
440
+ function unit(p: number[]): number[] {
441
+ const len = Math.hypot(p[0] ?? 0, p[1] ?? 0, p[2] ?? 0) || 1;
442
+ return [(p[0] ?? 0) / len, (p[1] ?? 0) / len, (p[2] ?? 0) / len];
443
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * The accumulator every solid primitive writes through: a vertex at a time, a triangle or a quad at
3
+ * a time, finished into a `Solid`.
4
+ *
5
+ * Internal to the geometry modules and not in the barrel — a caller building geometry of its own
6
+ * wants `MeshBuilder` or typed arrays, and this exists so the primitives share one place that
7
+ * decides how a vertex is laid out.
8
+ */
9
+ import { GrowableF32, GrowableU32 } from './growable.ts';
10
+ import type { Solid } from './solid.ts';
11
+
12
+ export class SolidWriter {
13
+ private readonly positions = new GrowableF32();
14
+ private readonly normals = new GrowableF32();
15
+ private readonly uvs = new GrowableF32();
16
+ private readonly indices = new GrowableU32();
17
+ private count = 0;
18
+
19
+ /** Add one vertex and return its index. The normal is taken as given; pass it unit length. */
20
+ vertex(
21
+ px: number,
22
+ py: number,
23
+ pz: number,
24
+ nx: number,
25
+ ny: number,
26
+ nz: number,
27
+ u: number,
28
+ v: number,
29
+ ): number {
30
+ this.positions.push(px, py, pz);
31
+ this.normals.push(nx, ny, nz);
32
+ this.uvs.push(u, v);
33
+ return this.count++;
34
+ }
35
+
36
+ triangle(a: number, b: number, c: number): void {
37
+ this.indices.push(a, b, c);
38
+ }
39
+
40
+ /** Two triangles over a counter-clockwise quad `a b c d`. */
41
+ quad(a: number, b: number, c: number, d: number): void {
42
+ this.indices.push(a, b, c, a, c, d);
43
+ }
44
+
45
+ /** How many vertices so far, which is the index the next `vertex` returns. */
46
+ get vertexCount(): number {
47
+ return this.count;
48
+ }
49
+
50
+ finish(): Solid {
51
+ return {
52
+ positions: this.positions.toTyped(),
53
+ normals: this.normals.toTyped(),
54
+ uvs: this.uvs.toTyped(),
55
+ indices: this.indices.toTyped(),
56
+ };
57
+ }
58
+ }
package/src/index.ts CHANGED
@@ -391,6 +391,17 @@ export type {
391
391
  } from './render/gi/globalField.ts';
392
392
  export { FIELD_FADE_SEC, LightField } from './render/driftLight/lightField.ts';
393
393
  export type { LightFieldOptions, LightFieldSource } from './render/driftLight/lightField.ts';
394
+ export {
395
+ DENSE_MAX_AXIS,
396
+ DENSE_MAX_HEIGHT,
397
+ bakeDenseField,
398
+ } from './render/driftLight/denseField.ts';
399
+ export type { DenseLightVolume } from './render/driftLight/denseField.ts';
400
+ export { WorldLightField } from './render/driftLight/worldLightField.ts';
401
+ export type { WorldLightFieldOptions } from './render/driftLight/worldLightField.ts';
402
+ export { SURFACE_EFFECT_TEXELS, packSurfaceEffects } from './render/surfaceEffects.ts';
403
+ export type { SurfaceLayerEffect } from './render/surfaceEffects.ts';
404
+ export type { FieldLight } from './render/driftLight/bake.ts';
394
405
  export {
395
406
  GI_SCREEN_MARCH,
396
407
  SCREEN_TRACE_BIAS_M,
@@ -603,8 +614,11 @@ export type { GpuIdentity, DescribeGpuOptions } from './render/gpuCapability.ts'
603
614
  export {
604
615
  DEFAULT_POINT_LIGHT_VIEW_RANGE,
605
616
  createPointLightBuffer,
617
+ selectGridLights,
606
618
  selectPointLights,
607
619
  } from './render/pointLightSelection.ts';
620
+ export { LIGHT_GRID_MAX_CELLS, createLightGrid, gatherLights } from './render/lightGrid.ts';
621
+ export type { LightGrid } from './render/lightGrid.ts';
608
622
  export type { PointLightBuffer, PointLightSource } from './render/pointLightSelection.ts';
609
623
  export { createFlame, flameFrequencyHz, updateFlame } from './render/flameLight.ts';
610
624
  export type { Flame, FlameOptions } from './render/flameLight.ts';
@@ -658,6 +672,38 @@ export { WaterRenderer } from './render/waterRenderer.ts';
658
672
  export type { WaterBody, WaterBounds, WaterSettings } from './render/waterRenderer.ts';
659
673
  export { MeshBuilder } from './geometry/meshBuilder.ts';
660
674
  export { concatMeshes, placeMesh } from './geometry/meshConcat.ts';
675
+ export {
676
+ emptySolid,
677
+ mergeSolids,
678
+ solidToMesh,
679
+ solidVolume,
680
+ transformSolid,
681
+ } from './geometry/solid.ts';
682
+ export type { Solid } from './geometry/solid.ts';
683
+ export {
684
+ solidBox,
685
+ solidExtrude,
686
+ solidPrism,
687
+ solidQuad,
688
+ solidRoundedBox,
689
+ solidSweep,
690
+ } from './geometry/solidFlat.ts';
691
+ export {
692
+ solidCapsule,
693
+ solidCone,
694
+ solidCylinder,
695
+ solidFrustum,
696
+ solidHemisphere,
697
+ solidIcosphere,
698
+ solidLathe,
699
+ solidSphere,
700
+ solidTorus,
701
+ solidTube,
702
+ } from './geometry/solidRound.ts';
703
+ export { solidIntersect, solidSubtract, solidUnion } from './geometry/csg.ts';
704
+ export { boxBooleanCells, solidBoxBoolean } from './geometry/boxCsg.ts';
705
+ export type { BoxOperation } from './geometry/boxCsg.ts';
706
+ export { smoothSolidNormals } from './geometry/solidNormals.ts';
661
707
  /* A tangent frame, which every map in the material track reads. */
662
708
  export { generateTangents } from './geometry/tangents.ts';
663
709
  export type { MeshBuildOptions } from './geometry/meshBuilder.ts';
@@ -1017,7 +1063,9 @@ export type { FlockParams } from './render/flockRenderer.ts';
1017
1063
  export { buildTree } from './geometry/treeBuilder.ts';
1018
1064
  export type { TreeParams, TreeGeometry } from './geometry/treeBuilder.ts';
1019
1065
  export type { InstanceData } from './render/instancedMesh.ts';
1020
- export type { MeshInstances } from './render/instances.ts';
1066
+ export type { InstancedOptions, MeshInstances } from './render/instances.ts';
1067
+ export { batchBoxVisible, cullInstances, instancesBox } from './render/instanceCull.ts';
1068
+ export type { BoxOccluder } from './render/instanceCull.ts';
1021
1069
  export type { WindProfile, WindState } from './render/wind.ts';
1022
1070
 
1023
1071
  /*
@@ -1055,3 +1103,5 @@ export {
1055
1103
  thawCell,
1056
1104
  } from './world/freeze.ts';
1057
1105
  export type { FreezableWorld, FrozenCell, FrozenCells } from './world/freeze.ts';
1106
+ export { HLOD_NONE, HlodSet, createHlodDraws, projectionScaleOf } from './world/hlod.ts';
1107
+ export type { HlodDraws, HlodOptions } from './world/hlod.ts';
@@ -4,7 +4,7 @@ import type { ReadonlyMat4 } from 'gl-matrix';
4
4
  import type { MeshData } from '../mesh.ts';
5
5
  import type { ProbeBakeOptions } from '../reflectionProbe.ts';
6
6
  import type { Renderer, TranslucentMeshOptions } from './webgl2/renderer.ts';
7
- import type { MeshInstances } from '../instances.ts';
7
+ import type { InstancedOptions, MeshInstances } from '../instances.ts';
8
8
  import type { SurfaceMaterial } from '../surfaceTexture.ts';
9
9
  import type { TextStyle } from '../textLayout.ts';
10
10
  import type { SdfFont } from '../sdfFont.ts';
@@ -282,6 +282,7 @@ export type RendererApi = Omit<
282
282
  | 'drawSdfText'
283
283
  | 'disposeSdfText'
284
284
  | 'createSurfaceTexture'
285
+ | 'createSurfaceTextureArray'
285
286
  | 'updateSurfaceTexture'
286
287
  | 'disposeSurfaceTexture'
287
288
  | 'setSurfaceTexture'
@@ -293,6 +294,24 @@ export type RendererApi = Omit<
293
294
  options?: Parameters<Renderer['createSurfaceTexture']>[1],
294
295
  ): SurfaceTextureHandle;
295
296
 
297
+ /**
298
+ * Upload several images of one size as the layers of one array texture.
299
+ *
300
+ * The same handle a single image returns — every surface texture is an array, and a plain image
301
+ * is an array of one — so it goes wherever a texture goes: any map of `setMaterial`. A mesh picks
302
+ * its image per vertex with `MeshData.layers`, and every map of the material is read at that
303
+ * layer, so an albedo array and an emissive array built in one order pair image with glow by
304
+ * index. **A merged mesh wearing many images is then one draw and one material change.**
305
+ *
306
+ * Every image must be the same size: the device allocates one size for every layer, and the
307
+ * refusal names the first image that differs. A layer past the end reads the last one.
308
+ * `updateSurfaceTexture` refuses an array.
309
+ */
310
+ createSurfaceTextureArray(
311
+ sources: readonly TexImageSource[],
312
+ options?: Parameters<Renderer['createSurfaceTexture']>[1],
313
+ ): SurfaceTextureHandle;
314
+
296
315
  /**
297
316
  * Replace a texture's pixels, keeping the GPU object and its sampler state.
298
317
  *
@@ -548,7 +567,7 @@ export type RendererApi = Omit<
548
567
  * would wear one expression. Both are refused where the variant is built rather than dropped
549
568
  * quietly.
550
569
  */
551
- createInstanced(mesh: MeshHandle, capacity: number): InstancedHandle;
570
+ createInstanced(mesh: MeshHandle, capacity: number, options?: InstancedOptions): InstancedHandle;
552
571
 
553
572
  /**
554
573
  * Push placement and colour to the device. Only `data.count` instances are written.
@@ -0,0 +1,31 @@
1
+ /**
2
+ * How a draw meets the medium between it and the eye: the value of the mesh shader's
3
+ * `uFogEnabled`, which both backends write from this one rule.
4
+ *
5
+ * **A surface recedes into the medium; light added to the frame fades in it.** A surface that
6
+ * covers what is behind it is mixed toward the medium's colour with distance, which is the haze
7
+ * standing in front of it. An additive draw covers nothing: what is behind it has already been
8
+ * fogged, and mixing the draw toward the medium's colour and then *adding* it puts that haze into
9
+ * the frame a second time. So a glow by day added the sky's grey in the shape of the glow, and
10
+ * added more of it the thicker the air — reported as solid grey cones under every street lamp on
11
+ * a hazy morning. Faded instead, added light loses what the medium scatters out of it and adds
12
+ * nothing of its own, which is how the same shader already treats the glow behind frosted glass.
13
+ *
14
+ * What fading gives up is the one thing mixing did for a glow: at great distance it disappears
15
+ * into the air instead of greying into it. That is what light in fog does. What would make it
16
+ * wrong is an additive draw standing for *lit air* rather than light — and that is what
17
+ * `drawLightVolume` is for, which is fogged by neither rule.
18
+ */
19
+
20
+ /** Out of the medium entirely: `TranslucentMeshOptions.fog` set to false. */
21
+ export const FOG_OFF = 0;
22
+ /** Mixed toward the medium's colour with distance: every surface, and the pass's default. */
23
+ export const FOG_RECEDE = 1;
24
+ /** Scaled down by what the medium scatters and given none of its colour: added light. */
25
+ export const FOG_FADE = 2;
26
+
27
+ /** The medium rule for a draw that asked for fog or not, and adds its light or covers. */
28
+ export function fogModeOf(fog: boolean, additive: boolean): number {
29
+ if (!fog) return FOG_OFF;
30
+ return additive ? FOG_FADE : FOG_RECEDE;
31
+ }
@@ -18,11 +18,14 @@
18
18
  * slot on the backend that keeps slots, and comparing every field would cost more than the slot.
19
19
  */
20
20
 
21
+ import { FOG_RECEDE } from './drawFog.ts';
22
+
21
23
  /** What of a draw's own options its material carries. */
22
24
  export interface DrawMaterialOptions {
23
25
  readonly opacity: number;
24
26
  readonly lit: boolean;
25
- readonly fog: boolean;
27
+ /** How it meets the medium, as `fogModeOf` says; the pass's own is `FOG_RECEDE`. */
28
+ readonly fog: number;
26
29
  readonly toneMapped: boolean;
27
30
  /** Whether the draw refracts this time, which a backend decides by whether it has a snapshot. */
28
31
  readonly refracting: boolean;
@@ -31,7 +34,11 @@ export interface DrawMaterialOptions {
31
34
  /** Whether a draw's options differ from the pass's, so it takes a material and leaves one to restore. */
32
35
  export function ownsMaterial(options: DrawMaterialOptions): boolean {
33
36
  return (
34
- options.opacity < 1 || !options.lit || !options.fog || !options.toneMapped || options.refracting
37
+ options.opacity < 1 ||
38
+ !options.lit ||
39
+ options.fog !== FOG_RECEDE ||
40
+ !options.toneMapped ||
41
+ options.refracting
35
42
  );
36
43
  }
37
44