@woosh/meep-engine 3.37.0 → 3.38.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 (106) hide show
  1. package/package.json +1 -1
  2. package/src/engine/ecs/storage/populateEngineSerializationRegistry.d.ts.map +1 -1
  3. package/src/engine/ecs/storage/populateEngineSerializationRegistry.js +2 -0
  4. package/src/engine/ecs/transform/TRANSFORM64_EVENT_PLACED.d.ts +11 -0
  5. package/src/engine/ecs/transform/TRANSFORM64_EVENT_PLACED.d.ts.map +1 -0
  6. package/src/engine/ecs/transform/TRANSFORM64_EVENT_PLACED.js +10 -0
  7. package/src/engine/ecs/transform-attachment/TransformAttachmentSystem.d.ts.map +1 -1
  8. package/src/engine/ecs/transform-attachment/TransformAttachmentSystem.js +16 -0
  9. package/src/engine/graphics/ecs/water/Water.d.ts +30 -0
  10. package/src/engine/graphics/ecs/water/Water.d.ts.map +1 -1
  11. package/src/engine/graphics/ecs/water/Water.js +125 -104
  12. package/src/engine/graphics/ecs/water/WaterSerializationAdapter.d.ts.map +1 -1
  13. package/src/engine/graphics/ecs/water/WaterSerializationAdapter.js +13 -1
  14. package/src/engine/graphics/ecs/water/WaterSerializationUpgrader_1_2.d.ts +6 -0
  15. package/src/engine/graphics/ecs/water/WaterSerializationUpgrader_1_2.d.ts.map +1 -0
  16. package/src/engine/graphics/ecs/water/WaterSerializationUpgrader_1_2.js +18 -0
  17. package/src/engine/graphics3/WaterSystem.d.ts +22 -6
  18. package/src/engine/graphics3/WaterSystem.d.ts.map +1 -1
  19. package/src/engine/graphics3/WaterSystem.js +314 -293
  20. package/src/engine/physics/cloth/collider/ClothColliderIndex.d.ts +13 -14
  21. package/src/engine/physics/cloth/collider/ClothColliderIndex.d.ts.map +1 -1
  22. package/src/engine/physics/cloth/collider/ClothColliderIndex.js +137 -36
  23. package/src/engine/physics/cloth/collider/cloth_collider_bounds.d.ts +30 -0
  24. package/src/engine/physics/cloth/collider/cloth_collider_bounds.d.ts.map +1 -1
  25. package/src/engine/physics/cloth/collider/cloth_collider_bounds.js +33 -0
  26. package/src/engine/physics/cloth/ecs/ClothColliderFlags.js +2 -1
  27. package/src/engine/physics/cloth/ecs/ClothColliderSystem.d.ts +10 -1
  28. package/src/engine/physics/cloth/ecs/ClothColliderSystem.d.ts.map +1 -1
  29. package/src/engine/physics/cloth/ecs/ClothColliderSystem.js +36 -14
  30. package/src/engine/physics/cloth/ecs/ClothInstance.d.ts +15 -2
  31. package/src/engine/physics/cloth/ecs/ClothInstance.d.ts.map +1 -1
  32. package/src/engine/physics/cloth/ecs/ClothInstance.js +17 -2
  33. package/src/engine/physics/cloth/ecs/ClothWorld.d.ts.map +1 -1
  34. package/src/engine/physics/cloth/ecs/ClothWorld.js +19 -6
  35. package/src/engine/physics/cloth/ecs/cloth_gather_colliders.d.ts +18 -20
  36. package/src/engine/physics/cloth/ecs/cloth_gather_colliders.d.ts.map +1 -1
  37. package/src/engine/physics/cloth/ecs/cloth_gather_colliders.js +139 -89
  38. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  39. package/src/engine/physics/ecs/PhysicsSystem.js +32 -16
  40. package/src/shade/playground/water_refraction/README.md +149 -0
  41. package/src/shade/playground/water_refraction/index.html +42 -0
  42. package/src/shade/playground/water_refraction/main.d.ts +2 -0
  43. package/src/shade/playground/water_refraction/main.d.ts.map +1 -0
  44. package/src/shade/playground/water_refraction/main.js +201 -0
  45. package/src/shade/playground/water_refraction/world.d.ts +37 -0
  46. package/src/shade/playground/water_refraction/world.d.ts.map +1 -0
  47. package/src/shade/playground/water_refraction/world.js +137 -0
  48. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  49. package/src/shade/renderer/Renderer.js +4 -0
  50. package/src/shade/renderer/animation/chunk_pose_accumulator.d.ts +2 -3
  51. package/src/shade/renderer/animation/chunk_pose_accumulator.d.ts.map +1 -1
  52. package/src/shade/renderer/animation/skinning/bounds_refresh/shader_meshlet_bounds_reduce.d.ts.map +1 -1
  53. package/src/shade/renderer/light/environment/chunk_roughness_to_mip_ratio.d.ts +8 -0
  54. package/src/shade/renderer/light/environment/chunk_roughness_to_mip_ratio.d.ts.map +1 -0
  55. package/src/shade/renderer/light/environment/chunk_sphere_probe_roughness_to_lod.d.ts +0 -6
  56. package/src/shade/renderer/light/environment/chunk_sphere_probe_roughness_to_lod.d.ts.map +1 -1
  57. package/src/shade/renderer/particles/bounds/chunk_particle_bounds.d.ts +1 -1
  58. package/src/shade/renderer/particles/bounds/chunk_particle_bounds.d.ts.map +1 -1
  59. package/src/shade/renderer/rasterize/native/avboit/AVBOITSideChannel.d.ts +17 -0
  60. package/src/shade/renderer/rasterize/native/avboit/AVBOITSideChannel.d.ts.map +1 -1
  61. package/src/shade/renderer/rasterize/native/avboit/AVBOITSideChannel.js +7 -0
  62. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_renormalization.d.ts +4 -0
  63. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_renormalization.d.ts.map +1 -0
  64. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_renormalization.js +11 -0
  65. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_rgb_contribution.d.ts +4 -0
  66. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_rgb_contribution.d.ts.map +1 -0
  67. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_rgb_contribution.js +19 -0
  68. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_splat_slice.d.ts.map +1 -1
  69. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_splat_slice.js +5 -3
  70. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_split_counts.d.ts +4 -0
  71. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_split_counts.d.ts.map +1 -0
  72. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_split_counts.js +8 -0
  73. package/src/shade/renderer/rasterize/native/avboit/graph_rasterize_meshes_transparent_avboit.d.ts +1 -1
  74. package/src/shade/renderer/rasterize/native/avboit/graph_rasterize_meshes_transparent_avboit.d.ts.map +1 -1
  75. package/src/shade/renderer/rasterize/native/avboit/graph_rasterize_meshes_transparent_avboit.js +21 -2
  76. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_resolve.d.ts.map +1 -1
  77. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_resolve.js +4 -8
  78. package/src/shade/renderer/scene/shader_bvh_extract_instance_leaves.d.ts.map +1 -1
  79. package/src/shade/renderer/shader/chunk/atomic/chunk_atomic_add_f32_storage.d.ts +12 -0
  80. package/src/shade/renderer/shader/chunk/atomic/chunk_atomic_add_f32_storage.d.ts.map +1 -0
  81. package/src/shade/renderer/shader/chunk/atomic/chunk_atomic_max_f32_storage.d.ts +12 -0
  82. package/src/shade/renderer/shader/chunk/atomic/chunk_atomic_max_f32_storage.d.ts.map +1 -0
  83. package/src/shade/renderer/shader/chunk/atomic/chunk_atomic_min_f32_storage.d.ts +12 -0
  84. package/src/shade/renderer/shader/chunk/atomic/chunk_atomic_min_f32_storage.d.ts.map +1 -0
  85. package/src/shade/renderer/shader/chunk/geometry/plane/{plane_ray_raycast.d.ts → chunk_plane_ray_raycast.d.ts} +2 -2
  86. package/src/shade/renderer/shader/chunk/geometry/plane/chunk_plane_ray_raycast.d.ts.map +1 -0
  87. package/src/shade/renderer/shader/chunk/screen_space/chunk_screen_space_newton_ray.d.ts +4 -0
  88. package/src/shade/renderer/shader/chunk/screen_space/chunk_screen_space_newton_ray.d.ts.map +1 -0
  89. package/src/shade/renderer/shader/chunk/screen_space/chunk_screen_space_newton_ray.js +101 -0
  90. package/src/shade/renderer/water/GPUWaterRenderer.d.ts +62 -29
  91. package/src/shade/renderer/water/GPUWaterRenderer.d.ts.map +1 -1
  92. package/src/shade/renderer/water/GPUWaterRenderer.js +235 -147
  93. package/src/shade/renderer/water/chunk_water_transport.d.ts +5 -0
  94. package/src/shade/renderer/water/chunk_water_transport.d.ts.map +1 -0
  95. package/src/shade/renderer/water/chunk_water_transport.js +94 -0
  96. package/src/shade/renderer/water/shader_water.d.ts +9 -26
  97. package/src/shade/renderer/water/shader_water.d.ts.map +1 -1
  98. package/src/shade/renderer/water/shader_water.js +384 -246
  99. package/src/shade/renderer/water/shader_water_avboit.d.ts +12 -0
  100. package/src/shade/renderer/water/shader_water_avboit.d.ts.map +1 -0
  101. package/src/shade/renderer/water/shader_water_avboit.js +258 -0
  102. package/src/shade/wgsl/emulator/WGSLLanguageCore.d.ts.map +1 -1
  103. package/src/shade/wgsl/emulator/WGSLLanguageCore.js +13 -0
  104. package/src/shade/renderer/animation/skinning/bounds_refresh/chunk_aabb_atomic_f32.d.ts +0 -29
  105. package/src/shade/renderer/animation/skinning/bounds_refresh/chunk_aabb_atomic_f32.d.ts.map +0 -1
  106. package/src/shade/renderer/shader/chunk/geometry/plane/plane_ray_raycast.d.ts.map +0 -1
