@driftengine/core 4.3.0 → 4.4.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 (215) hide show
  1. package/CHANGELOG.json +261 -0
  2. package/README.md +1 -1
  3. package/dist/cinematic/cameraPath.d.ts +53 -0
  4. package/dist/cinematic/cameraPath.js +118 -0
  5. package/dist/environment/daylight.d.ts +77 -0
  6. package/dist/environment/daylight.js +77 -0
  7. package/dist/geometry/meshConcat.d.ts +9 -0
  8. package/dist/geometry/meshConcat.js +101 -0
  9. package/dist/index.d.ts +12 -2
  10. package/dist/index.js +14 -1
  11. package/dist/render/backend/createRenderer.d.ts +13 -1
  12. package/dist/render/backend/createRenderer.js +6 -5
  13. package/dist/render/backend/webgl2/renderer.d.ts +147 -1
  14. package/dist/render/backend/webgl2/renderer.js +402 -23
  15. package/dist/render/backend/webgpu/buffers.js +46 -8
  16. package/dist/render/backend/webgpu/depthPass.d.ts +81 -3
  17. package/dist/render/backend/webgpu/depthPass.js +75 -14
  18. package/dist/render/backend/webgpu/exposurePass.d.ts +39 -0
  19. package/dist/render/backend/webgpu/exposurePass.js +190 -0
  20. package/dist/render/backend/webgpu/fieldCompose.d.ts +11 -0
  21. package/dist/render/backend/webgpu/fieldCompose.js +13 -0
  22. package/dist/render/backend/webgpu/flatPass.d.ts +3 -1
  23. package/dist/render/backend/webgpu/flatPass.js +12 -5
  24. package/dist/render/backend/webgpu/particlePass.d.ts +8 -0
  25. package/dist/render/backend/webgpu/particlePass.js +1 -0
  26. package/dist/render/backend/webgpu/postPass.d.ts +12 -2
  27. package/dist/render/backend/webgpu/probeBake.d.ts +59 -3
  28. package/dist/render/backend/webgpu/probeBake.js +97 -7
  29. package/dist/render/backend/webgpu/probeTargets.d.ts +65 -0
  30. package/dist/render/backend/webgpu/probeTargets.js +180 -0
  31. package/dist/render/backend/webgpu/renderer.d.ts +174 -11
  32. package/dist/render/backend/webgpu/renderer.js +675 -249
  33. package/dist/render/backend/webgpu/scatterPass.js +7 -7
  34. package/dist/render/backend/webgpu/shaders/clusterBin.wgsl.d.ts +1 -1
  35. package/dist/render/backend/webgpu/skinPaletteRing.d.ts +4 -0
  36. package/dist/render/backend/webgpu/skinPaletteRing.js +9 -0
  37. package/dist/render/backend/webgpu/skyPass.js +5 -1
  38. package/dist/render/backend/webgpu/uniformRing.d.ts +31 -1
  39. package/dist/render/backend/webgpu/uniformRing.js +53 -3
  40. package/dist/render/bloomChain.d.ts +6 -0
  41. package/dist/render/bloomChain.js +8 -0
  42. package/dist/render/clusteredLights.d.ts +19 -11
  43. package/dist/render/clusteredLights.js +96 -45
  44. package/dist/render/cutoutCaster.d.ts +23 -0
  45. package/dist/render/cutoutCaster.js +20 -0
  46. package/dist/render/driftLight/bake.d.ts +34 -0
  47. package/dist/render/driftLight/bake.js +95 -0
  48. package/dist/render/driftLight/falloff.d.ts +16 -0
  49. package/dist/render/driftLight/falloff.js +23 -0
  50. package/dist/render/driftLight/layout.d.ts +59 -0
  51. package/dist/render/driftLight/layout.js +138 -0
  52. package/dist/render/driftLight/lightField.d.ts +108 -0
  53. package/dist/render/driftLight/lightField.js +179 -0
  54. package/dist/render/driftLight/uniforms.d.ts +28 -0
  55. package/dist/render/driftLight/uniforms.js +41 -0
  56. package/dist/render/driftLight/visibility.d.ts +18 -0
  57. package/dist/render/driftLight/visibility.js +47 -0
  58. package/dist/render/emptyTexture.d.ts +6 -0
  59. package/dist/render/emptyTexture.js +19 -0
  60. package/dist/render/exposurePass.d.ts +48 -0
  61. package/dist/render/exposurePass.js +172 -0
  62. package/dist/render/flameLight.d.ts +71 -0
  63. package/dist/render/flameLight.js +63 -0
  64. package/dist/render/gi/bounceLights.d.ts +34 -0
  65. package/dist/render/gi/bounceLights.js +66 -0
  66. package/dist/render/gi/fieldReach.d.ts +12 -0
  67. package/dist/render/gi/fieldReach.js +62 -0
  68. package/dist/render/gi/globalField.d.ts +8 -0
  69. package/dist/render/gi/globalField.js +31 -0
  70. package/dist/render/gi/probeTrace.d.ts +3 -1
  71. package/dist/render/gi/probeTrace.js +3 -1
  72. package/dist/render/gpuCapability.js +19 -4
  73. package/dist/render/lightBudget.d.ts +11 -0
  74. package/dist/render/lightBudget.js +11 -0
  75. package/dist/render/pointLightSelection.d.ts +16 -0
  76. package/dist/render/pointLightSelection.js +178 -83
  77. package/dist/render/probeGrid.d.ts +11 -1
  78. package/dist/render/probeGrid.js +5 -2
  79. package/dist/render/probeSweeps.d.ts +45 -0
  80. package/dist/render/probeSweeps.js +77 -0
  81. package/dist/render/reflectionProbe.d.ts +43 -1
  82. package/dist/render/reflectionProbe.js +17 -3
  83. package/dist/render/renderQuality.d.ts +3 -1
  84. package/dist/render/rendererHarness.d.ts +9 -0
  85. package/dist/render/rendererHarness.js +18 -3
  86. package/dist/render/sceneTarget.d.ts +20 -0
  87. package/dist/render/sceneTarget.js +62 -1
  88. package/dist/render/shaders/agx.d.ts +31 -0
  89. package/dist/render/shaders/agx.js +81 -0
  90. package/dist/render/shaders/ambientOcclusion.d.ts +24 -5
  91. package/dist/render/shaders/ambientOcclusion.js +120 -63
  92. package/dist/render/shaders/cutoutCoverage.d.ts +31 -0
  93. package/dist/render/shaders/cutoutCoverage.js +40 -0
  94. package/dist/render/shaders/depth.d.ts +25 -0
  95. package/dist/render/shaders/depth.js +69 -3
  96. package/dist/render/shaders/exposure.d.ts +52 -0
  97. package/dist/render/shaders/exposure.js +124 -0
  98. package/dist/render/shaders/filmLook.d.ts +47 -0
  99. package/dist/render/shaders/filmLook.js +98 -0
  100. package/dist/render/shaders/flat/directionalShadow.js +35 -4
  101. package/dist/render/shaders/flat/driftLight.d.ts +29 -0
  102. package/dist/render/shaders/flat/driftLight.js +75 -0
  103. package/dist/render/shaders/flat/index.js +5 -0
  104. package/dist/render/shaders/flat/lobes.js +13 -0
  105. package/dist/render/shaders/flat/main.d.ts +1 -1
  106. package/dist/render/shaders/flat/main.js +68 -9
  107. package/dist/render/shaders/flat/preamble.js +23 -5
  108. package/dist/render/shaders/flat/probeGrid.js +46 -3
  109. package/dist/render/shaders/generated/ambientOcclusion.wgsl.d.ts +1 -1
  110. package/dist/render/shaders/generated/ambientOcclusion.wgsl.js +1 -1
  111. package/dist/render/shaders/generated/depth.wgsl.d.ts +100 -0
  112. package/dist/render/shaders/generated/depth.wgsl.js +100 -0
  113. package/dist/render/shaders/generated/exposure.wgsl.d.ts +42 -0
  114. package/dist/render/shaders/generated/exposure.wgsl.js +48 -0
  115. package/dist/render/shaders/generated/flat.wgsl.d.ts +1800 -1344
  116. package/dist/render/shaders/generated/flat.wgsl.js +1816 -1360
  117. package/dist/render/shaders/generated/localExposure.wgsl.d.ts +18 -0
  118. package/dist/render/shaders/generated/localExposure.wgsl.js +24 -0
  119. package/dist/render/shaders/generated/rush.wgsl.d.ts +40 -10
  120. package/dist/render/shaders/generated/rush.wgsl.js +40 -10
  121. package/dist/render/shaders/generated/temporalResolve.wgsl.d.ts +1 -1
  122. package/dist/render/shaders/generated/temporalResolve.wgsl.js +1 -1
  123. package/dist/render/shaders/gi/driftLight.wgsl.d.ts +16 -0
  124. package/dist/render/shaders/gi/driftLight.wgsl.js +49 -0
  125. package/dist/render/shaders/gi/probeBake.wgsl.d.ts +29 -4
  126. package/dist/render/shaders/gi/probeBake.wgsl.js +190 -20
  127. package/dist/render/shaders/globalMedium.js +4 -0
  128. package/dist/render/shaders/localExposure.d.ts +35 -0
  129. package/dist/render/shaders/localExposure.js +138 -0
  130. package/dist/render/shaders/prefilter.d.ts +1 -1
  131. package/dist/render/shaders/prefilter.js +6 -0
  132. package/dist/render/shaders/rush.js +33 -5
  133. package/dist/render/shaders/tangentFrame.d.ts +1 -1
  134. package/dist/render/shaders/tangentFrame.js +15 -3
  135. package/dist/render/shaders/temporalResolve.js +39 -1
  136. package/dist/render/shadowCasters.d.ts +5 -3
  137. package/dist/render/shadowMap.js +13 -0
  138. package/dist/render/surfaceTexture.d.ts +5 -0
  139. package/package.json +6 -6
  140. package/scripts/gpuCompute.mjs +24 -3
  141. package/scripts/png.mjs +23 -12
  142. package/src/cinematic/cameraPath.ts +178 -0
  143. package/src/environment/daylight.ts +152 -0
  144. package/src/geometry/meshConcat.ts +100 -0
  145. package/src/index.ts +32 -0
  146. package/src/render/backend/createRenderer.ts +22 -6
  147. package/src/render/backend/webgl2/renderer.ts +439 -20
  148. package/src/render/backend/webgpu/buffers.ts +51 -8
  149. package/src/render/backend/webgpu/depthPass.ts +78 -10
  150. package/src/render/backend/webgpu/exposurePass.ts +234 -0
  151. package/src/render/backend/webgpu/fieldCompose.ts +15 -0
  152. package/src/render/backend/webgpu/flatPass.ts +10 -2
  153. package/src/render/backend/webgpu/particlePass.ts +9 -0
  154. package/src/render/backend/webgpu/probeBake.ts +143 -9
  155. package/src/render/backend/webgpu/probeTargets.ts +211 -0
  156. package/src/render/backend/webgpu/renderer.ts +801 -270
  157. package/src/render/backend/webgpu/scatterPass.ts +7 -7
  158. package/src/render/backend/webgpu/skinPaletteRing.ts +10 -0
  159. package/src/render/backend/webgpu/skyPass.ts +5 -1
  160. package/src/render/backend/webgpu/uniformRing.ts +49 -3
  161. package/src/render/bloomChain.ts +9 -0
  162. package/src/render/clusteredLights.ts +108 -63
  163. package/src/render/cutoutCaster.ts +44 -0
  164. package/src/render/driftLight/bake.ts +125 -0
  165. package/src/render/driftLight/falloff.ts +26 -0
  166. package/src/render/driftLight/layout.ts +177 -0
  167. package/src/render/driftLight/lightField.ts +227 -0
  168. package/src/render/driftLight/uniforms.ts +61 -0
  169. package/src/render/driftLight/visibility.ts +58 -0
  170. package/src/render/emptyTexture.ts +41 -0
  171. package/src/render/exposurePass.ts +186 -0
  172. package/src/render/flameLight.ts +135 -0
  173. package/src/render/gi/bounceLights.ts +68 -0
  174. package/src/render/gi/fieldReach.ts +74 -0
  175. package/src/render/gi/globalField.ts +39 -0
  176. package/src/render/gi/probeTrace.ts +3 -1
  177. package/src/render/gpuCapability.ts +18 -5
  178. package/src/render/lightBudget.ts +12 -0
  179. package/src/render/pointLightSelection.ts +191 -79
  180. package/src/render/probeGrid.ts +13 -2
  181. package/src/render/probeSweeps.ts +80 -0
  182. package/src/render/reflectionProbe.ts +54 -2
  183. package/src/render/renderQuality.ts +3 -1
  184. package/src/render/rendererHarness.ts +25 -3
  185. package/src/render/sceneTarget.ts +75 -1
  186. package/src/render/shaders/ambientOcclusion.ts +125 -63
  187. package/src/render/shaders/cutoutCoverage.ts +43 -0
  188. package/src/render/shaders/depth.ts +80 -3
  189. package/src/render/shaders/exposure.ts +128 -0
  190. package/src/render/shaders/filmLook.ts +111 -0
  191. package/src/render/shaders/flat/directionalShadow.ts +35 -4
  192. package/src/render/shaders/flat/driftLight.ts +75 -0
  193. package/src/render/shaders/flat/index.ts +5 -0
  194. package/src/render/shaders/flat/lobes.ts +13 -0
  195. package/src/render/shaders/flat/main.ts +68 -9
  196. package/src/render/shaders/flat/preamble.ts +23 -5
  197. package/src/render/shaders/flat/probeGrid.ts +46 -3
  198. package/src/render/shaders/generated/ambientOcclusion.wgsl.ts +1 -1
  199. package/src/render/shaders/generated/depth.wgsl.ts +103 -0
  200. package/src/render/shaders/generated/exposure.wgsl.ts +51 -0
  201. package/src/render/shaders/generated/flat.wgsl.ts +1816 -1360
  202. package/src/render/shaders/generated/localExposure.wgsl.ts +26 -0
  203. package/src/render/shaders/generated/rush.wgsl.ts +40 -10
  204. package/src/render/shaders/generated/temporalResolve.wgsl.ts +1 -1
  205. package/src/render/shaders/gi/driftLight.wgsl.ts +49 -0
  206. package/src/render/shaders/gi/probeBake.wgsl.ts +191 -20
  207. package/src/render/shaders/globalMedium.ts +4 -0
  208. package/src/render/shaders/localExposure.ts +154 -0
  209. package/src/render/shaders/prefilter.ts +6 -0
  210. package/src/render/shaders/rush.ts +33 -5
  211. package/src/render/shaders/tangentFrame.ts +15 -3
  212. package/src/render/shaders/temporalResolve.ts +39 -1
  213. package/src/render/shadowCasters.ts +5 -3
  214. package/src/render/shadowMap.ts +14 -0
  215. package/src/render/surfaceTexture.ts +5 -0
