@woosh/meep-engine 3.36.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.
- package/package.json +1 -1
- package/src/engine/ecs/storage/populateEngineSerializationRegistry.d.ts.map +1 -1
- package/src/engine/ecs/storage/populateEngineSerializationRegistry.js +2 -0
- package/src/engine/ecs/transform/TRANSFORM64_EVENT_PLACED.d.ts +11 -0
- package/src/engine/ecs/transform/TRANSFORM64_EVENT_PLACED.d.ts.map +1 -0
- package/src/engine/ecs/transform/TRANSFORM64_EVENT_PLACED.js +10 -0
- package/src/engine/ecs/transform-attachment/TransformAttachmentSystem.d.ts.map +1 -1
- package/src/engine/ecs/transform-attachment/TransformAttachmentSystem.js +16 -0
- package/src/engine/graphics/ecs/water/Water.d.ts +30 -0
- package/src/engine/graphics/ecs/water/Water.d.ts.map +1 -1
- package/src/engine/graphics/ecs/water/Water.js +125 -104
- package/src/engine/graphics/ecs/water/WaterSerializationAdapter.d.ts.map +1 -1
- package/src/engine/graphics/ecs/water/WaterSerializationAdapter.js +13 -1
- package/src/engine/graphics/ecs/water/WaterSerializationUpgrader_1_2.d.ts +6 -0
- package/src/engine/graphics/ecs/water/WaterSerializationUpgrader_1_2.d.ts.map +1 -0
- package/src/engine/graphics/ecs/water/WaterSerializationUpgrader_1_2.js +18 -0
- package/src/engine/graphics3/WaterSystem.d.ts +22 -6
- package/src/engine/graphics3/WaterSystem.d.ts.map +1 -1
- package/src/engine/graphics3/WaterSystem.js +314 -293
- package/src/engine/input/devices/XRInputDevice.d.ts +51 -23
- package/src/engine/input/devices/XRInputDevice.d.ts.map +1 -1
- package/src/engine/input/devices/XRInputDevice.js +145 -52
- package/src/engine/input/devices/xr/XRGamepadMapping.d.ts +14 -0
- package/src/engine/input/devices/xr/XRGamepadMapping.d.ts.map +1 -0
- package/src/engine/input/devices/xr/XRGamepadMapping.js +9 -0
- package/src/engine/input/devices/xr/XRHandedness.d.ts +15 -0
- package/src/engine/input/devices/xr/XRHandedness.d.ts.map +1 -0
- package/src/engine/input/devices/xr/XRHandedness.js +10 -0
- package/src/engine/input/devices/xr/XRInputAction.d.ts +24 -11
- package/src/engine/input/devices/xr/XRInputAction.d.ts.map +1 -1
- package/src/engine/input/devices/xr/XRInputAction.js +55 -24
- package/src/engine/input/devices/xr/XRInputButton.d.ts +7 -9
- package/src/engine/input/devices/xr/XRInputButton.d.ts.map +1 -1
- package/src/engine/input/devices/xr/XRInputButton.js +18 -8
- package/src/engine/input/devices/xr/XRInputCoordinates.d.ts +11 -3
- package/src/engine/input/devices/xr/XRInputCoordinates.d.ts.map +1 -1
- package/src/engine/input/devices/xr/XRInputCoordinates.js +17 -3
- package/src/engine/input/devices/xr/XRInputHandle.d.ts +41 -11
- package/src/engine/input/devices/xr/XRInputHandle.d.ts.map +1 -1
- package/src/engine/input/devices/xr/XRInputHandle.js +129 -32
- package/src/engine/input/devices/xr/XRInputPose.d.ts +41 -5
- package/src/engine/input/devices/xr/XRInputPose.d.ts.map +1 -1
- package/src/engine/input/devices/xr/XRInputPose.js +57 -16
- package/src/engine/input/devices/xr/XRInputTypes.d.ts +18 -18
- package/src/engine/input/devices/xr/XRInputTypes.js +3 -3
- package/src/engine/input/devices/xr/XRTargetRayMode.d.ts +17 -0
- package/src/engine/input/devices/xr/XRTargetRayMode.d.ts.map +1 -0
- package/src/engine/input/devices/xr/XRTargetRayMode.js +12 -0
- package/src/engine/input/devices/xr/XRVisibilityState.d.ts +15 -0
- package/src/engine/input/devices/xr/XRVisibilityState.d.ts.map +1 -0
- package/src/engine/input/devices/xr/XRVisibilityState.js +10 -0
- package/src/engine/input/ecs/ism/coordinate/InputCoordinateDevice.d.ts +3 -1
- package/src/engine/input/ecs/ism/coordinate/InputCoordinateDevice.d.ts.map +1 -1
- package/src/engine/input/ecs/ism/coordinate/InputCoordinateDevice.js +5 -1
- package/src/engine/input/ecs/ism/device/XRInputDeviceAdapter.d.ts +19 -5
- package/src/engine/input/ecs/ism/device/XRInputDeviceAdapter.d.ts.map +1 -1
- package/src/engine/input/ecs/ism/device/XRInputDeviceAdapter.js +42 -9
- package/src/engine/input/ecs/ism/device/XR_INPUT_DEVICE_IDS.d.ts +8 -2
- package/src/engine/input/ecs/ism/device/XR_INPUT_DEVICE_IDS.d.ts.map +1 -1
- package/src/engine/input/ecs/ism/device/XR_INPUT_DEVICE_IDS.js +19 -9
- package/src/engine/input/ecs/ism/trigger/InputTriggerXRButton.d.ts +10 -2
- package/src/engine/input/ecs/ism/trigger/InputTriggerXRButton.d.ts.map +1 -1
- package/src/engine/input/ecs/ism/trigger/InputTriggerXRButton.js +12 -2
- package/src/engine/physics/cloth/collider/ClothColliderIndex.d.ts +13 -14
- package/src/engine/physics/cloth/collider/ClothColliderIndex.d.ts.map +1 -1
- package/src/engine/physics/cloth/collider/ClothColliderIndex.js +137 -36
- package/src/engine/physics/cloth/collider/cloth_collider_bounds.d.ts +30 -0
- package/src/engine/physics/cloth/collider/cloth_collider_bounds.d.ts.map +1 -1
- package/src/engine/physics/cloth/collider/cloth_collider_bounds.js +33 -0
- package/src/engine/physics/cloth/ecs/ClothColliderFlags.js +2 -1
- package/src/engine/physics/cloth/ecs/ClothColliderSystem.d.ts +10 -1
- package/src/engine/physics/cloth/ecs/ClothColliderSystem.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/ClothColliderSystem.js +36 -14
- package/src/engine/physics/cloth/ecs/ClothInstance.d.ts +15 -2
- package/src/engine/physics/cloth/ecs/ClothInstance.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/ClothInstance.js +17 -2
- package/src/engine/physics/cloth/ecs/ClothWorld.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/ClothWorld.js +19 -6
- package/src/engine/physics/cloth/ecs/cloth_gather_colliders.d.ts +18 -20
- package/src/engine/physics/cloth/ecs/cloth_gather_colliders.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/cloth_gather_colliders.js +139 -89
- package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
- package/src/engine/physics/ecs/PhysicsSystem.js +32 -16
- package/src/shade/playground/water_refraction/README.md +149 -0
- package/src/shade/playground/water_refraction/index.html +42 -0
- package/src/shade/playground/water_refraction/main.d.ts +2 -0
- package/src/shade/playground/water_refraction/main.d.ts.map +1 -0
- package/src/shade/playground/water_refraction/main.js +201 -0
- package/src/shade/playground/water_refraction/world.d.ts +37 -0
- package/src/shade/playground/water_refraction/world.d.ts.map +1 -0
- package/src/shade/playground/water_refraction/world.js +137 -0
- package/src/shade/renderer/Renderer.d.ts.map +1 -1
- package/src/shade/renderer/Renderer.js +4 -0
- package/src/shade/renderer/animation/chunk_pose_accumulator.d.ts +2 -3
- package/src/shade/renderer/animation/chunk_pose_accumulator.d.ts.map +1 -1
- package/src/shade/renderer/animation/chunk_pose_accumulator.js +14 -24
- package/src/shade/renderer/animation/skinning/bounds_refresh/shader_meshlet_bounds_reduce.d.ts.map +1 -1
- package/src/shade/renderer/animation/skinning/bounds_refresh/shader_meshlet_bounds_reduce.js +16 -15
- package/src/shade/renderer/deferred/indirect/chunk_lpv_sample_light.js +1 -1
- package/src/shade/renderer/gpu_primitive/bvh/GPU_BVH_BUILD_PLAN.md +1123 -1122
- package/src/shade/renderer/gpu_primitive/bvh/PHASE1_HANDOVER.md +202 -201
- package/src/shade/renderer/light/environment/chunk_roughness_to_mip_ratio.d.ts +8 -0
- package/src/shade/renderer/light/environment/chunk_roughness_to_mip_ratio.d.ts.map +1 -0
- package/src/shade/renderer/light/environment/chunk_roughness_to_mip_ratio.js +24 -0
- package/src/shade/renderer/light/environment/chunk_sphere_probe_roughness_to_lod.d.ts +0 -6
- package/src/shade/renderer/light/environment/chunk_sphere_probe_roughness_to_lod.d.ts.map +1 -1
- package/src/shade/renderer/light/environment/chunk_sphere_probe_roughness_to_lod.js +2 -25
- package/src/shade/renderer/light/environment/shader_project_environment_sh.d.ts +2 -2
- package/src/shade/renderer/light/environment/shader_project_environment_sh.d.ts.map +1 -1
- package/src/shade/renderer/light/environment/shader_project_environment_sh.js +50 -19
- package/src/shade/renderer/particles/DESIGN.md +1 -1
- package/src/shade/renderer/particles/bounds/chunk_particle_bounds.d.ts +1 -1
- package/src/shade/renderer/particles/bounds/chunk_particle_bounds.d.ts.map +1 -1
- package/src/shade/renderer/particles/bounds/chunk_particle_bounds.js +10 -9
- package/src/shade/renderer/rasterize/native/avboit/AVBOITSideChannel.d.ts +17 -0
- package/src/shade/renderer/rasterize/native/avboit/AVBOITSideChannel.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/native/avboit/AVBOITSideChannel.js +7 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_renormalization.d.ts +4 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_renormalization.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_renormalization.js +11 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_rgb_contribution.d.ts +4 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_rgb_contribution.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_rgb_contribution.js +19 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_splat_slice.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_splat_slice.js +5 -3
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_split_counts.d.ts +4 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_split_counts.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_split_counts.js +8 -0
- package/src/shade/renderer/rasterize/native/avboit/graph_rasterize_meshes_transparent_avboit.d.ts +1 -1
- package/src/shade/renderer/rasterize/native/avboit/graph_rasterize_meshes_transparent_avboit.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/native/avboit/graph_rasterize_meshes_transparent_avboit.js +21 -2
- package/src/shade/renderer/rasterize/native/avboit/shader_avboit_resolve.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/native/avboit/shader_avboit_resolve.js +4 -8
- package/src/shade/renderer/scene/shader_bvh_extract_instance_leaves.d.ts.map +1 -1
- package/src/shade/renderer/scene/shader_bvh_extract_instance_leaves.js +10 -8
- package/src/shade/renderer/shader/chunk/atomic/chunk_atomic_add_f32_storage.d.ts +12 -0
- package/src/shade/renderer/shader/chunk/atomic/chunk_atomic_add_f32_storage.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/atomic/chunk_atomic_add_f32_storage.js +25 -0
- package/src/shade/renderer/shader/chunk/atomic/chunk_atomic_max_f32_storage.d.ts +12 -0
- package/src/shade/renderer/shader/chunk/atomic/chunk_atomic_max_f32_storage.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/atomic/chunk_atomic_max_f32_storage.js +28 -0
- package/src/shade/renderer/shader/chunk/atomic/chunk_atomic_min_f32_storage.d.ts +12 -0
- package/src/shade/renderer/shader/chunk/atomic/chunk_atomic_min_f32_storage.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/atomic/chunk_atomic_min_f32_storage.js +28 -0
- package/src/shade/renderer/shader/chunk/geometry/plane/{plane_ray_raycast.d.ts → chunk_plane_ray_raycast.d.ts} +2 -2
- package/src/shade/renderer/shader/chunk/geometry/plane/chunk_plane_ray_raycast.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/geometry/plane/{plane_ray_raycast.js → chunk_plane_ray_raycast.js} +1 -1
- package/src/shade/renderer/shader/chunk/screen_space/chunk_screen_space_newton_ray.d.ts +4 -0
- package/src/shade/renderer/shader/chunk/screen_space/chunk_screen_space_newton_ray.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/screen_space/chunk_screen_space_newton_ray.js +101 -0
- package/src/shade/renderer/shadow/map/csm/shader/shader_csm_caster_range.js +3 -3
- package/src/shade/renderer/water/GPUWaterRenderer.d.ts +62 -29
- package/src/shade/renderer/water/GPUWaterRenderer.d.ts.map +1 -1
- package/src/shade/renderer/water/GPUWaterRenderer.js +235 -147
- package/src/shade/renderer/water/chunk_water_transport.d.ts +5 -0
- package/src/shade/renderer/water/chunk_water_transport.d.ts.map +1 -0
- package/src/shade/renderer/water/chunk_water_transport.js +94 -0
- package/src/shade/renderer/water/shader_water.d.ts +9 -26
- package/src/shade/renderer/water/shader_water.d.ts.map +1 -1
- package/src/shade/renderer/water/shader_water.js +384 -246
- package/src/shade/renderer/water/shader_water_avboit.d.ts +12 -0
- package/src/shade/renderer/water/shader_water_avboit.d.ts.map +1 -0
- package/src/shade/renderer/water/shader_water_avboit.js +258 -0
- package/src/shade/wgsl/emulator/WGSLLanguageCore.d.ts.map +1 -1
- package/src/shade/wgsl/emulator/WGSLLanguageCore.js +13 -0
- package/src/shade/renderer/animation/skinning/bounds_refresh/chunk_aabb_atomic_f32.d.ts +0 -29
- package/src/shade/renderer/animation/skinning/bounds_refresh/chunk_aabb_atomic_f32.d.ts.map +0 -1
- package/src/shade/renderer/animation/skinning/bounds_refresh/chunk_aabb_atomic_f32.js +0 -59
- package/src/shade/renderer/shader/chunk/geometry/plane/plane_ray_raycast.d.ts.map +0 -1
|
@@ -11,6 +11,7 @@ import { ResourceAccessKind } from "../../../core/model/ResourceAccessKind.js";
|
|
|
11
11
|
import { ResourceAccessSpecification } from "../../../core/model/ResourceAccessSpecification.js";
|
|
12
12
|
import { System } from "../../ecs/System.js";
|
|
13
13
|
import { t64_announce_change } from "../../ecs/transform/t64_announce_change.js";
|
|
14
|
+
import { TRANSFORM64_EVENT_PLACED } from "../../ecs/transform/TRANSFORM64_EVENT_PLACED.js";
|
|
14
15
|
import { Transform64 } from "../../ecs/transform/Transform64.js";
|
|
15
16
|
import { Interpolated } from "../../interpolation/Interpolated.js";
|
|
16
17
|
import { body_id_index, BodyStorage } from "../body/BodyStorage.js";
|
|
@@ -1168,15 +1169,40 @@ export class PhysicsSystem extends System {
|
|
|
1168
1169
|
assert.isNumber(rotation[2], 'rotation[2]');
|
|
1169
1170
|
assert.isNumber(rotation[3], 'rotation[3]');
|
|
1170
1171
|
|
|
1171
|
-
const
|
|
1172
|
+
const body_id = rigidBody._bodyId;
|
|
1173
|
+
if (!this.storage.is_valid(body_id)) return;
|
|
1174
|
+
|
|
1175
|
+
const idx = body_id_index(body_id);
|
|
1172
1176
|
const transform = this.__transforms[idx];
|
|
1173
|
-
if (transform === undefined) {
|
|
1177
|
+
if (transform === undefined || this.__bodies[idx] !== rigidBody) {
|
|
1174
1178
|
return;
|
|
1175
1179
|
}
|
|
1176
1180
|
|
|
1177
1181
|
transform.setTranslation(position[0], position[1], position[2]);
|
|
1178
1182
|
transform.setRotation(rotation[0], rotation[1], rotation[2], rotation[3]);
|
|
1179
1183
|
|
|
1184
|
+
const dataset = this.entityManager?.dataset;
|
|
1185
|
+
const entity = this.storage.entity_at(idx);
|
|
1186
|
+
if (dataset !== null && dataset !== undefined) {
|
|
1187
|
+
// Snap interpolation before publishing the pose. Compose attached
|
|
1188
|
+
// worlds before re-homing any of the body's collider leaves below.
|
|
1189
|
+
const interpolated = dataset.getComponent(entity, Interpolated);
|
|
1190
|
+
if (interpolated !== undefined && interpolated !== null) {
|
|
1191
|
+
interpolated.snap = true;
|
|
1192
|
+
}
|
|
1193
|
+
transform.updateMatrix();
|
|
1194
|
+
t64_announce_change(dataset, entity);
|
|
1195
|
+
|
|
1196
|
+
// Listeners can destroy/relink this body and immediately reuse its
|
|
1197
|
+
// slot or entity id. Neither its replacement's leaves nor its
|
|
1198
|
+
// placement listeners belong to this call. Check the generation
|
|
1199
|
+
// as well as the references: a listener can relink the same objects.
|
|
1200
|
+
if (rigidBody._bodyId !== body_id || !this.storage.is_valid(body_id)
|
|
1201
|
+
|| this.__bodies[idx] !== rigidBody || this.__transforms[idx] !== transform) {
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1180
1206
|
if (rigidBody.kind !== BodyKind.Dynamic) {
|
|
1181
1207
|
// Static AND Kinematic: the body itself never sleeps and __wake_body is a no-op for it,
|
|
1182
1208
|
// so teleporting a support must instead wake what rested on it (old pose) and what it
|
|
@@ -1203,20 +1229,10 @@ export class PhysicsSystem extends System {
|
|
|
1203
1229
|
this.__wake_body(rigidBody);
|
|
1204
1230
|
}
|
|
1205
1231
|
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
const em = this.entityManager;
|
|
1211
|
-
if (em !== null && em !== undefined) {
|
|
1212
|
-
const dataset = em.dataset;
|
|
1213
|
-
if (dataset !== null && dataset !== undefined) {
|
|
1214
|
-
const entity = this.storage.entity_at(idx);
|
|
1215
|
-
const interpolated = dataset.getComponent(entity, Interpolated);
|
|
1216
|
-
if (interpolated !== undefined && interpolated !== null) {
|
|
1217
|
-
interpolated.snap = true;
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1232
|
+
if (dataset !== null && dataset !== undefined) {
|
|
1233
|
+
// Motion consumers reset history at this pose; subsequent ordinary
|
|
1234
|
+
// movement is still a sweep. Attachments forward the placement.
|
|
1235
|
+
dataset.sendEvent(entity, TRANSFORM64_EVENT_PLACED);
|
|
1220
1236
|
}
|
|
1221
1237
|
}
|
|
1222
1238
|
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# Water refraction / AVBOIT playground
|
|
2
|
+
|
|
3
|
+
Run `npm run dev --workspace @woosh/meep-engine` from the repository root and open
|
|
4
|
+
`http://localhost:5173/src/shade/playground/water_refraction/index.html`.
|
|
5
|
+
The page needs the renderer's usual WebGPU features, including subgroups and immediate data.
|
|
6
|
+
It generates its fixtures and sprites locally; no external scene download is required.
|
|
7
|
+
|
|
8
|
+
This uses `EntityManager`, the production `WaterSystem`, `ShadedGeometrySystem`, `LightSystem`,
|
|
9
|
+
and `GPUParticleEmitterSystem`. The ribbon uses `DynamicMeshAVBOITChannel`. The terrain component
|
|
10
|
+
holds the real CPU height field; a normal opaque meshlet floor visualizes the same field.
|
|
11
|
+
Terrain tile streaming is deliberately outside this fixture. Water records through the production
|
|
12
|
+
shared occupancy, warp, extinction volume, integral, four accumulators and single AVBOIT resolve.
|
|
13
|
+
There is no playground water renderer or color overlay.
|
|
14
|
+
|
|
15
|
+
## Controls and review
|
|
16
|
+
|
|
17
|
+
Drag to orbit, right-drag to pan, wheel to zoom. Camera input is confined to the viewport.
|
|
18
|
+
Pause freezes waves, particles and fixture motion; the camera remains interactive. Reset also replaces
|
|
19
|
+
the particle components through the ECS lifecycle to start fresh emitter populations. Step advances
|
|
20
|
+
one 1/60-second engine tick. Presets cover the shore, normal incidence, grazing views, thin posts,
|
|
21
|
+
screen edges, the emergent-object/shoreline discontinuity view, and the deliberately unsupported
|
|
22
|
+
below-interface view. The grazing camera stays over the submerged floor.
|
|
23
|
+
|
|
24
|
+
Water fields edit the actual component. Optical normals are independent of the legacy shoreline
|
|
25
|
+
wave modulation. Screen reflections can be disabled while retaining the environment fallback.
|
|
26
|
+
The Newton, bounded HZB traversal, and undisplaced control modes all use the same AVBOIT path.
|
|
27
|
+
An IOR of 1, zero RGB extinction and zero optical normal strength should leave opaque transport
|
|
28
|
+
unchanged. Increase extinction to see independent color attenuation and the body-color source.
|
|
29
|
+
|
|
30
|
+
Move the card, alpha/additive particles and ribbon above/below the interface using Layer height.
|
|
31
|
+
The box and five thin posts exercise opaque occlusion and discontinuities. Swap channel order
|
|
32
|
+
checks registration independence. Overlap water verifies deterministic nearest-interface selection;
|
|
33
|
+
it does **not** enable transport through multiple interfaces. Replace terrain exercises height-field
|
|
34
|
+
replacement. Restart device exercises retained facade registration and new GPU resource ownership.
|
|
35
|
+
|
|
36
|
+
Water enabled off elides all water preparation/contribution passes. MBOIT suppresses water and
|
|
37
|
+
the other AVBOIT side channels. There is no legacy-overlay fallback. TAA can be enabled for moving
|
|
38
|
+
wave/object checks. NSS uses the renderer's existing weight requirement and falls back to TAA
|
|
39
|
+
when weights have not been installed; this standalone page does not supply NSS weights.
|
|
40
|
+
|
|
41
|
+
Inspection modes display prepared coverage, receiver UV, bounded path, classification/update count,
|
|
42
|
+
surface radiance, residual, RGB throughput, shared front throughput, water's normalization term,
|
|
43
|
+
and the confidence used to select the refraction lookup.
|
|
44
|
+
They replace presentation **after** shared resolve and temporal processing, so diagnostics show
|
|
45
|
+
the current frame's measurements without TAA history. Internal-resolution measurements are
|
|
46
|
+
displayed with nearest sampling at the output resolution.
|
|
47
|
+
Shift-click requests one pixel from four prepared textures on the next frame; normal frames perform
|
|
48
|
+
no readback. Arrays are `receiver=[u,v,path,status+updates/32]`,
|
|
49
|
+
`transmission=[Tr,Tg,Tb,coverage]`, `radiance=[Wr,Wg,Wb,residual]`.
|
|
50
|
+
The separate `confidence` field is the final weight after rejecting unsupported lookups; the
|
|
51
|
+
confidence display reads this same weight rather than recomputing an approximation.
|
|
52
|
+
Status 0 is a Newton-assisted, visibility-checked hit; codes 8–13 are validated HZB hits, 16–21 undisplaced opaque fallbacks,
|
|
53
|
+
24–29 bounded sky fallbacks, and 31 the below-interface control. The low three bits preserve the
|
|
54
|
+
Newton miss reason: 0 none/not attempted, 1 bounds/clip, 2 sky, 3 parallel/invalid, 4 ray interval,
|
|
55
|
+
5 exhausted budget/residual. The probe decodes these names and the update count. Residual is capped
|
|
56
|
+
at 65504 for half-float storage. The status display uses green/cyan/purple/orange/pink for those
|
|
57
|
+
five final classes; red increases with update count.
|
|
58
|
+
|
|
59
|
+
Compile check compiles all ten production water modules on the active device. Rendering exercises
|
|
60
|
+
their real pipelines and bind groups; the first validation error stops rendering and remains visible
|
|
61
|
+
in the status panel and console. Restart device resumes after correcting the error. Profile / T
|
|
62
|
+
uses the existing GPU profiler and exports `.sgpt`. Compare water on/off at a fixed paused camera,
|
|
63
|
+
resolution and transparency population; include preparation, background transport and all AVBOIT
|
|
64
|
+
passes, rather than quoting just the Newton loop. The recording includes trace budgets and temporal
|
|
65
|
+
mode in its note. A device without timestamp-query records structure without timings.
|
|
66
|
+
|
|
67
|
+
Copy state writes versioned JSON into the text area and attempts clipboard copying. Apply state
|
|
68
|
+
restores camera, displayed controls, water/fixture clock, terrain offset and channel order.
|
|
69
|
+
GPU particle populations and temporal histories are not serialized: reload/reset them and warm up
|
|
70
|
+
equally for image/performance comparisons. Probe values describe the captured frame; camera changes
|
|
71
|
+
require a fresh probe.
|
|
72
|
+
|
|
73
|
+
## Integration boundaries and adversarial checks
|
|
74
|
+
|
|
75
|
+
Opaque receiver transport uses the immutable pre-transparency HDR scene. Water contributes only its
|
|
76
|
+
surface source and RGB extinction; shared resolve applies the transported opaque background once.
|
|
77
|
+
Rear transparent surfaces/particles are attenuated at their original screen positions. Their radiance
|
|
78
|
+
is not sampled through the bent ray. Missing/off-screen receivers get bounded, explicit fallbacks.
|
|
79
|
+
Missing receivers use the local terrain column to estimate water thickness; the trace budget is only
|
|
80
|
+
an upper bound. Final shading fades the receiver UV toward the control UV around emergent silhouettes,
|
|
81
|
+
shoreline visibility gaps and screen edges, then performs one geometry-aware color lookup. It derives
|
|
82
|
+
water thickness from that lookup's local receiver plane and builds its complete optical event. It
|
|
83
|
+
does not crossfade two displaced images. Three quarter-resolution passes form a conservative
|
|
84
|
+
geometry/sky boundary mask and bounded Euclidean distance field. Isolated trace failures never seed
|
|
85
|
+
that field, which would amplify them into large moving blotches. The raw receiver/residual displays
|
|
86
|
+
still show classified misses; Refraction confidence shows the displacement fade actually used by
|
|
87
|
+
the final image. Screen reflections also fade near the ray origin and frame
|
|
88
|
+
edges to avoid self-hit speckles, reusing the existing SSR back-face/thickness/vignette validation.
|
|
89
|
+
Displaced Newton roots also require first-intersection evidence from a water adapter using the
|
|
90
|
+
existing FFX cell intersection/depth-loading chunks. The adapter traverses the HZB's actual integer
|
|
91
|
+
mip dimensions, including odd viewport sizes; it does not assume a floating halving of the source
|
|
92
|
+
grid. It starts in the source pixel, since water is absent from opaque depth. HZB slab crossings
|
|
93
|
+
are intersected once with the selected receiver plane and validated again before shading. A second
|
|
94
|
+
Newton search here could jump to a farther root and falsely certify it as the first hit. Analytic ray distance sets the visibility
|
|
95
|
+
interval instead of the biased texel-center projection. An exhausted traversal uses the control
|
|
96
|
+
event. Color filtering accepts only taps on the validated
|
|
97
|
+
receiver's local plane, with geometric-normal and interface-side checks. Receiver selection retains
|
|
98
|
+
full-precision UV/path/control-path data. Final shading reads the same selected interface and
|
|
99
|
+
deterministic surface owner before shared occupancy/extinction/draw consume its optical result.
|
|
100
|
+
AVBOIT still supplies the sole transparency resolve.
|
|
101
|
+
Underwater transport, two-interface transport, transparent receivers and caustics remain the plan's
|
|
102
|
+
follow-on stage, rather than being implied by the below-interface/overlap controls.
|
|
103
|
+
|
|
104
|
+
The tangent-plane update follows the paper's Appendix A, but geometry reconstruction differs:
|
|
105
|
+
Newton reads the unfiltered depth and geometric face normal at mip zero, rather than the paper's
|
|
106
|
+
distance-dependent mip filtering for local smoothness. The conservative HZB is a visibility
|
|
107
|
+
traversal, not that filtered reconstruction. Nearest face planes avoid mixing foreground and
|
|
108
|
+
background geometry at silhouettes; the tradeoff is weaker convergence on curved or undersampled
|
|
109
|
+
surfaces. Bounded fallback and classified misses remain necessary. Neither the paper's convergence
|
|
110
|
+
statistics nor its timing results apply directly to this implementation.
|
|
111
|
+
|
|
112
|
+
Acceptance reconstructs the receiver at its subpixel UV on the sampled face plane before measuring
|
|
113
|
+
ray proximity. Using the texel-center position instead can reject exact planar intersections at
|
|
114
|
+
grazing angles and introduce striped hit/control transitions. This is independent of adaptive mip
|
|
115
|
+
filtering. First-hit traversal still has a finite budget: inspect trace status when increasing the
|
|
116
|
+
fallback steps, since an exhausted visibility check deliberately selects the control event.
|
|
117
|
+
|
|
118
|
+
The main risks to check on target hardware are moving-wave temporal ghosts (water keeps the opaque
|
|
119
|
+
depth/motion contract), coarse 8×8 volume attenuation near thin/partial edges, tangent-plane hits at
|
|
120
|
+
depth discontinuities, and first activation of AVBOIT in otherwise opaque scenes. No paper performance
|
|
121
|
+
claim is adopted; visibility checks and geometry-aware filtering add work to water rendering.
|
|
122
|
+
Water transient targets cost approximately 68 bytes per internal pixel before allocator lifetime
|
|
123
|
+
reuse, plus up to 0.375 bytes per pixel for the three quarter-resolution visibility fields:
|
|
124
|
+
32 bytes for receiver/trace metadata, 4 for interface depth, and 32 for final optics/background. The tiny-loss
|
|
125
|
+
throughput target is float32 and is read through unfilterable loads, so float32-filterable is unnecessary.
|
|
126
|
+
|
|
127
|
+
The checker fixture uses 64 texels per square, preserving its world-space pattern while keeping
|
|
128
|
+
the opaque input sharp. Compare water disabled before attributing source-image filtering to water.
|
|
129
|
+
|
|
130
|
+
Numerical regressions execute production receiver/final-shading entries against analytic optical
|
|
131
|
+
transport, and water's volume splat through shared integration at full, partial and odd-edge coverage.
|
|
132
|
+
They also cover the actual shared WGSL contribution/resolve, count conservation,
|
|
133
|
+
normal-incidence acceptance, analytic plane/sphere intersections, invalid rays, bounded fallback thickness,
|
|
134
|
+
geometry-only visibility seeds, single-lookup transport, subpixel receiver thickness,
|
|
135
|
+
grazing visibility intervals, odd-dimension traversal crossings, receiver ownership at silhouettes,
|
|
136
|
+
first-receiver selection, source/receiver frame-edge fades and old/new serialization.
|
|
137
|
+
Software-device orchestration verifies water precedes shared warp/integration/resolve without adding
|
|
138
|
+
another one. Visual checks are complementary: inspect TAA/fog, mixed sources, below-interface fallback,
|
|
139
|
+
overlap, no-water/MBOIT suppression, terrain replacement and device restart on a real GPU.
|
|
140
|
+
The CPU emulator stores one mip and cannot propagate scalar pointer outputs back to callers;
|
|
141
|
+
its traversal regression checks the returned crossing. Full mip-chain hit classification needs
|
|
142
|
+
the real GPU; the CPU test does not claim to prove that contract.
|
|
143
|
+
|
|
144
|
+
Blast radius: water lifecycle/shaders/data plus AVBOIT preparation/background plumbing and its
|
|
145
|
+
tiny-loss resolve guard; existing particle, mesh and ribbon APIs remain intact.
|
|
146
|
+
|
|
147
|
+
Reference: Mayer, Assarsson and Sintorn, *Ultrafast Screen-Space Refractions and Caustics via Newton's
|
|
148
|
+
Method*, JCGT 15(1), 2026, [paper](https://jcgt.org/published/0015/01/03/paper-lowres.pdf).
|
|
149
|
+
See [the integration plan](../../../../WATER_MAYER2026_INTEGRATION_PLAN.md) for the transport contract.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Water · Mayer refraction / AVBOIT</title>
|
|
4
|
+
<style>
|
|
5
|
+
:root{color-scheme:dark;font:14px/1.45 system-ui,sans-serif;color:#d6e4ea;background:#10161b}*{box-sizing:border-box}
|
|
6
|
+
body{margin:0;display:grid;grid-template-columns:minmax(0,1fr) 330px;grid-template-rows:minmax(0,1fr);height:100vh;overflow:hidden}
|
|
7
|
+
#viewport{min-width:0;min-height:0;overflow:hidden;position:relative;outline:none}#viewport canvas{width:100%;height:100%}
|
|
8
|
+
aside{padding:22px;overflow:auto;border-left:1px solid #2d3a42;background:#151e25}h1{font-size:21px;margin:0 0 6px}h2{font-size:13px;color:#8cb9bf;text-transform:uppercase;letter-spacing:.07em;margin:23px 0 9px}
|
|
9
|
+
p{margin:8px 0;color:#9faeb9;font-size:12px}label{display:flex;align-items:center;justify-content:space-between;gap:8px;margin:8px 0}
|
|
10
|
+
input[type=range]{width:135px}input[type=number]{width:84px}input,select,button,textarea{font:inherit;background:#202d36;border:1px solid #3c505e;border-radius:5px;color:#e2edf0;padding:5px}select{max-width:180px}button{cursor:pointer}button:hover{background:#304753}button:disabled{opacity:.4;cursor:default}.row{display:flex;gap:6px;flex-wrap:wrap}textarea{width:100%;height:92px;font:11px/1.4 monospace;resize:vertical}pre{white-space:pre-wrap;overflow-wrap:anywhere;font:11px/1.5 monospace;color:#8bd3d5;background:#0c151b;padding:10px;border-radius:5px}#status{border-left:3px solid #70b8b5;padding:10px;font-size:12px;background:#1d2c32}#badge{position:absolute;top:18px;left:18px;pointer-events:none;background:#13242cd9;padding:9px 13px;border-radius:5px;color:#c7eff0;font-size:12px}a{color:#8dd3db}@media(max-width:760px){body{grid-template-columns:minmax(0,1fr) 275px}aside{padding:14px}input[type=range]{width:95px}}
|
|
11
|
+
</style></head>
|
|
12
|
+
<body tabindex="0"><div id="viewport" tabindex="0"><div id="badge">Drag to orbit · wheel to zoom · Shift-click to inspect a pixel</div></div>
|
|
13
|
+
<aside><h1>Water / refraction</h1><p>Mayer 2026 · production ECS + shared AVBOIT</p><div id="status">Starting renderer…</div>
|
|
14
|
+
<h2>View</h2><label>Preset<select id="preset"><option value="shore">Shore</option><option value="normal">Normal incidence</option><option value="grazing">Grazing / horizon</option><option value="posts">Thin posts</option><option value="edge">Screen edge</option><option value="discontinuities">Objects / shoreline</option><option value="underwater">Below interface</option></select></label>
|
|
15
|
+
<div class="row"><button id="pause">Pause</button><button id="step">Step</button><button id="reset">Reset</button></div>
|
|
16
|
+
<h2>Water</h2><label>Enabled<input id="enabled" type="checkbox" checked></label>
|
|
17
|
+
<label>Level<input id="level" type="range" min="-1.5" max="2" step=".05" value="0"></label>
|
|
18
|
+
<label>IOR<input id="ior" type="range" min="1" max="1.6" step=".01" value="1.33"></label>
|
|
19
|
+
<label>Optical normal<input id="normal" type="range" min="0" max=".5" step=".01" value=".08"></label>
|
|
20
|
+
<label>Normal scale<input id="scale" type="range" min=".2" max="8" step=".1" value="3"></label>
|
|
21
|
+
<label>Roughness<input id="roughness" type="range" min="0" max="1" step=".01" value=".08"></label>
|
|
22
|
+
<label>Extinction R<input id="extinction_r" type="number" min="0" max="2" step=".01" value=".12"></label>
|
|
23
|
+
<label>Extinction G<input id="extinction_g" type="number" min="0" max="2" step=".01" value=".035"></label>
|
|
24
|
+
<label>Extinction B<input id="extinction_b" type="number" min="0" max="2" step=".01" value=".02"></label>
|
|
25
|
+
<label>Body color<input id="color" type="color" value="#004d80"></label><label>Shore color<input id="shore_color" type="color" value="#95cad9"></label>
|
|
26
|
+
<label>Screen reflections<input id="reflection" type="checkbox" checked></label>
|
|
27
|
+
<h2>Mixed transparency</h2><label>Layer height<input id="layer_height" type="range" min="-2" max="3" step=".05" value="0"></label>
|
|
28
|
+
<label>Card opacity<input id="opacity" type="range" min=".02" max=".98" step=".02" value=".5"></label>
|
|
29
|
+
<label>Alpha + additive particles<input id="particles" type="checkbox" checked></label>
|
|
30
|
+
<label>Ribbon<input id="ribbon" type="checkbox" checked></label><label>Move submerged object<input id="animate" type="checkbox"></label>
|
|
31
|
+
<label>Overlap water<input id="overlap" type="checkbox"></label><div class="row"><button id="order">Swap channel order</button><button id="terrain">Replace terrain</button><button id="restart">Restart device</button></div>
|
|
32
|
+
<h2>Tracing / frame</h2><label>Trace<select id="mode"><option value="0">Newton + fallback</option><option value="1">Bounded traversal</option><option value="2">Undisplaced control</option></select></label>
|
|
33
|
+
<label>Newton updates<input id="iterations" type="number" min="0" max="16" value="4"></label><label>Fallback steps<input id="fallback" type="number" min="0" max="128" value="48"></label>
|
|
34
|
+
<label>Temporal<select id="temporal"><option value="off">Off</option><option value="taa">TAA</option><option value="nss">NSS (requires weights)</option></select></label>
|
|
35
|
+
<label>Internal scale<select id="resolution"><option value="1">1×</option><option value=".75">0.75×</option><option value=".5">0.5×</option></select></label>
|
|
36
|
+
<label>Opaque SSR<input id="ssr" type="checkbox"></label><label>Atmospheric fog<input id="fog" type="checkbox"></label>
|
|
37
|
+
<label>Transparency<select id="transparency"><option value="avboit">AVBOIT</option><option value="mboit">MBOIT (water unavailable)</option></select></label>
|
|
38
|
+
<h2>Inspect</h2><label>Display<select id="debug"><option value="0">Final composite</option><option value="1">Coverage</option><option value="2">Receiver UV</option><option value="3">Water path</option><option value="4">Trace status / updates</option><option value="5">Surface radiance</option><option value="6">Residual</option><option value="7">RGB throughput</option><option value="8">Shared front throughput</option><option value="9">Water normalization</option><option value="10">Refraction confidence</option></select></label>
|
|
39
|
+
<div class="row"><button id="compile">Compile check</button><button id="profile">Profile (T)</button></div><pre id="probe">Shift-click water to read a pixel.</pre>
|
|
40
|
+
<h2>Reproduce a view</h2><div class="row"><button id="copy">Copy state</button><button id="apply">Apply state</button></div><textarea id="state" aria-label="Saved camera and settings"></textarea>
|
|
41
|
+
<p>Rear transparent radiance is attenuated on its original screen path. Initial optics select one water interface; underwater transport and caustics are follow-on work. <a href="./README.md">Review guide</a></p>
|
|
42
|
+
</aside><script type="module" src="./main.js"></script></body></html>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../../../../src/shade/playground/water_refraction/main.js"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import ConcurrentExecutor from "../../../core/process/executor/ConcurrentExecutor.js";
|
|
2
|
+
import Vector3 from "../../../core/geom/Vector3.js";
|
|
3
|
+
import Quaternion from "../../../core/geom/Quaternion.js";
|
|
4
|
+
import { AssetManager } from "../../../engine/asset/AssetManager.js";
|
|
5
|
+
import { GameAssetType } from "../../../engine/asset/GameAssetType.js";
|
|
6
|
+
import { ImageRGBADataLoader } from "../../../engine/asset/loaders/image/ImageRGBADataLoader.js";
|
|
7
|
+
import { EntityComponentDataset } from "../../../engine/ecs/EntityComponentDataset.js";
|
|
8
|
+
import { EntityManager } from "../../../engine/ecs/EntityManager.js";
|
|
9
|
+
import { Entity } from "../../../engine/ecs/Entity.js";
|
|
10
|
+
import { Transform64 } from "../../../engine/ecs/transform/Transform64.js";
|
|
11
|
+
import { Light } from "../../../engine/graphics/ecs/light/Light.js";
|
|
12
|
+
import { GPUParticleEmitterSystem } from "../../../engine/graphics3/GPUParticleEmitterSystem.js";
|
|
13
|
+
import { GraphicsEngine } from "../../../engine/graphics3/GraphicsEngine.js";
|
|
14
|
+
import { LightSystem } from "../../../engine/graphics3/LightSystem.js";
|
|
15
|
+
import { ShadedGeometrySystem } from "../../../engine/graphics3/ShadedGeometrySystem.js";
|
|
16
|
+
import { WaterSystem } from "../../../engine/graphics3/WaterSystem.js";
|
|
17
|
+
import { OrbitalCameraController } from "../../renderer/camera/OrbitalCameraController.js";
|
|
18
|
+
import { create_frame_loop } from "../../renderer/create_frame_loop.js";
|
|
19
|
+
import { DynamicMeshAVBOITChannel } from "../../renderer/dynamic/DynamicMeshAVBOITChannel.js";
|
|
20
|
+
import { Scene } from "../../renderer/scene/Scene.js";
|
|
21
|
+
import { ShadeTransparencyMode } from "../../renderer/ShadeTransparencyMode.js";
|
|
22
|
+
import { ShadeUpscalerType } from "../../renderer/ShadeUpscalerType.js";
|
|
23
|
+
import { ParticipatingMediaVolume } from "../../renderer/volumetrics/ParticipatingMediaVolume.js";
|
|
24
|
+
import { water_shader, water_finalize_shader } from "../../renderer/water/shader_water.js";
|
|
25
|
+
import * as water_shaders from "../../renderer/water/shader_water_avboit.js";
|
|
26
|
+
import { install_profile_hotkey } from "../profile_hotkey.js";
|
|
27
|
+
import { build_world } from "./world.js";
|
|
28
|
+
|
|
29
|
+
const element = id => document.getElementById(id);
|
|
30
|
+
const presets = {
|
|
31
|
+
shore: [[22,9,28],[16,0,15]], normal: [[12,17,12.01],[12,0,12]],
|
|
32
|
+
grazing: [[21,0.6,17],[12,0,12]], posts: [[8,3,20],[10,-0.5,14]],
|
|
33
|
+
edge: [[22,5,18],[23,0,11]], underwater: [[19,-0.8,18],[12,-1,12]],
|
|
34
|
+
discontinuities: [[18.946,7.002,6.28],[15.4,2.439,10.562]],
|
|
35
|
+
};
|
|
36
|
+
const input_ids = ["preset","enabled","level","ior","normal","scale","roughness","extinction_r","extinction_g",
|
|
37
|
+
"extinction_b","color","shore_color","reflection","layer_height","opacity","particles","ribbon","animate",
|
|
38
|
+
"overlap","mode","iterations","fallback","temporal","resolution","ssr","fog","transparency","debug"];
|
|
39
|
+
const initial_inputs = Object.fromEntries(input_ids.map(id => [id,element(id).type === "checkbox" ? element(id).checked : element(id).value]));
|
|
40
|
+
const number = id => Number(element(id).value);
|
|
41
|
+
const checked = id => element(id).checked;
|
|
42
|
+
const color = (target, hex) => target.setRGB(...[1,3,5].map(i => parseInt(hex.slice(i,i+2),16)/255));
|
|
43
|
+
|
|
44
|
+
async function main() {
|
|
45
|
+
const viewport = element("viewport"), graphics = new GraphicsEngine(), scene = new Scene();
|
|
46
|
+
viewport.appendChild(graphics.viewport.el);
|
|
47
|
+
const assets = new AssetManager({context:{},executor:new ConcurrentExecutor()});
|
|
48
|
+
await assets.registerLoader(GameAssetType.Image,new ImageRGBADataLoader()); assets.startup();
|
|
49
|
+
const dataset = new EntityComponentDataset(), entities = new EntityManager();
|
|
50
|
+
const water_system = new WaterSystem(graphics);
|
|
51
|
+
entities.addSystem(new ShadedGeometrySystem(graphics,scene)); entities.addSystem(new LightSystem(graphics,scene));
|
|
52
|
+
entities.addSystem(new GPUParticleEmitterSystem(graphics,scene,assets)); entities.addSystem(water_system);
|
|
53
|
+
entities.attachDataset(dataset);
|
|
54
|
+
await new Promise((resolve,reject) => entities.startup(resolve,reject));
|
|
55
|
+
const world = await build_world(dataset), channel = new DynamicMeshAVBOITChannel(world.batch);
|
|
56
|
+
graphics.add_transparency_channel(channel);
|
|
57
|
+
const sun = new Light(); sun.intensity.set(2.2);
|
|
58
|
+
const sun_pose = new Transform64(), rotation = new Quaternion();
|
|
59
|
+
rotation.fromUnitVectors(Vector3.forward,new Vector3(-0.3,-1,0.2).normalize());
|
|
60
|
+
sun_pose.setRotation(rotation.x,rotation.y,rotation.z,rotation.w);sun_pose.updateMatrix();
|
|
61
|
+
const sun_entity = new Entity().add(sun).add(sun_pose);sun_entity.build(dataset);
|
|
62
|
+
const camera = graphics.camera.camera, controller = new OrbitalCameraController(camera,viewport);
|
|
63
|
+
camera.near=0.1;camera.far=300;camera.fov=Math.PI/3;
|
|
64
|
+
const look = key => controller.look(new Vector3(...presets[key][0]),new Vector3(...presets[key][1]));
|
|
65
|
+
look("shore");
|
|
66
|
+
let ready=false,paused=false,step=false,time=0,profile=null,stop_loop=null,swapped=false,terrain_offset=0,revision=0;
|
|
67
|
+
const fog = new ParticipatingMediaVolume();fog.target_extinction=0.015;
|
|
68
|
+
fog.transform.setScale(80,40,80);fog.transform.setTranslation(16,0,16);fog.transform.updateMatrix();
|
|
69
|
+
let fog_added=false;
|
|
70
|
+
const report = message => {element("status").textContent=message;};
|
|
71
|
+
graphics.on.contextFailed.add(error => report(error.message));
|
|
72
|
+
graphics.on.contextLost.add(error => {ready=false;report(`GPU device lost · ${error.message} · use Restart device`);});
|
|
73
|
+
function configure() {
|
|
74
|
+
revision++;
|
|
75
|
+
const w=world.water,s=water_system.settings,r=graphics.renderer;
|
|
76
|
+
w.level.set(number("level"));w.ior.set(number("ior"));w.opticalNormalStrength.set(number("normal"));
|
|
77
|
+
w.opticalNormalScale.set(number("scale"));w.reflectionRoughness.set(number("roughness"));
|
|
78
|
+
w.extinction.set(number("extinction_r"),number("extinction_g"),number("extinction_b"));
|
|
79
|
+
color(w.color,element("color").value);color(w.shoreColor,element("shore_color").value);
|
|
80
|
+
Object.assign(s,{enabled:checked("enabled"),mode:number("mode"),max_updates:number("iterations"),
|
|
81
|
+
fallback_steps:number("fallback"),reflections:checked("reflection")?1:0,debug_view:number("debug")});
|
|
82
|
+
world.card.component.material.diffuse_color.a=number("opacity");
|
|
83
|
+
world.effects.forEach(effect=>effect.emitting=checked("particles"));world.ribbon.visible=checked("ribbon");
|
|
84
|
+
world.overlap(checked("overlap"));
|
|
85
|
+
if(checked("fog")&&!fog_added){scene.volumetrics.add(fog);fog_added=true;}
|
|
86
|
+
if(!checked("fog")&&fog_added){scene.volumetrics.remove(fog);fog_added=false;}
|
|
87
|
+
if(r!==null&&r!==undefined){
|
|
88
|
+
r.transparency_mode=element("transparency").value==="avboit"?ShadeTransparencyMode.AVBOIT:ShadeTransparencyMode.MBOIT;
|
|
89
|
+
r.feature_taa_enabled=element("temporal").value!=="off";
|
|
90
|
+
r.upscale_type=element("temporal").value==="nss"?ShadeUpscalerType.NSS:ShadeUpscalerType.TAA;
|
|
91
|
+
r.feature_ssr_enabled=checked("ssr");r.pixel_ratio=number("resolution");
|
|
92
|
+
r.feature_automatic_exposure_enabled=false;r.exposure_compensation=0;
|
|
93
|
+
r.feature_bloom_enabled=false;r.feature_ssao_enabled=false;
|
|
94
|
+
}
|
|
95
|
+
element("probe").textContent="Settings changed — Shift-click to inspect a fresh pixel.";
|
|
96
|
+
if(element("transparency").value!=="avboit")report("MBOIT · water, particles and ribbons are unavailable");
|
|
97
|
+
else if(!s.enabled)report("Water disabled · shared transparency remains active");
|
|
98
|
+
else if(element("preset").value==="underwater")report("Below interface · bounded tint control; underwater transport is unsupported");
|
|
99
|
+
else report("Ready · one water interface · shared AVBOIT");
|
|
100
|
+
}
|
|
101
|
+
async function start_device() {
|
|
102
|
+
ready=false;profile?.dispose();await graphics.start();configure();
|
|
103
|
+
const renderer=graphics.renderer;
|
|
104
|
+
profile=install_profile_hotkey({renderer,name:"water_refraction",status:element("status"),target:viewport,
|
|
105
|
+
settings:()=>[element("mode").selectedOptions[0].textContent,`${number("iterations")} updates`,`${number("fallback")} fallback`,element("temporal").value]});
|
|
106
|
+
let validation_failed=false;
|
|
107
|
+
renderer.graphics.device.addEventListener("uncapturederror",event=>{
|
|
108
|
+
if(validation_failed)return;
|
|
109
|
+
validation_failed=true;ready=false;report(event.error.message);console.error(event.error.message);
|
|
110
|
+
});
|
|
111
|
+
ready=true;report("Ready · one water interface · shared AVBOIT");
|
|
112
|
+
}
|
|
113
|
+
function state() {
|
|
114
|
+
const t=camera.transform;
|
|
115
|
+
return {version:1,inputs:Object.fromEntries(input_ids.map(id=>[id,element(id).type==="checkbox"?checked(id):element(id).value])),
|
|
116
|
+
camera:{position:[t.translation_x,t.translation_y,t.translation_z],target:[controller.target.x,controller.target.y,controller.target.z]},
|
|
117
|
+
time,paused,terrain_offset,swapped};
|
|
118
|
+
}
|
|
119
|
+
function apply_state(value) {
|
|
120
|
+
if(value.version!==1)throw new Error("Unsupported state version");
|
|
121
|
+
for(const id of input_ids){const el=element(id),v=value.inputs?.[id];if(v===undefined)continue;
|
|
122
|
+
if(el.type==="checkbox")el.checked=!!v;
|
|
123
|
+
else if(el.type==="range"||el.type==="number"){
|
|
124
|
+
const n=Number(v);if(!Number.isFinite(n))throw new Error(`Invalid ${id}`);
|
|
125
|
+
el.value=Math.max(Number(el.min),Math.min(Number(el.max),n));
|
|
126
|
+
}else if(el.tagName==="SELECT"){
|
|
127
|
+
if(![...el.options].some(o=>o.value===String(v)))throw new Error(`Invalid ${id}`);el.value=v;
|
|
128
|
+
}else if(/^#[0-9a-f]{6}$/i.test(v))el.value=v;
|
|
129
|
+
}
|
|
130
|
+
const vector=v=>{if(!Array.isArray(v)||v.length!==3||!v.every(Number.isFinite))throw new Error("Invalid camera");return new Vector3(...v);};
|
|
131
|
+
controller.look(vector(value.camera.position),vector(value.camera.target));
|
|
132
|
+
if(!Number.isFinite(value.time)||value.time<0)throw new Error("Invalid time");
|
|
133
|
+
time=value.time;water_system.time=time;paused=!!value.paused;
|
|
134
|
+
terrain_offset=Number(value.terrain_offset)||0;world.replace_terrain(terrain_offset);
|
|
135
|
+
if(!!value.swapped!==swapped)swap_order();
|
|
136
|
+
configure();element("pause").textContent=paused?"Resume":"Pause";
|
|
137
|
+
}
|
|
138
|
+
function swap_order() {
|
|
139
|
+
graphics.remove_transparency_channel(channel);graphics.remove_transparency_channel(water_system.transparency_channel);
|
|
140
|
+
swapped=!swapped;
|
|
141
|
+
(swapped?[channel,water_system.transparency_channel]:[water_system.transparency_channel,channel]).forEach(c=>graphics.add_transparency_channel(c));
|
|
142
|
+
}
|
|
143
|
+
input_ids.forEach(id=>element(id).addEventListener("input",configure));
|
|
144
|
+
element("preset").addEventListener("change",()=>{look(element("preset").value);configure();});
|
|
145
|
+
element("pause").onclick=()=>{paused=!paused;element("pause").textContent=paused?"Resume":"Pause";};
|
|
146
|
+
element("step").onclick=()=>{paused=true;step=true;element("pause").textContent="Resume";};
|
|
147
|
+
element("reset").onclick=()=>{world.reset_particles();apply_state({version:1,inputs:initial_inputs,
|
|
148
|
+
camera:{position:presets.shore[0],target:presets.shore[1]},time:0,paused:false,terrain_offset:0,swapped:false});};
|
|
149
|
+
element("order").onclick=swap_order;
|
|
150
|
+
element("terrain").onclick=()=>{terrain_offset=terrain_offset===0?-0.25:0;world.replace_terrain(terrain_offset);};
|
|
151
|
+
element("restart").onclick=async()=>{try{ready=false;graphics.stop();await start_device();}catch(error){report(error.message);}};
|
|
152
|
+
element("profile").onclick=()=>profile?.toggle().catch(error=>report(error.message));
|
|
153
|
+
element("copy").onclick=async()=>{const json=JSON.stringify(state(),null,2);element("state").value=json;
|
|
154
|
+
try{await navigator.clipboard.writeText(json);}catch{/* Text remains available for manual copying. */}};
|
|
155
|
+
element("apply").onclick=()=>{try{apply_state(JSON.parse(element("state").value));}catch(error){report(error.message);}};
|
|
156
|
+
element("compile").onclick=async()=>{
|
|
157
|
+
if(!ready)return;
|
|
158
|
+
const device=graphics.renderer.graphics.device,errors=[];
|
|
159
|
+
for(const shader of [water_shader,water_finalize_shader,...Object.values(water_shaders).map(s=>s.descriptor)]){
|
|
160
|
+
if(typeof shader?.source!=="string")continue;
|
|
161
|
+
const module=device.createShaderModule({code:shader.source});const info=await module.getCompilationInfo();
|
|
162
|
+
errors.push(...info.messages.filter(m=>m.type==="error").map(m=>`${m.lineNum}: ${m.message}`));
|
|
163
|
+
}
|
|
164
|
+
report(errors.length?errors.join("\n"):"Water shader modules compiled successfully.");
|
|
165
|
+
};
|
|
166
|
+
let probe_pending=false;
|
|
167
|
+
viewport.addEventListener("click",async event=>{
|
|
168
|
+
if(!event.shiftKey||!ready||probe_pending)return;
|
|
169
|
+
if(!checked("enabled")||element("transparency").value!=="avboit"){element("probe").textContent="Enable AVBOIT water before inspecting.";return;}
|
|
170
|
+
const rect=viewport.getBoundingClientRect();probe_pending=true;
|
|
171
|
+
const probe_revision=revision,probe_camera=JSON.stringify(state().camera);
|
|
172
|
+
try{const result=await water_system.request_probe([(event.clientX-rect.left)/rect.width,(event.clientY-rect.top)/rect.height]);
|
|
173
|
+
const packed=result.receiver[3],code=Math.floor(packed);
|
|
174
|
+
const classification=result.transmission[3]<=0?"no water coverage":code===31?"below-interface control":
|
|
175
|
+
["Newton-assisted hit","HZB hit","undisplaced fallback","bounded sky"][Math.floor(code/8)]??"invalid";
|
|
176
|
+
const reason=["none / not attempted","bounds / clip","sky","parallel / invalid","ray interval","budget / residual"][code%8];
|
|
177
|
+
element("probe").textContent=JSON.stringify({...result,
|
|
178
|
+
stale:revision!==probe_revision||JSON.stringify(state().camera)!==probe_camera,
|
|
179
|
+
classification,newton_reason:code===31?"unsupported view":reason,
|
|
180
|
+
newton_updates:Math.round((packed-code)*32)},null,2);
|
|
181
|
+
}catch(error){element("probe").textContent=error.message;}finally{probe_pending=false;}
|
|
182
|
+
});
|
|
183
|
+
Object.assign(globalThis,{graphics,scene,dataset,entities,world,water_system,camera,controller,
|
|
184
|
+
water_playground:{state,apply_state,get ready(){return ready;}}});
|
|
185
|
+
await start_device();
|
|
186
|
+
function frame(raw_dt) {
|
|
187
|
+
if(!ready)return;
|
|
188
|
+
const dt=step?1/60:paused?0:Math.min(raw_dt,0.05);step=false;time+=dt;
|
|
189
|
+
graphics.viewport.size.set(Math.max(1,viewport.clientWidth),Math.max(1,viewport.clientHeight));
|
|
190
|
+
controller.update();world.update(time,{layer_height:number("layer_height"),animate:checked("animate")});
|
|
191
|
+
entities.update(dt);graphics.render(dt);
|
|
192
|
+
}
|
|
193
|
+
stop_loop=create_frame_loop(frame,60);
|
|
194
|
+
globalThis.water_playground.frame=frame;
|
|
195
|
+
window.addEventListener("pagehide",()=>{
|
|
196
|
+
ready=false;stop_loop?.();profile?.dispose();world.dispose();dataset.removeEntity(sun_entity.id);
|
|
197
|
+
channel.release(world.ribbon.geometry);graphics.remove_transparency_channel(channel);
|
|
198
|
+
entities.shutdown(()=>{},console.error);controller.pointer.stop();controller.keyboard.stop();graphics.stop();
|
|
199
|
+
},{once:true});
|
|
200
|
+
}
|
|
201
|
+
main().catch(error=>{console.error(error);element("status").textContent=error.message;});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** All visible static content uses ordinary ECS meshlets; water owns no demo-specific pass. */
|
|
2
|
+
export function build_world(dataset: any): Promise<{
|
|
3
|
+
terrain: Terrain;
|
|
4
|
+
water: Water;
|
|
5
|
+
water_entity: any;
|
|
6
|
+
card: {
|
|
7
|
+
transform: Transform64;
|
|
8
|
+
component: ShadedGeometry;
|
|
9
|
+
entity: any;
|
|
10
|
+
};
|
|
11
|
+
submerged: {
|
|
12
|
+
transform: Transform64;
|
|
13
|
+
component: ShadedGeometry;
|
|
14
|
+
entity: any;
|
|
15
|
+
};
|
|
16
|
+
batch: DynamicMeshBatch;
|
|
17
|
+
ribbon: DynamicMesh;
|
|
18
|
+
effects: ParticleEffect[];
|
|
19
|
+
entities: any[];
|
|
20
|
+
reset_particles(): void;
|
|
21
|
+
update(time: any, { layer_height, animate }?: {
|
|
22
|
+
layer_height?: number;
|
|
23
|
+
animate?: boolean;
|
|
24
|
+
}): void;
|
|
25
|
+
replace_terrain(offset?: number): void;
|
|
26
|
+
overlap(enabled: any): void;
|
|
27
|
+
dispose(): void;
|
|
28
|
+
}>;
|
|
29
|
+
export function height_at(x: any, z: any, offset?: number): number;
|
|
30
|
+
import Terrain from "../../../engine/ecs/terrain/ecs/Terrain.js";
|
|
31
|
+
import Water from "../../../engine/graphics/ecs/water/Water.js";
|
|
32
|
+
import { Transform64 } from "../../../engine/ecs/transform/Transform64.js";
|
|
33
|
+
import { ShadedGeometry } from "../../../engine/graphics/ecs/mesh-v2/ShadedGeometry.js";
|
|
34
|
+
import { DynamicMeshBatch } from "../../renderer/scene/DynamicMeshBatch.js";
|
|
35
|
+
import { DynamicMesh } from "../../renderer/scene/DynamicMesh.js";
|
|
36
|
+
import { ParticleEffect } from "../../../engine/graphics/ecs/particles/ParticleEffect.js";
|
|
37
|
+
//# sourceMappingURL=world.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"world.d.ts","sourceRoot":"","sources":["../../../../../src/shade/playground/water_refraction/world.js"],"names":[],"mappings":"AA0DA,+FAA+F;AAC/F;;;;;;;;;;;;;;;;;;;;;;;;;;GA6EC;AA5GM,mEAAsE;oBAxBzD,4CAA4C;kBAI9C,6CAA6C;4BAHnC,8CAA8C;+BAE3C,wDAAwD;iCActD,0CAA0C;4BAD/C,qCAAqC;+BAXlC,0DAA0D"}
|