@@ -1,293 +1,314 @@
1
- import { assert } from "../../core/assert.js";
2
- import Water from "../graphics/ecs/water/Water.js";
3
- import { WATER_SIZE } from "../graphics/ecs/water/WATER_SIZE.js";
4
- import { obtainTerrain } from "../ecs/terrain/util/obtainTerrain.js";
5
- import { System } from "../ecs/System.js";
6
- import { GPUWaterRenderer } from "../../shade/renderer/water/GPUWaterRenderer.js";
7
- import { water_height_texture_from_sampler } from "../../shade/renderer/water/water_height_texture_from_sampler.js";
8
- import { FramePhase } from "../../shade/renderer/extension/FramePhase.js";
9
- import { RenderExtension } from "../../shade/renderer/extension/RenderExtension.js";
10
- import { SceneColor } from "../../shade/renderer/extension/SceneColor.js";
11
- import { ViewTextures } from "../../shade/renderer/extension/ViewTextures.js";
12
-
13
- /**
14
- * Draws the level's water under Shade.
15
- *
16
- * **Water is a transparent pass on top of opaque**, so this system owns an extension rather than putting
17
- * anything in the scene: it registers once at startup against {@link FramePhase.AfterLighting} —
18
- * opaque lit and composited, transparency not yet run — and keeps a list of surfaces the pass draws.
19
- *
20
- * Registration does not wait for a device. `GraphicsEngine` holds extensions and applies them
21
- * to the renderer when one exists and again after a restart (E9, §4.8).
22
- */
23
- export class WaterSystem extends System {
24
- dependencies = [Water];
25
-
26
- /**
27
- * @type {GraphicsEngine}
28
- */
29
- #graphics;
30
-
31
- /**
32
- * @type {GPUWaterRenderer}
33
- */
34
- #renderer = new GPUWaterRenderer();
35
-
36
- /**
37
- * What the pass draws, in link order.
38
- *
39
- * @type {WaterSurface[]}
40
- */
41
- #surfaces = [];
42
-
43
- /**
44
- * @type {Map<number, WaterSurface>}
45
- */
46
- #entries = new Map();
47
-
48
- /**
49
- * @type {RenderExtension|null}
50
- */
51
- #extension = null;
52
-
53
- /**
54
- * Seconds since startup. Waves are a function of it and nothing else advances it.
55
- *
56
- * @type {number}
57
- */
58
- #time = 0;
59
-
60
- /**
61
- * What the pass draws. Read-only — the surfaces follow the entities, and a harness that wants
62
- * one gone takes its component off.
63
- *
64
- * @returns {WaterSurface[]}
65
- */
66
- get surfaces() {
67
- return this.#surfaces;
68
- }
69
-
70
- /**
71
- * @param {GraphicsEngine} graphics
72
- */
73
- constructor(graphics) {
74
- super();
75
-
76
- assert.defined(graphics, 'graphics');
77
-
78
- this.#graphics = graphics;
79
- }
80
-
81
- async startup(entityManager) {
82
- this.entityManager = entityManager;
83
-
84
- this.#extension = this.#graphics.add_extension(new WaterExtension(this));
85
- }
86
-
87
- async shutdown(entityManager) {
88
- if (this.#extension !== null) {
89
- this.#graphics.remove_extension(this.#extension);
90
-
91
- this.#extension = null;
92
- }
93
-
94
- this.#surfaces.length = 0;
95
- this.#entries.clear();
96
- }
97
-
98
- /**
99
- * @param {FrameContext} frame
100
- */
101
- record(frame) {
102
- if (this.#surfaces.length === 0) {
103
- // nothing to draw, and the frame's colour is left exactly as it was
104
- return;
105
- }
106
-
107
- const scene = frame.get(SceneColor);
108
-
109
- scene.color = this.#renderer.graph_draw({
110
- graph: frame.graph,
111
- surfaces: this.#surfaces,
112
- color: scene.color,
113
- depth: frame.get(ViewTextures).depth,
114
- camera: frame.view.camera.buffer
115
- });
116
- }
117
-
118
- /**
119
- * @param {Water} water
120
- * @param {number} entity
121
- */
122
- link(water, entity) {
123
- const surface = {
124
- visible: true,
125
- level: water.level.getValue(),
126
- time: this.#time,
127
- extent: new Float32Array(4),
128
- height_uv: new Float32Array([0, 0, 1, 1]),
129
- color_deep: new Float32Array(3),
130
- color_shore: new Float32Array(3),
131
- wave_amplitude: water.waveAmplitude.getValue(),
132
- wave_speed: water.waveSpeed.getValue(),
133
- wave_frequency: water.waveFrequency.getValue(),
134
- scattering: water.scattering.getValue(),
135
- shore_transition: new Float32Array(2),
136
- height_texture: null,
137
- height_data: null,
138
- height_version: -1,
139
- component: water
140
- };
141
-
142
- this.#entries.set(entity, surface);
143
-
144
- this.#refresh(surface, water);
145
-
146
- if (surface.height_texture !== null) {
147
- this.#surfaces.push(surface);
148
- }
149
- }
150
-
151
- /**
152
- * @param {Water} water
153
- * @param {number} entity
154
- */
155
- unlink(water, entity) {
156
- const surface = this.#entries.get(entity);
157
-
158
- if (surface === undefined) {
159
- return;
160
- }
161
-
162
- const index = this.#surfaces.indexOf(surface);
163
-
164
- if (index !== -1) {
165
- this.#surfaces.splice(index, 1);
166
- }
167
-
168
- this.#entries.delete(entity);
169
- }
170
-
171
- update(timeDelta) {
172
- this.#time += timeDelta;
173
-
174
- for (const surface of this.#entries.values()) {
175
- surface.time = this.#time;
176
-
177
- this.#refresh(surface, surface.component);
178
-
179
- if (surface.height_texture !== null && this.#surfaces.indexOf(surface) === -1) {
180
- // the terrain arrived after the water did, which is ordinary at level-load rates
181
- this.#surfaces.push(surface);
182
- }
183
- }
184
- }
185
-
186
- /**
187
- * Copy the component's authored values across, and work out where the surface sits over the
188
- * terrain it is flooding.
189
- *
190
- * @param {WaterSurface} surface
191
- * @param {Water} water
192
- */
193
- #refresh(surface, water) {
194
- surface.level = water.level.getValue();
195
- surface.wave_amplitude = water.waveAmplitude.getValue();
196
- surface.wave_speed = water.waveSpeed.getValue();
197
- surface.wave_frequency = water.waveFrequency.getValue();
198
- surface.scattering = water.scattering.getValue();
199
-
200
- surface.color_deep[0] = water.color.r;
201
- surface.color_deep[1] = water.color.g;
202
- surface.color_deep[2] = water.color.b;
203
-
204
- surface.color_shore[0] = water.shoreColor.r;
205
- surface.color_shore[1] = water.shoreColor.g;
206
- surface.color_shore[2] = water.shoreColor.b;
207
-
208
- surface.shore_transition[0] = water.shoreDepthTransition.min;
209
- surface.shore_transition[1] = water.shoreDepthTransition.max;
210
-
211
- const terrain = obtainTerrain(this.entityManager.dataset);
212
-
213
- if (terrain === null || terrain === undefined) {
214
- // no terrain to flood; the shore line has nothing to read and the surface stays out of
215
- // the pass until one exists
216
- return;
217
- }
218
-
219
- const width = terrain.size.x * terrain.gridScale;
220
- const height = terrain.size.y * terrain.gridScale;
221
-
222
- /*
223
- The legacy placement, read off it rather than derived: an 800-unit plane centred at
224
- `WATER_SIZE / 4` on both axes, so it spans [-200, 600] regardless of how big the terrain is.
225
- The map is an island in an ocean, not a lake fitted to the map, and where the surface hangs
226
- past the terrain the height map clamps — which is what lets the shore line continue instead
227
- of stopping at the boundary.
228
-
229
- The three path stated the same thing as a UV transform,
230
- `(uv - 0.25) * (WATER_SIZE / terrain_size)`, and the two agree by construction: world
231
- position is `(u - 0.25) * WATER_SIZE`, and terrain uv is that over the terrain's own size.
232
- */
233
- const x0 = -WATER_SIZE * 0.25;
234
- const z0 = -WATER_SIZE * 0.25;
235
-
236
- surface.extent[0] = x0;
237
- surface.extent[1] = z0;
238
- surface.extent[2] = x0 + WATER_SIZE;
239
- surface.extent[3] = z0 + WATER_SIZE;
240
-
241
- surface.height_uv[0] = x0 / width;
242
- surface.height_uv[1] = z0 / height;
243
- surface.height_uv[2] = WATER_SIZE / width;
244
- surface.height_uv[3] = WATER_SIZE / height;
245
-
246
- const height_field = terrain.samplerHeight;
247
-
248
- /*
249
- The height field a water surface links against is not the one it will read. A `Terrain`
250
- carries a 512² placeholder from the moment its entity exists, and the level's own field
251
- arrives later — while Shade uploads a texture once and has no notion of one changing. So a
252
- surface linked at load time would read a flat sea floor for the rest of the session.
253
-
254
- Watched on the data array rather than on the sampler: `Sampler2D.fromJSON` replaces `data`
255
- and leaves `version` alone, so deserialization is invisible to the counter. The version is
256
- watched too, for the edits that do bump it and keep the array.
257
- */
258
- if (surface.height_data !== height_field.data || surface.height_version !== height_field.version) {
259
- surface.height_texture = water_height_texture_from_sampler(height_field);
260
- surface.height_data = height_field.data;
261
- surface.height_version = height_field.version;
262
- }
263
- }
264
- }
265
-
266
- /**
267
- * Water is a transparent surface over the lit opaque scene, so it draws at
268
- * {@link FramePhase.AfterLighting} — opaque lit and composited, transparency not yet run, so what
269
- * transparency draws afterwards blends over the water rather than under it.
270
- */
271
- export class WaterExtension extends RenderExtension {
272
- name = "water";
273
-
274
- phase = FramePhase.AfterLighting;
275
-
276
- /**
277
- * @type {WaterSystem}
278
- */
279
- #system;
280
-
281
- /**
282
- * @param {WaterSystem} system
283
- */
284
- constructor(system) {
285
- super();
286
-
287
- this.#system = system;
288
- }
289
-
290
- record(frame) {
291
- this.#system.record(frame);
292
- }
293
- }
1
+ import { assert } from "../../core/assert.js";
2
+ import Water from "../graphics/ecs/water/Water.js";
3
+ import { WATER_SIZE } from "../graphics/ecs/water/WATER_SIZE.js";
4
+ import { obtainTerrain } from "../ecs/terrain/util/obtainTerrain.js";
5
+ import { System } from "../ecs/System.js";
6
+ import { GPUWaterRenderer } from "../../shade/renderer/water/GPUWaterRenderer.js";
7
+ import { water_height_texture_from_sampler } from "../../shade/renderer/water/water_height_texture_from_sampler.js";
8
+ import { FramePhase } from "../../shade/renderer/extension/FramePhase.js";
9
+ import { RenderExtension } from "../../shade/renderer/extension/RenderExtension.js";
10
+ import { SceneColor } from "../../shade/renderer/extension/SceneColor.js";
11
+
12
+ /**
13
+ * Draws the level's water under Shade.
14
+ *
15
+ * Water is a shared AVBOIT side channel. The extension is debug presentation only; normal frames
16
+ * composite water exactly once alongside particles, meshlets and trails in AVBOIT's resolve.
17
+ *
18
+ * Registration does not wait for a device. `GraphicsEngine` holds extensions and applies them
19
+ * to the renderer when one exists and again after a restart (E9, §4.8).
20
+ */
21
+ export class WaterSystem extends System {
22
+ dependencies = [Water];
23
+
24
+ /**
25
+ * @type {GraphicsEngine}
26
+ */
27
+ #graphics;
28
+
29
+ /**
30
+ * @type {GPUWaterRenderer}
31
+ */
32
+ #renderer = new GPUWaterRenderer();
33
+
34
+ /**
35
+ * What the pass draws, in link order.
36
+ *
37
+ * @type {WaterSurface[]}
38
+ */
39
+ #surfaces = [];
40
+
41
+ /**
42
+ * @type {Map<number, WaterSurface>}
43
+ */
44
+ #entries = new Map();
45
+
46
+ /**
47
+ * @type {RenderExtension|null}
48
+ */
49
+ #extension = null;
50
+
51
+ get settings() { return this.#renderer.settings; }
52
+
53
+ /** The registered channel, for diagnostics such as testing registration order. */
54
+ get transparency_channel() { return this.#renderer; }
55
+
56
+ request_probe(uv) { return this.#renderer.request_probe(uv); }
57
+
58
+ get time() { return this.#time; }
59
+ set time(seconds) {
60
+ assert.ok(Number.isFinite(seconds) && seconds >= 0, 'water time must be finite and non-negative');
61
+ this.#time = seconds;
62
+ this.update(0);
63
+ }
64
+
65
+ /**
66
+ * Seconds since startup. Waves are a function of it and nothing else advances it.
67
+ *
68
+ * @type {number}
69
+ */
70
+ #time = 0;
71
+
72
+ /**
73
+ * What the pass draws. Read-only — the surfaces follow the entities, and a harness that wants
74
+ * one gone takes its component off.
75
+ *
76
+ * @returns {WaterSurface[]}
77
+ */
78
+ get surfaces() {
79
+ return this.#surfaces;
80
+ }
81
+
82
+ /**
83
+ * @param {GraphicsEngine} graphics
84
+ */
85
+ constructor(graphics) {
86
+ super();
87
+
88
+ assert.defined(graphics, 'graphics');
89
+
90
+ this.#graphics = graphics;
91
+ }
92
+
93
+ async startup(entityManager) {
94
+ this.entityManager = entityManager;
95
+
96
+ this.#renderer.surfaces = this.#surfaces;
97
+ this.#graphics.add_transparency_channel(this.#renderer);
98
+ this.#extension = this.#graphics.add_extension(new WaterExtension(this));
99
+ }
100
+
101
+ async shutdown(entityManager) {
102
+ this.#graphics.remove_transparency_channel(this.#renderer);
103
+ this.#renderer.dispose();
104
+ if (this.#extension !== null) {
105
+ this.#graphics.remove_extension(this.#extension);
106
+
107
+ this.#extension = null;
108
+ }
109
+
110
+ this.#surfaces.length = 0;
111
+ this.#entries.clear();
112
+ }
113
+
114
+ /**
115
+ * @param {FrameContext} frame
116
+ */
117
+ record(frame) {
118
+ const inspection = this.#renderer.inspect(frame.graph, frame.view, frame.resolution);
119
+ if (inspection !== undefined) { frame.get(SceneColor).color = inspection; }
120
+ }
121
+
122
+ /**
123
+ * @param {Water} water
124
+ * @param {number} entity
125
+ */
126
+ link(water, entity) {
127
+ const surface = {
128
+ entity,
129
+ visible: true,
130
+ level: water.level.getValue(),
131
+ time: this.#time,
132
+ extent: new Float32Array(4),
133
+ height_uv: new Float32Array([0, 0, 1, 1]),
134
+ color_deep: new Float32Array(3),
135
+ color_shore: new Float32Array(3),
136
+ wave_amplitude: water.waveAmplitude.getValue(),
137
+ wave_speed: water.waveSpeed.getValue(),
138
+ wave_frequency: water.waveFrequency.getValue(),
139
+ scattering: water.scattering.getValue(),
140
+ shore_transition: new Float32Array(2),
141
+ extinction: new Float32Array(3),
142
+ height_texture: null,
143
+ height_data: null,
144
+ height_version: -1,
145
+ component: water
146
+ };
147
+
148
+ this.#entries.set(entity, surface);
149
+
150
+ this.#refresh(surface, water);
151
+
152
+ if (surface.height_texture !== null) {
153
+ this.#surfaces.push(surface);
154
+ }
155
+ }
156
+
157
+ /**
158
+ * @param {Water} water
159
+ * @param {number} entity
160
+ */
161
+ unlink(water, entity) {
162
+ const surface = this.#entries.get(entity);
163
+
164
+ if (surface === undefined) {
165
+ return;
166
+ }
167
+
168
+ const index = this.#surfaces.indexOf(surface);
169
+
170
+ if (index !== -1) {
171
+ this.#surfaces.splice(index, 1);
172
+ }
173
+
174
+ this.#entries.delete(entity);
175
+ }
176
+
177
+ update(timeDelta) {
178
+ this.#time += timeDelta;
179
+
180
+ for (const surface of this.#entries.values()) {
181
+ surface.time = this.#time;
182
+
183
+ this.#refresh(surface, surface.component);
184
+
185
+ if (surface.height_texture !== null && this.#surfaces.indexOf(surface) === -1) {
186
+ // the terrain arrived after the water did, which is ordinary at level-load rates
187
+ this.#surfaces.push(surface);
188
+ } else if (surface.height_texture === null) {
189
+ const index = this.#surfaces.indexOf(surface);
190
+ if (index !== -1) { this.#surfaces.splice(index, 1); }
191
+ }
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Copy the component's authored values across, and work out where the surface sits over the
197
+ * terrain it is flooding.
198
+ *
199
+ * @param {WaterSurface} surface
200
+ * @param {Water} water
201
+ */
202
+ #refresh(surface, water) {
203
+ surface.level = water.level.getValue();
204
+ surface.wave_amplitude = water.waveAmplitude.getValue();
205
+ surface.wave_speed = water.waveSpeed.getValue();
206
+ surface.wave_frequency = water.waveFrequency.getValue();
207
+ surface.scattering = water.scattering.getValue();
208
+ surface.ior = Math.max(1, water.ior.getValue());
209
+ surface.normal_strength = Math.max(0, water.opticalNormalStrength.getValue());
210
+ surface.normal_scale = Math.max(0.01, water.opticalNormalScale.getValue());
211
+ surface.roughness = Math.min(1, Math.max(0, water.reflectionRoughness.getValue()));
212
+ surface.extinction.set([water.extinction.x, water.extinction.y, water.extinction.z]);
213
+
214
+ surface.color_deep[0] = water.color.r;
215
+ surface.color_deep[1] = water.color.g;
216
+ surface.color_deep[2] = water.color.b;
217
+
218
+ surface.color_shore[0] = water.shoreColor.r;
219
+ surface.color_shore[1] = water.shoreColor.g;
220
+ surface.color_shore[2] = water.shoreColor.b;
221
+
222
+ surface.shore_transition[0] = water.shoreDepthTransition.min;
223
+ surface.shore_transition[1] = water.shoreDepthTransition.max;
224
+
225
+ const terrain = obtainTerrain(this.entityManager.dataset);
226
+
227
+ if (terrain === null || terrain === undefined) {
228
+ // no terrain to flood; the shore line has nothing to read and the surface stays out of
229
+ // the pass until one exists
230
+ surface.height_texture = null;
231
+ surface.height_data = null;
232
+ surface.height_version = -1;
233
+ return;
234
+ }
235
+
236
+ const width = terrain.size.x * terrain.gridScale;
237
+ const height = terrain.size.y * terrain.gridScale;
238
+ if (!(width > 0 && height > 0)) {
239
+ surface.height_texture = null; surface.height_data = null; surface.height_version = -1;
240
+ return;
241
+ }
242
+
243
+ /*
244
+ The legacy placement, read off it rather than derived: an 800-unit plane centred at
245
+ `WATER_SIZE / 4` on both axes, so it spans [-200, 600] regardless of how big the terrain is.
246
+ The map is an island in an ocean, not a lake fitted to the map, and where the surface hangs
247
+ past the terrain the height map clamps — which is what lets the shore line continue instead
248
+ of stopping at the boundary.
249
+
250
+ The three path stated the same thing as a UV transform,
251
+ `(uv - 0.25) * (WATER_SIZE / terrain_size)`, and the two agree by construction: world
252
+ position is `(u - 0.25) * WATER_SIZE`, and terrain uv is that over the terrain's own size.
253
+ */
254
+ const x0 = -WATER_SIZE * 0.25;
255
+ const z0 = -WATER_SIZE * 0.25;
256
+
257
+ surface.extent[0] = x0;
258
+ surface.extent[1] = z0;
259
+ surface.extent[2] = x0 + WATER_SIZE;
260
+ surface.extent[3] = z0 + WATER_SIZE;
261
+
262
+ surface.height_uv[0] = x0 / width;
263
+ surface.height_uv[1] = z0 / height;
264
+ surface.height_uv[2] = WATER_SIZE / width;
265
+ surface.height_uv[3] = WATER_SIZE / height;
266
+
267
+ const height_field = terrain.samplerHeight;
268
+
269
+ /*
270
+ The height field a water surface links against is not the one it will read. A `Terrain`
271
+ carries a 512² placeholder from the moment its entity exists, and the level's own field
272
+ arrives later — while Shade uploads a texture once and has no notion of one changing. So a
273
+ surface linked at load time would read a flat sea floor for the rest of the session.
274
+
275
+ Watched on the data array rather than on the sampler: `Sampler2D.fromJSON` replaces `data`
276
+ and leaves `version` alone, so deserialization is invisible to the counter. The version is
277
+ watched too, for the edits that do bump it and keep the array.
278
+ */
279
+ if (surface.height_data !== height_field.data || surface.height_version !== height_field.version) {
280
+ surface.height_texture = water_height_texture_from_sampler(height_field);
281
+ surface.height_data = height_field.data;
282
+ surface.height_version = height_field.version;
283
+ }
284
+ }
285
+ }
286
+
287
+ /**
288
+ * Opt-in water inspection after temporal processing. Kept under the exported name
289
+ * for compatibility; this never records a water color overlay in normal rendering.
290
+ */
291
+ export class WaterExtension extends RenderExtension {
292
+ name = "water";
293
+
294
+ // Diagnostics must show this frame's measurements, not temporally accumulated colors.
295
+ phase = FramePhase.BeforePresent;
296
+
297
+ /**
298
+ * @type {WaterSystem}
299
+ */
300
+ #system;
301
+
302
+ /**
303
+ * @param {WaterSystem} system
304
+ */
305
+ constructor(system) {
306
+ super();
307
+
308
+ this.#system = system;
309
+ }
310
+
311
+ record(frame) {
312
+ this.#system.record(frame);
313
+ }
314
+ }