@@ -0,0 +1,152 @@
1
+ /**
2
+ * A day's lighting as a palette keyed by one number, and an exposure that follows it in time.
3
+ *
4
+ * **Keyed by any scalar a caller chooses.** A courtyard keys it by the sun's elevation in degrees,
5
+ * because what the light does there is a function of how high the sun stands; a scene that lerps
6
+ * between a night and a day by a day factor keys it at 0 and 1 and gets exactly the blend it had.
7
+ * The palette is data — colours, a density, a strength, an exposure — so which hours exist and what
8
+ * they look like stay the scene's, and the mechanism is the engine's.
9
+ *
10
+ * **Linear between keys, and in stops for exposure.** Colours and densities blend linearly, which
11
+ * is what every hand-written day cycle in this repository did; exposure blends in log2, because two
12
+ * keys a stop apart should pass through half a stop and not through the arithmetic mean, which sits
13
+ * nearer the brighter key. What that gives up is smoothness at a key: the blend has a corner there.
14
+ * What would make it wrong is a palette so sparse that the corner shows as a change of pace in the
15
+ * sky, and the answer then is more keys rather than a curve.
16
+ *
17
+ * Nothing here allocates after construction: `resolveDaylight` writes into a caller-owned state.
18
+ */
19
+ import type { Vec3 } from '../math/color.ts';
20
+
21
+ /** The lighting at one point of the palette's scalar. Every colour is linear and may exceed one. */
22
+ export interface DaylightKey {
23
+ /** Where on the palette's scalar this key sits: a sun elevation, a day factor, an hour. */
24
+ readonly at: number;
25
+ /** The sun's radiance at the surface it lights, which is the directional light while it is up. */
26
+ readonly sunColor: Readonly<Vec3>;
27
+ /** The moon's, for whichever part of the palette the moon lights. */
28
+ readonly moonColor: Readonly<Vec3>;
29
+ readonly skyTop: Readonly<Vec3>;
30
+ readonly skyHorizon: Readonly<Vec3>;
31
+ readonly skyDeep: Readonly<Vec3>;
32
+ /** The hemisphere's sky half. */
33
+ readonly ambient: Readonly<Vec3>;
34
+ /** The hemisphere's ground half: what the floor throws back up. */
35
+ readonly ambientGround: Readonly<Vec3>;
36
+ readonly fogColor: Readonly<Vec3>;
37
+ readonly fogDensity: number;
38
+ /** How dark a directional shadow is, 0 to 1. */
39
+ readonly shadowStrength: number;
40
+ /** The emissive master gain: how strongly lamps and flames read against this light. */
41
+ readonly emissiveGain: number;
42
+ /** The exposure a viewer's eye settles on here. Positive; blended in stops. */
43
+ readonly exposure: number;
44
+ }
45
+
46
+ /** Keys in ascending order of `at`, validated once. Build with `createDaylightPalette`. */
47
+ export interface DaylightPalette {
48
+ readonly keys: readonly DaylightKey[];
49
+ }
50
+
51
+ /** One moment of the palette, written in place by `resolveDaylight`. */
52
+ export interface DaylightState {
53
+ sunColor: Vec3;
54
+ moonColor: Vec3;
55
+ skyTop: Vec3;
56
+ skyHorizon: Vec3;
57
+ skyDeep: Vec3;
58
+ ambient: Vec3;
59
+ ambientGround: Vec3;
60
+ fogColor: Vec3;
61
+ fogDensity: number;
62
+ shadowStrength: number;
63
+ emissiveGain: number;
64
+ exposure: number;
65
+ }
66
+
67
+ /** Sort and check the keys. Throws at construction, so a bad palette never reaches a frame. */
68
+ export function createDaylightPalette(keys: readonly DaylightKey[]): DaylightPalette {
69
+ if (keys.length === 0) throw new Error('a daylight palette needs at least one key');
70
+ const sorted = [...keys].sort((a, b) => a.at - b.at);
71
+ for (let i = 0; i < sorted.length; i++) {
72
+ const current = sorted[i] as DaylightKey;
73
+ if (!(current.exposure > 0)) {
74
+ throw new Error(`the key at ${current.at} has exposure ${current.exposure}; it must be > 0`);
75
+ }
76
+ if (i > 0 && (sorted[i - 1] as DaylightKey).at === current.at) {
77
+ throw new Error(`a daylight palette has two keys at ${current.at}`);
78
+ }
79
+ }
80
+ return { keys: sorted };
81
+ }
82
+
83
+ export function createDaylightState(): DaylightState {
84
+ return {
85
+ sunColor: [0, 0, 0],
86
+ moonColor: [0, 0, 0],
87
+ skyTop: [0, 0, 0],
88
+ skyHorizon: [0, 0, 0],
89
+ skyDeep: [0, 0, 0],
90
+ ambient: [0, 0, 0],
91
+ ambientGround: [0, 0, 0],
92
+ fogColor: [0, 0, 0],
93
+ fogDensity: 0,
94
+ shadowStrength: 0,
95
+ emissiveGain: 0,
96
+ exposure: 1,
97
+ };
98
+ }
99
+
100
+ function blendInto(out: Vec3, a: Readonly<Vec3>, b: Readonly<Vec3>, t: number): void {
101
+ out[0] = a[0] + (b[0] - a[0]) * t;
102
+ out[1] = a[1] + (b[1] - a[1]) * t;
103
+ out[2] = a[2] + (b[2] - a[2]) * t;
104
+ }
105
+
106
+ /** The palette at `at`, into `out`. Past either end the nearest key holds. */
107
+ export function resolveDaylight(
108
+ at: number,
109
+ palette: DaylightPalette,
110
+ out: DaylightState,
111
+ ): DaylightState {
112
+ const keys = palette.keys;
113
+ let upper = 0;
114
+ while (upper < keys.length && (keys[upper] as DaylightKey).at < at) upper++;
115
+ const b = keys[Math.min(upper, keys.length - 1)] as DaylightKey;
116
+ const a = keys[Math.max(upper - 1, 0)] as DaylightKey;
117
+ const t = a === b ? 0 : (at - a.at) / (b.at - a.at);
118
+ blendInto(out.sunColor, a.sunColor, b.sunColor, t);
119
+ blendInto(out.moonColor, a.moonColor, b.moonColor, t);
120
+ blendInto(out.skyTop, a.skyTop, b.skyTop, t);
121
+ blendInto(out.skyHorizon, a.skyHorizon, b.skyHorizon, t);
122
+ blendInto(out.skyDeep, a.skyDeep, b.skyDeep, t);
123
+ blendInto(out.ambient, a.ambient, b.ambient, t);
124
+ blendInto(out.ambientGround, a.ambientGround, b.ambientGround, t);
125
+ blendInto(out.fogColor, a.fogColor, b.fogColor, t);
126
+ out.fogDensity = a.fogDensity + (b.fogDensity - a.fogDensity) * t;
127
+ out.shadowStrength = a.shadowStrength + (b.shadowStrength - a.shadowStrength) * t;
128
+ out.emissiveGain = a.emissiveGain + (b.emissiveGain - a.emissiveGain) * t;
129
+ out.exposure = t === 0 ? a.exposure : a.exposure * (b.exposure / a.exposure) ** t;
130
+ return out;
131
+ }
132
+
133
+ /**
134
+ * `current` moved toward `target` over `dtSec`, closing half the distance in stops every
135
+ * `halfLifeSec`.
136
+ *
137
+ * **In time, not per frame**, so a 144 Hz display adapts exactly as fast as a 60 Hz one: the
138
+ * remaining distance after any number of steps is `2^(−elapsed / halfLife)` of what it was, however
139
+ * the elapsed time was cut up. A half-life of zero is a cut to the target. Both exposures must be
140
+ * positive, which is what an exposure is.
141
+ */
142
+ export function easeExposure(
143
+ current: number,
144
+ target: number,
145
+ dtSec: number,
146
+ halfLifeSec: number,
147
+ ): number {
148
+ if (halfLifeSec <= 0) return target;
149
+ if (dtSec <= 0) return current;
150
+ const closed = 1 - 2 ** (-dtSec / halfLifeSec);
151
+ return current * (target / current) ** closed;
152
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Meshes moved and joined as typed arrays: the loader's fast path, with no builder in between.
3
+ *
4
+ * **`MeshBuilder` is for building geometry, and a loader was using it to copy geometry.** It
5
+ * pushes every vertex one number at a time into growable arrays, and it carries the attributes a
6
+ * procedural mesh has. Streaming a bought courtyard spent seven seconds of its main thread there,
7
+ * copying each part twice, once to upload it and once into its merge group. Worse, the builder has
8
+ * no tangent channel, so every normal-mapped part lost the frame its file carried and the shader
9
+ * fell back to derivatives.
10
+ *
11
+ * So `placeMesh` moves a mesh by a fit, or hands it back untouched when the fit is the identity,
12
+ * and `concatMeshes` joins a group once, into arrays sized in advance. An attribute one member
13
+ * lacks is filled with the value a mesh without it draws with (`ABSENT_ATTRIBUTE`). One no member
14
+ * carries is left out, as absent rather than invented.
15
+ */
16
+ import { ABSENT_ATTRIBUTE } from '../render/vertexDefaults.ts';
17
+ import type { MeshData } from '../render/mesh.ts';
18
+
19
+ /** The optional per-vertex attributes, with how many floats a vertex each carries. */
20
+ const OPTIONAL = [
21
+ ['specular', 1],
22
+ ['tangents', 4],
23
+ ['uvs', 2],
24
+ ['emissiveColor', 3],
25
+ ['roughness', 1],
26
+ ['grain', 1],
27
+ ['relief', 1],
28
+ ['channel', 4],
29
+ ['joints', 4],
30
+ ['weights', 4],
31
+ ] as const;
32
+
33
+ /**
34
+ * `mesh` scaled uniformly and then moved by `x, y, z`. The same object when the fit is the
35
+ * identity; otherwise a copy of the positions and every other attribute shared, since a uniform
36
+ * scale turns no normal. Morph deltas are the caller's to scale.
37
+ */
38
+ export function placeMesh(mesh: MeshData, x: number, y: number, z: number, scale = 1): MeshData {
39
+ if (x === 0 && y === 0 && z === 0 && scale === 1) return mesh;
40
+ const from = mesh.positions;
41
+ const positions = new Float32Array(from.length);
42
+ for (let i = 0; i + 2 < from.length; i += 3) {
43
+ positions[i] = (from[i] as number) * scale + x;
44
+ positions[i + 1] = (from[i + 1] as number) * scale + y;
45
+ positions[i + 2] = (from[i + 2] as number) * scale + z;
46
+ }
47
+ return { ...mesh, positions };
48
+ }
49
+
50
+ /** `meshes` as one, in order, each member's indices moved past the vertices before it. */
51
+ export function concatMeshes(meshes: readonly MeshData[]): MeshData {
52
+ const only = meshes[0];
53
+ if (meshes.length === 1 && only !== undefined) return only;
54
+ let vertices = 0;
55
+ let indexCount = 0;
56
+ for (const mesh of meshes) {
57
+ vertices += mesh.positions.length / 3;
58
+ indexCount += mesh.indices.length;
59
+ }
60
+ const out: MeshData = {
61
+ positions: new Float32Array(vertices * 3),
62
+ normals: new Float32Array(vertices * 3),
63
+ colors: new Float32Array(vertices * 3),
64
+ emissive: new Float32Array(vertices),
65
+ indices: new Uint32Array(indexCount),
66
+ };
67
+ const optional = out as unknown as Record<string, Float32Array | undefined>;
68
+ for (const [key, width] of OPTIONAL) {
69
+ if (meshes.some((mesh) => mesh[key] !== undefined)) {
70
+ optional[key] = new Float32Array(vertices * width);
71
+ }
72
+ }
73
+ let vertex = 0;
74
+ let index = 0;
75
+ for (const mesh of meshes) {
76
+ const count = mesh.positions.length / 3;
77
+ out.positions.set(mesh.positions, vertex * 3);
78
+ out.normals.set(mesh.normals, vertex * 3);
79
+ out.colors.set(mesh.colors, vertex * 3);
80
+ out.emissive.set(mesh.emissive, vertex);
81
+ for (const [key, width] of OPTIONAL) {
82
+ const target = optional[key];
83
+ if (target === undefined) continue;
84
+ const source = mesh[key];
85
+ if (source !== undefined) {
86
+ target.set(source, vertex * width);
87
+ continue;
88
+ }
89
+ const fill = ABSENT_ATTRIBUTE[key] ?? [];
90
+ for (let v = 0; v < count; v++) {
91
+ for (let c = 0; c < width; c++) target[(vertex + v) * width + c] = fill[c] ?? 0;
92
+ }
93
+ }
94
+ const from = mesh.indices;
95
+ for (let i = 0; i < from.length; i++) out.indices[index + i] = (from[i] as number) + vertex;
96
+ vertex += count;
97
+ index += from.length;
98
+ }
99
+ return out;
100
+ }
package/src/index.ts CHANGED
@@ -50,6 +50,17 @@ export {
50
50
  } from './core/timeCurve.ts';
51
51
  export type { TimeCurve, TimeCurveKind, TimeCurveOptions } from './core/timeCurve.ts';
52
52
  export { CinematicPlayer } from './cinematic/player.ts';
53
+ export {
54
+ createCameraPath,
55
+ createCameraPathSample,
56
+ sampleCameraPath,
57
+ } from './cinematic/cameraPath.ts';
58
+ export type {
59
+ CameraPath,
60
+ CameraPathKey,
61
+ CameraPathOptions,
62
+ CameraPathSample,
63
+ } from './cinematic/cameraPath.ts';
53
64
  export { defineCinematic, validateCinematic } from './cinematic/script.ts';
54
65
  export type {
55
66
  CameraKeyframe,
@@ -65,6 +76,13 @@ export {
65
76
  moonIllumination,
66
77
  } from './environment/celestialClock.ts';
67
78
  export type { CelestialSite, CelestialState } from './environment/celestialClock.ts';
79
+ export {
80
+ createDaylightPalette,
81
+ createDaylightState,
82
+ easeExposure,
83
+ resolveDaylight,
84
+ } from './environment/daylight.ts';
85
+ export type { DaylightKey, DaylightPalette, DaylightState } from './environment/daylight.ts';
68
86
 
69
87
  export {
70
88
  TAU,
@@ -219,6 +237,7 @@ export { createRenderer } from './render/backend/createRenderer.ts';
219
237
  export type {
220
238
  CreatedRenderer,
221
239
  CreateRendererOptions,
240
+ QualityForBackend,
222
241
  RenderPipeline,
223
242
  } from './render/backend/createRenderer.ts';
224
243
  /*
@@ -361,6 +380,8 @@ export type {
361
380
  GlobalFieldCascade,
362
381
  GlobalFieldInstance,
363
382
  } from './render/gi/globalField.ts';
383
+ export { FIELD_FADE_SEC, LightField } from './render/driftLight/lightField.ts';
384
+ export type { LightFieldOptions, LightFieldSource } from './render/driftLight/lightField.ts';
364
385
  export {
365
386
  GI_SCREEN_MARCH,
366
387
  SCREEN_TRACE_BIAS_M,
@@ -554,6 +575,8 @@ export type {
554
575
  WaterReflectionFilterTaps,
555
576
  } from './render/renderQuality.ts';
556
577
  export { MAX_POINT_LIGHTS, SURFACE_TEXTURE_UNIT } from './render/lightBudget.ts';
578
+ /* The width a light buffer can usefully be with `clusteredLights` on; `createPointLightBuffer`. */
579
+ export { MAX_CLUSTERED_LIGHTS } from './render/clusteredLights.ts';
557
580
  export { SurfaceTexture } from './render/surfaceTexture.ts';
558
581
  export type { SurfaceMaterial, SurfaceTextureOptions } from './render/surfaceTexture.ts';
559
582
  export { DEFAULT_GOVERNOR_LIMITS, ResolutionGovernor } from './render/resolutionGovernor.ts';
@@ -573,6 +596,8 @@ export {
573
596
  selectPointLights,
574
597
  } from './render/pointLightSelection.ts';
575
598
  export type { PointLightBuffer, PointLightSource } from './render/pointLightSelection.ts';
599
+ export { createFlame, flameFrequencyHz, updateFlame } from './render/flameLight.ts';
600
+ export type { Flame, FlameOptions } from './render/flameLight.ts';
576
601
  export { computeLightMatrix } from './render/lightMatrix.ts';
577
602
  export { PlumeRenderer } from './render/plumeRenderer.ts';
578
603
  export { createEnvironment } from './render/backend/webgl2/renderer.ts';
@@ -622,6 +647,7 @@ export { SMOKE_FRAG } from './render/shaders/smoke.ts';
622
647
  export { WaterRenderer } from './render/waterRenderer.ts';
623
648
  export type { WaterBody, WaterBounds, WaterSettings } from './render/waterRenderer.ts';
624
649
  export { MeshBuilder } from './geometry/meshBuilder.ts';
650
+ export { concatMeshes, placeMesh } from './geometry/meshConcat.ts';
625
651
  /* A tangent frame, which every map in the material track reads. */
626
652
  export { generateTangents } from './geometry/tangents.ts';
627
653
  export type { MeshBuildOptions } from './geometry/meshBuilder.ts';
@@ -941,6 +967,12 @@ export {
941
967
  * and draw with that one number.
942
968
  */
943
969
  deviceSnappedCellSize,
970
+ /*
971
+ * How many lit cells one text object draws before the rest of its string is cut. Exported so a
972
+ * caller laying out a runtime string, a readout that grows, can break it before the engine does:
973
+ * the cut is silent by design, since a clipped message beats a frame that does not render.
974
+ */
975
+ MAX_CELLS as MAX_TEXT_CELLS,
944
976
  textHeightPx,
945
977
  textWidthPx,
946
978
  } from './render/textLayout.ts';
@@ -49,6 +49,20 @@ export function pipelineRefusal(
49
49
  }
50
50
 
51
51
  /** Everything about selection a caller may want to decide instead of inherit. */
52
+ /**
53
+ * The quality to build with, or a function of the backend `createRenderer` chose.
54
+ *
55
+ * **A function is how an option only one backend has is asked of that backend alone.** Traced
56
+ * indirect light and reconstruction are WebGPU's, and WebGL2 refuses either in words when handed
57
+ * one: right for a consumer who asked for it there, and noise for one who wanted it wherever it
58
+ * runs, since every device that fell back printed a refusal. Called once for the backend that
59
+ * draws, and once more for WebGPU's timing request where WebGPU is tried. What would make it wrong
60
+ * is a consumer needing to know *why* the backend fell back before choosing, which `reason` on the
61
+ * result says after the fact and nothing says before.
62
+ */
63
+ export type QualityForBackend =
64
+ RenderQualityOptions | ((backend: RenderBackend) => RenderQualityOptions);
65
+
52
66
  export interface CreateRendererOptions {
53
67
  /**
54
68
  * Whether to try WebGPU at all. **Defaults to true**: WebGPU is the backend this engine is
@@ -234,9 +248,11 @@ function presentOnFirstFrame(renderer: RendererApi, splash: MountedSplash): void
234
248
  */
235
249
  export async function createRenderer(
236
250
  canvas: HTMLCanvasElement,
237
- quality: RenderQualityOptions = {},
251
+ quality: QualityForBackend = {},
238
252
  options: CreateRendererOptions = {},
239
253
  ): Promise<CreatedRenderer> {
254
+ const qualityFor = (backend: RenderBackend): RenderQualityOptions =>
255
+ typeof quality === 'function' ? quality(backend) : quality;
240
256
  const search = options.search ?? globalThis.location?.search ?? '';
241
257
 
242
258
  /*
@@ -317,7 +333,7 @@ export async function createRenderer(
317
333
  const choice = await selectBackend(
318
334
  search,
319
335
  options.preferWebGpu ?? true,
320
- quality.gpuTiming ?? false,
336
+ (options.preferWebGpu ?? true) ? (qualityFor('webgpu').gpuTiming ?? false) : false,
321
337
  options.probeShaders ?? [],
322
338
  budgetMs,
323
339
  );
@@ -364,7 +380,7 @@ export async function createRenderer(
364
380
  if (settled === TIMED_OUT) {
365
381
  device.destroy();
366
382
  return built({
367
- renderer: new Renderer(canvas, quality),
383
+ renderer: new Renderer(canvas, qualityFor('webgl2')),
368
384
  backend: 'webgl2',
369
385
  reason: `WebGPU stalled while ${stage}, fell back after ${budgetMs} ms`,
370
386
  });
@@ -385,7 +401,7 @@ export async function createRenderer(
385
401
  */
386
402
  renderer: new WebGPURenderer(
387
403
  surface,
388
- resolveRenderQuality(quality),
404
+ resolveRenderQuality(qualityFor('webgpu')),
389
405
  /* The adapter's own words, so the capability clamp has a part number to
390
406
  recognise rather than the literal string `WebGPU`. */
391
407
  choice.rendererName ?? undefined,
@@ -397,7 +413,7 @@ export async function createRenderer(
397
413
  choice.device.destroy();
398
414
  const detail = error instanceof Error ? error.message : String(error);
399
415
  return built({
400
- renderer: new Renderer(canvas, quality),
416
+ renderer: new Renderer(canvas, qualityFor('webgl2')),
401
417
  backend: 'webgl2',
402
418
  reason: `WebGPU surface failed, fell back: ${detail}`,
403
419
  });
@@ -425,7 +441,7 @@ export async function createRenderer(
425
441
  console.warn(`[driftengine] WebGPU was asked for and is not being used: ${choice.reason}`);
426
442
  }
427
443
  return built({
428
- renderer: new Renderer(canvas, quality),
444
+ renderer: new Renderer(canvas, qualityFor('webgl2')),
429
445
  backend: 'webgl2',
430
446
  reason: choice.reason,
431
447
  });