@woosh/meep-engine 3.42.0 → 3.43.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 +91 -96
- package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
- package/src/shade/playground/vgeo_runtime/README.md +5 -4
- package/src/shade/renderer/Renderer.d.ts.map +1 -1
- package/src/shade/renderer/Renderer.js +6 -5
- package/src/shade/renderer/atmosphere/GPUSky.d.ts +12 -0
- package/src/shade/renderer/atmosphere/GPUSky.d.ts.map +1 -1
- package/src/shade/renderer/atmosphere/GPUSky.js +162 -147
- package/src/shade/renderer/lightmap/bake/shader_lightmap_denoise_sh.d.ts.map +1 -1
- package/src/shade/renderer/lightmap/bake/shader_lightmap_denoise_sh.js +10 -3
- package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
- package/src/shade/renderer/particles/DESIGN.md +13 -9
- package/src/shade/renderer/particles/shaders/chunk_particle_lighting.d.ts +1 -1
- package/src/shade/renderer/particles/shaders/chunk_particle_lighting.js +1 -1
- package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
- package/src/shade/renderer/rasterize/native/avboit/AVBOITSideChannel.d.ts +3 -2
- package/src/shade/renderer/rasterize/native/avboit/AVBOITSideChannel.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/native/avboit/AVBOITSideChannel.js +3 -2
- package/src/shade/renderer/scene/GPUSceneContext.d.ts +9 -1
- package/src/shade/renderer/scene/GPUSceneContext.d.ts.map +1 -1
- package/src/shade/renderer/scene/GPUSceneContext.js +23 -1
- package/src/shade/renderer/volumetrics/GPUViewVolumetrics.d.ts +4 -1
- package/src/shade/renderer/volumetrics/GPUViewVolumetrics.d.ts.map +1 -1
- package/src/shade/renderer/volumetrics/GPUViewVolumetrics.js +74 -3
- package/src/shade/renderer/volumetrics/NOTES.md +1023 -957
- package/src/shade/renderer/volumetrics/chunk_volumetrics_irradiance_sh.d.ts +5 -4
- package/src/shade/renderer/volumetrics/chunk_volumetrics_irradiance_sh.d.ts.map +1 -1
- package/src/shade/renderer/volumetrics/chunk_volumetrics_irradiance_sh.js +5 -4
- package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_gi.d.ts +1 -1
- package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_gi.js +1 -1
- package/src/shade/renderer/volumetrics/gi/graph_build_froxel_gi.d.ts +1 -1
- package/src/shade/renderer/volumetrics/gi/graph_build_froxel_gi.js +1 -1
- package/src/shade/renderer/volumetrics/graph_build_froxel_irradiance.d.ts +41 -0
- package/src/shade/renderer/volumetrics/graph_build_froxel_irradiance.d.ts.map +1 -0
- package/src/shade/renderer/volumetrics/graph_build_froxel_irradiance.js +112 -0
- package/src/shade/renderer/volumetrics/graph_build_volumetrics.d.ts +9 -23
- package/src/shade/renderer/volumetrics/graph_build_volumetrics.d.ts.map +1 -1
- package/src/shade/renderer/volumetrics/graph_build_volumetrics.js +18 -126
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_irradiance.d.ts +22 -0
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_irradiance.d.ts.map +1 -0
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_irradiance.js +228 -0
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.d.ts.map +1 -1
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.js +0 -53
- package/src/shade/renderer/water/surface/chunk_water_surface_sample.d.ts +4 -3
- package/src/shade/renderer/water/surface/chunk_water_surface_sample.d.ts.map +1 -1
- package/src/shade/renderer/water/surface/chunk_water_surface_sample.js +17 -7
- package/src/shade/renderer/water/surface/particles/GPUWaterParticles.d.ts.map +1 -1
- package/src/shade/renderer/water/surface/particles/GPUWaterParticles.js +69 -23
- package/src/shade/renderer/water/surface/particles/WATER_PARTICLE_STRUCT.d.ts.map +1 -1
- package/src/shade/renderer/water/surface/particles/WATER_PARTICLE_STRUCT.js +13 -5
- package/src/shade/renderer/water/surface/particles/chunk_water_particle.d.ts +29 -8
- package/src/shade/renderer/water/surface/particles/chunk_water_particle.d.ts.map +1 -1
- package/src/shade/renderer/water/surface/particles/chunk_water_particle.js +155 -30
- package/src/shade/renderer/water/surface/particles/chunk_water_particle_step.d.ts +6 -1
- package/src/shade/renderer/water/surface/particles/chunk_water_particle_step.d.ts.map +1 -1
- package/src/shade/renderer/water/surface/particles/chunk_water_particle_step.js +157 -39
- package/src/shade/renderer/water/surface/particles/shader_water_particles.d.ts +91 -21
- package/src/shade/renderer/water/surface/particles/shader_water_particles.d.ts.map +1 -1
- package/src/shade/renderer/water/surface/particles/shader_water_particles.js +442 -70
- package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +11 -3
- package/src/shade/wgsl/emulator/WGSLLanguageCore.d.ts.map +1 -1
- package/src/shade/wgsl/emulator/WGSLLanguageCore.js +3 -0
|
@@ -10,7 +10,7 @@ import { WebGPUStruct } from "../../../shader/type/WebGPUStruct.js";
|
|
|
10
10
|
* without integration error and every particle of one front shares the origin and the age.
|
|
11
11
|
*/
|
|
12
12
|
export const WATER_PARTICLE_STRUCT = WebGPUStruct.from({
|
|
13
|
-
/** Centre of the front, or
|
|
13
|
+
/** Centre of the front, or the mirror image of it for a reflection; metric local xz. */
|
|
14
14
|
origin: vec2f,
|
|
15
15
|
/** Propagation direction, radians from local +x towards +z. */
|
|
16
16
|
angle: f32,
|
|
@@ -24,6 +24,11 @@ export const WATER_PARTICLE_STRUCT = WebGPUStruct.from({
|
|
|
24
24
|
dispersion: f32,
|
|
25
25
|
/** The water_waves row of the body it lives on. */
|
|
26
26
|
body: u32,
|
|
27
|
+
/**
|
|
28
|
+
* 1 once the first of its support has left its body's outline, or that of the particle it split from: its mirror
|
|
29
|
+
* image was emitted then, and it dies once the last of it has left too. 0 from emission.
|
|
30
|
+
*/
|
|
31
|
+
reflected: u32,
|
|
27
32
|
}, 'WaterParticle');
|
|
28
33
|
|
|
29
34
|
/**
|
|
@@ -37,13 +42,16 @@ export const WATER_PARTICLE_ENTRY_STRUCT = WebGPUStruct.from({
|
|
|
37
42
|
angle: f32,
|
|
38
43
|
amplitude: f32,
|
|
39
44
|
radius: f32,
|
|
40
|
-
/**
|
|
41
|
-
|
|
45
|
+
/** Half-angle of the window around the front's centre: the angle to the front's neighbouring particle. */
|
|
46
|
+
dispersion: f32,
|
|
42
47
|
/** Carrier phase lag of the crests behind the envelope, radians. */
|
|
43
48
|
phase: f32,
|
|
44
|
-
/** water_waves row
|
|
49
|
+
/** water_waves row and level, as water_particle_key packs them */
|
|
45
50
|
key: u32,
|
|
46
51
|
/** Index of the particle, which orders a bucket. */
|
|
47
52
|
source: u32,
|
|
48
|
-
|
|
53
|
+
/** How far the particle stands from its front's centre, metres. */
|
|
54
|
+
distance: f32,
|
|
55
|
+
/** Half the length of its envelope across its front, metres. */
|
|
56
|
+
half_length: f32,
|
|
49
57
|
}, 'WaterParticleEntry');
|
|
@@ -1,19 +1,40 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Levels of the particle hash: cell sizes 4 mm · 2^level, so supports up to 256 m, which a 2 m splash's stretched
|
|
3
|
+
* packets reach before they die.
|
|
4
|
+
*/
|
|
5
|
+
export const WATER_PARTICLE_LEVELS: 18;
|
|
3
6
|
/** Cell size of level 0, metres. A particle sits at the smallest level whose cell is at least its support's diameter. */
|
|
4
7
|
export const WATER_PARTICLE_LEVEL0_CELL: number;
|
|
8
|
+
/**
|
|
9
|
+
* How fast a packet's envelope stretches as it disperses, as a fraction κ of its group speed, by its wavenumber k against
|
|
10
|
+
* k_m = √(g/(σ/ρ)), the wavenumber of the slowest crests (λ = 1.71 cm): from x = log₂(k/k_m) = -7 in steps of a quarter,
|
|
11
|
+
* held at both ends.
|
|
12
|
+
*
|
|
13
|
+
* Its half-length h, 2r when emitted, grows as h² = (2r)² + (κD/2)² over the distance D its front has travelled since:
|
|
14
|
+
* the spread of an unchirped packet's energy grows as the spread of its group speeds times time, in quadrature with
|
|
15
|
+
* where it began (Jeschke & Wojtan 2017 grow it linearly, eq. 14, its far limit). Each κ is the one whose peak
|
|
16
|
+
* √(2r/h) best matches exact linear theory, ω² = gk + (σ/ρ)k³, for the emitted packet at 5 to 225 wavelengths (FFT,
|
|
17
|
+
* worst ratio minimised): within 2% for capillary waves up to r = 8.5 mm and gravity waves from r = 6 cm, where κ
|
|
18
|
+
* tends to 1 and 0.9, and 5% from 4 cm. Between, around the slowest group speed (λ = 4.4 cm), a broad band's energy
|
|
19
|
+
* gathers at that speed and the peak falls as t^(−1/3), which no κ draws: up to 16% below r = 12 mm, 27% at 3 cm.
|
|
20
|
+
*/
|
|
21
|
+
export const WATER_PARTICLE_STRETCH: number[];
|
|
5
22
|
/**
|
|
6
23
|
* The physics of one wave particle as pure functions (WATER_SURFACE_SIMULATION_PLAN_2026_10_06.md §5).
|
|
7
24
|
*
|
|
8
25
|
* A particle of radius r carries a packet of wavelength 2r, k = π/r. Under the gravity–capillary
|
|
9
26
|
* dispersion relation the packet's envelope travels at the group speed and its crests at the phase speed
|
|
10
|
-
* (Jeschke & Wojtan 2017; Xue et al. 2025 give wave particles their own wavelength's dispersion).
|
|
11
|
-
*
|
|
12
|
-
*
|
|
27
|
+
* (Jeschke & Wojtan 2017; Xue et al. 2025 give wave particles their own wavelength's dispersion). The packet holds a
|
|
28
|
+
* band of wavelengths around 2r, which the same relation moves apart: its envelope, two radii either side when emitted,
|
|
29
|
+
* stretches as it travels and its height falls to keep its energy (WATER_PARTICLE_STRETCH), as a splash's single ring
|
|
30
|
+
* becomes a widening train of ripples. It loses amplitude at the clean-surface viscous rate 2νk² plus the surface-film
|
|
31
|
+
* rate k√(νω)/(2√2) of natural water — so a raindrop's ripple is gone in a fraction of a second while a big splash
|
|
32
|
+
* crosses a bay.
|
|
13
33
|
*
|
|
14
|
-
* The particles of a front are
|
|
15
|
-
*
|
|
16
|
-
* whatever the spacing,
|
|
34
|
+
* The particles of a front are β apart in angle around its centre, and a packet's crests are arcs around that
|
|
35
|
+
* centre; a cosine window of half-angle β is a partition of unity over them at every radius, so the front's
|
|
36
|
+
* height is the packet's amplitude whatever the spacing, splitting a front changes none of it, and one front
|
|
37
|
+
* adds at most two particles at any point.
|
|
17
38
|
*
|
|
18
39
|
* @type {CodeChunk}
|
|
19
40
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chunk_water_particle.d.ts","sourceRoot":"","sources":["../../../../../../../src/shade/renderer/water/surface/particles/chunk_water_particle.js"],"names":[],"mappings":"AAKA
|
|
1
|
+
{"version":3,"file":"chunk_water_particle.d.ts","sourceRoot":"","sources":["../../../../../../../src/shade/renderer/water/surface/particles/chunk_water_particle.js"],"names":[],"mappings":"AAKA;;;GAGG;AACH,uCAAwC;AAKxC,yHAAyH;AACzH,gDAAkD;AAElD;;;;;;;;;;;;GAYG;AACH,8CAIE;AAKF;;;;;;;;;;;;;;;;;;GAkBG;AACH,mCAFU,SAAS,CAmN6G;0BA3QtG,uCAAuC"}
|
|
@@ -3,30 +3,65 @@ import { chunk_pcg3d } from "../../../shader/chunk/random/pcg/chunk_pcg3d.js";
|
|
|
3
3
|
import { WATER_PARTICLE_ENTRY_STRUCT, WATER_PARTICLE_STRUCT } from "./WATER_PARTICLE_STRUCT.js";
|
|
4
4
|
import { chunk_water_spectrum } from "../spectrum/chunk_water_spectrum.js";
|
|
5
5
|
|
|
6
|
-
/**
|
|
7
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Levels of the particle hash: cell sizes 4 mm · 2^level, so supports up to 256 m, which a 2 m splash's stretched
|
|
8
|
+
* packets reach before they die.
|
|
9
|
+
*/
|
|
10
|
+
export const WATER_PARTICLE_LEVELS = 18;
|
|
11
|
+
|
|
12
|
+
/** Bits of a hash key that hold the level, below the body's row. */
|
|
13
|
+
const WATER_PARTICLE_LEVEL_BITS = Math.ceil(Math.log2(WATER_PARTICLE_LEVELS));
|
|
8
14
|
|
|
9
15
|
/** Cell size of level 0, metres. A particle sits at the smallest level whose cell is at least its support's diameter. */
|
|
10
16
|
export const WATER_PARTICLE_LEVEL0_CELL = 1 / 256;
|
|
11
17
|
|
|
18
|
+
/**
|
|
19
|
+
* How fast a packet's envelope stretches as it disperses, as a fraction κ of its group speed, by its wavenumber k against
|
|
20
|
+
* k_m = √(g/(σ/ρ)), the wavenumber of the slowest crests (λ = 1.71 cm): from x = log₂(k/k_m) = -7 in steps of a quarter,
|
|
21
|
+
* held at both ends.
|
|
22
|
+
*
|
|
23
|
+
* Its half-length h, 2r when emitted, grows as h² = (2r)² + (κD/2)² over the distance D its front has travelled since:
|
|
24
|
+
* the spread of an unchirped packet's energy grows as the spread of its group speeds times time, in quadrature with
|
|
25
|
+
* where it began (Jeschke & Wojtan 2017 grow it linearly, eq. 14, its far limit). Each κ is the one whose peak
|
|
26
|
+
* √(2r/h) best matches exact linear theory, ω² = gk + (σ/ρ)k³, for the emitted packet at 5 to 225 wavelengths (FFT,
|
|
27
|
+
* worst ratio minimised): within 2% for capillary waves up to r = 8.5 mm and gravity waves from r = 6 cm, where κ
|
|
28
|
+
* tends to 1 and 0.9, and 5% from 4 cm. Between, around the slowest group speed (λ = 4.4 cm), a broad band's energy
|
|
29
|
+
* gathers at that speed and the peak falls as t^(−1/3), which no κ draws: up to 16% below r = 12 mm, 27% at 3 cm.
|
|
30
|
+
*/
|
|
31
|
+
export const WATER_PARTICLE_STRETCH = [
|
|
32
|
+
0.9002, 0.9002, 0.8998, 0.8993, 0.8984, 0.8971, 0.8957, 0.8935, 0.8904, 0.8859, 0.8798, 0.8706, 0.8585, 0.8411, 0.8166,
|
|
33
|
+
0.7819, 0.7323, 0.6669, 0.5795, 0.461, 0.216, 0.0922, 0.064, 0.0723, 0.1089, 0.1877, 0.3427, 0.6145, 0.9295, 1.0417,
|
|
34
|
+
1.0734, 1.0739, 1.0622, 1.0469, 1.0313, 1.0175, 1.0059, 0.9969, 0.9899,
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
/** log₂(k/k_m) of the stretch table's first entry; entries are a quarter apart. */
|
|
38
|
+
const WATER_PARTICLE_STRETCH_FIRST = -7;
|
|
39
|
+
|
|
12
40
|
/**
|
|
13
41
|
* The physics of one wave particle as pure functions (WATER_SURFACE_SIMULATION_PLAN_2026_10_06.md §5).
|
|
14
42
|
*
|
|
15
43
|
* A particle of radius r carries a packet of wavelength 2r, k = π/r. Under the gravity–capillary
|
|
16
44
|
* dispersion relation the packet's envelope travels at the group speed and its crests at the phase speed
|
|
17
|
-
* (Jeschke & Wojtan 2017; Xue et al. 2025 give wave particles their own wavelength's dispersion).
|
|
18
|
-
*
|
|
19
|
-
*
|
|
45
|
+
* (Jeschke & Wojtan 2017; Xue et al. 2025 give wave particles their own wavelength's dispersion). The packet holds a
|
|
46
|
+
* band of wavelengths around 2r, which the same relation moves apart: its envelope, two radii either side when emitted,
|
|
47
|
+
* stretches as it travels and its height falls to keep its energy (WATER_PARTICLE_STRETCH), as a splash's single ring
|
|
48
|
+
* becomes a widening train of ripples. It loses amplitude at the clean-surface viscous rate 2νk² plus the surface-film
|
|
49
|
+
* rate k√(νω)/(2√2) of natural water — so a raindrop's ripple is gone in a fraction of a second while a big splash
|
|
50
|
+
* crosses a bay.
|
|
20
51
|
*
|
|
21
|
-
* The particles of a front are
|
|
22
|
-
*
|
|
23
|
-
* whatever the spacing,
|
|
52
|
+
* The particles of a front are β apart in angle around its centre, and a packet's crests are arcs around that
|
|
53
|
+
* centre; a cosine window of half-angle β is a partition of unity over them at every radius, so the front's
|
|
54
|
+
* height is the packet's amplitude whatever the spacing, splitting a front changes none of it, and one front
|
|
55
|
+
* adds at most two particles at any point.
|
|
24
56
|
*
|
|
25
57
|
* @type {CodeChunk}
|
|
26
58
|
*/
|
|
27
59
|
export const chunk_water_particle = CodeChunk.from(/* language=WGSL */ `
|
|
28
60
|
const WATER_PARTICLE_LEVELS: u32 = ${WATER_PARTICLE_LEVELS}u;
|
|
61
|
+
const WATER_PARTICLE_LEVEL_BITS: u32 = ${WATER_PARTICLE_LEVEL_BITS}u;
|
|
29
62
|
const WATER_PARTICLE_LEVEL0_CELL: f32 = ${WATER_PARTICLE_LEVEL0_CELL};
|
|
63
|
+
const WATER_PARTICLE_STRETCH = array<f32, ${WATER_PARTICLE_STRETCH.length}>(${WATER_PARTICLE_STRETCH.join(', ')});
|
|
64
|
+
const WATER_PARTICLE_STRETCH_FIRST: f32 = ${WATER_PARTICLE_STRETCH_FIRST}.0;
|
|
30
65
|
// kinematic viscosity of water, m²/s
|
|
31
66
|
const WATER_VISCOSITY: f32 = 1e-6;
|
|
32
67
|
const WATER_PI: f32 = 3.14159265358979;
|
|
@@ -66,7 +101,7 @@ fn water_particle_position(particle: ${WATER_PARTICLE_STRUCT.wgsl_ref}) -> vec2<
|
|
|
66
101
|
return particle.origin + water_particle_direction(particle.angle) * water_particle_distance(particle);
|
|
67
102
|
}
|
|
68
103
|
|
|
69
|
-
// The front's arc between neighbouring particles
|
|
104
|
+
// The front's arc between neighbouring particles, at the particle's own radius: what the window spans there.
|
|
70
105
|
fn water_particle_width(particle: ${WATER_PARTICLE_STRUCT.wgsl_ref}) -> f32 {
|
|
71
106
|
return max(particle.dispersion * water_particle_distance(particle), 1e-3 * particle.radius);
|
|
72
107
|
}
|
|
@@ -77,9 +112,79 @@ fn water_particle_phase(particle: ${WATER_PARTICLE_STRUCT.wgsl_ref}) -> f32 {
|
|
|
77
112
|
return water_particle_wavenumber(particle.radius) * lag;
|
|
78
113
|
}
|
|
79
114
|
|
|
80
|
-
//
|
|
81
|
-
fn
|
|
82
|
-
return
|
|
115
|
+
// How far from a splash's centre its ring starts: two radii, the packet's half-length, so it starts clear of the centre.
|
|
116
|
+
fn water_particle_launch_distance(radius: f32) -> f32 {
|
|
117
|
+
return 2.0 * radius;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// How fast the packet's envelope stretches, as a fraction of its group speed (WATER_PARTICLE_STRETCH): interpolated in
|
|
121
|
+
// log κ between quarter octaves of k against the wavenumber of the slowest crests.
|
|
122
|
+
fn water_particle_stretch(radius: f32) -> f32 {
|
|
123
|
+
let octave = log2(water_particle_wavenumber(radius) * sqrt(WATER_SURFACE_TENSION / WATER_GRAVITY));
|
|
124
|
+
let last = f32(${WATER_PARTICLE_STRETCH.length}u - 1u);
|
|
125
|
+
let t = clamp(4.0 * (octave - WATER_PARTICLE_STRETCH_FIRST), 0.0, last);
|
|
126
|
+
let i = u32(min(floor(t), last - 1.0));
|
|
127
|
+
return exp2(mix(log2(WATER_PARTICLE_STRETCH[i]), log2(WATER_PARTICLE_STRETCH[i + 1u]), t - f32(i)));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Half the length of the packet's envelope across its front, for a front that stands distance from its centre: 2r when
|
|
131
|
+
// emitted, growing in quadrature with half the stretch over the distance travelled since. Held to half the largest support
|
|
132
|
+
// the hash holds, so a window no wider keeps the packet within the coarsest cells.
|
|
133
|
+
fn water_particle_half_length_at(radius: f32, distance: f32) -> f32 {
|
|
134
|
+
let travelled = max(distance - water_particle_launch_distance(radius), 0.0);
|
|
135
|
+
let stretched = length(vec2<f32>(2.0 * radius, 0.5 * water_particle_stretch(radius) * travelled));
|
|
136
|
+
return min(stretched, 0.5 * water_particle_support_limit());
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
fn water_particle_half_length(particle: ${WATER_PARTICLE_STRUCT.wgsl_ref}) -> f32 {
|
|
140
|
+
return water_particle_half_length_at(particle.radius, water_particle_distance(particle));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Radius of a circle around the particle holding its whole kernel: the envelope's half-length h across the front, and
|
|
144
|
+
// along it the front's arc of half-angle β plus its outer edge's overhang. A point at radius ρ = D + u and angle ψ from
|
|
145
|
+
// a particle at distance D lies |d|² = u² + 4ρD·sin²(ψ/2) ≤ h² + (D + h)Dβ² ≤ h² + ((D + h/2)β)² from it, and
|
|
146
|
+
// width ≥ Dβ.
|
|
147
|
+
fn water_particle_support(half_length: f32, width: f32, dispersion: f32) -> f32 {
|
|
148
|
+
return length(vec2<f32>(half_length, width + 0.5 * half_length * dispersion));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// The largest support the hash holds: half its coarsest cell.
|
|
152
|
+
fn water_particle_support_limit() -> f32 {
|
|
153
|
+
return 0.5 * water_particle_cell_size(WATER_PARTICLE_LEVELS - 1u);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Whether two particles are of one front: one splash's, neither reflected or faded apart, alike in all but angle.
|
|
157
|
+
fn water_particle_same_front(a: ${WATER_PARTICLE_STRUCT.wgsl_ref}, b: ${WATER_PARTICLE_STRUCT.wgsl_ref}) -> bool {
|
|
158
|
+
return a.body == b.body && all(a.origin == b.origin) && a.age == b.age && a.amplitude == b.amplitude
|
|
159
|
+
&& a.radius == b.radius && a.dispersion == b.dispersion;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// How a particle is drawn, given its front's run of particles in the array and its place in it: 1 as itself; 0 not at
|
|
163
|
+
// all, or a group of 3^g as one packet with 3^g times its window, when its front is whole. Whole is every particle
|
|
164
|
+
// the front split into still there, in order and alike: as many as fill its circle. The step puts a particle's three
|
|
165
|
+
// in its place, the middle at its own angle, so a group of 3^g is 3^g in a row and its middle stands at the angle
|
|
166
|
+
// the group was emitted at; with curved crests the groups draw what the particles do. The largest group whose packet
|
|
167
|
+
// the hash holds: the seven a front was emitted as, until their windows outgrow the coarsest cell.
|
|
168
|
+
fn water_particle_group(particle: ${WATER_PARTICLE_STRUCT.wgsl_ref}, members: u32, offset: u32) -> u32 {
|
|
169
|
+
if (members != u32(round(WATER_TAU / particle.dispersion))) { return 1u; }
|
|
170
|
+
var group = 1u;
|
|
171
|
+
for (var g = 0u; g < 20u && members % (3u * group) == 0u; g++) { group *= 3u; }
|
|
172
|
+
let distance = water_particle_distance(particle);
|
|
173
|
+
let half_length = water_particle_half_length_at(particle.radius, distance);
|
|
174
|
+
for (var g = 0u; g < 20u && group > 1u; g++) {
|
|
175
|
+
let dispersion = particle.dispersion * f32(group);
|
|
176
|
+
let width = max(dispersion * distance, 1e-3 * particle.radius);
|
|
177
|
+
if (water_particle_support(half_length, width, dispersion) <= water_particle_support_limit()) { break; }
|
|
178
|
+
group /= 3u;
|
|
179
|
+
}
|
|
180
|
+
return select(0u, group, offset % group == group / 2u);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// The particle as its group draws it: one packet with the group's window.
|
|
184
|
+
fn water_particle_drawn(particle: ${WATER_PARTICLE_STRUCT.wgsl_ref}, group: u32) -> ${WATER_PARTICLE_STRUCT.wgsl_ref} {
|
|
185
|
+
var drawn = particle;
|
|
186
|
+
drawn.dispersion *= f32(group);
|
|
187
|
+
return drawn;
|
|
83
188
|
}
|
|
84
189
|
|
|
85
190
|
// The smallest hash level whose cell is at least the support's diameter, so the support overlaps at most
|
|
@@ -93,8 +198,13 @@ fn water_particle_cell_size(level: u32) -> f32 {
|
|
|
93
198
|
return WATER_PARTICLE_LEVEL0_CELL * exp2(f32(level));
|
|
94
199
|
}
|
|
95
200
|
|
|
201
|
+
// A hash entry's body and level in one word.
|
|
202
|
+
fn water_particle_key(body: u32, level: u32) -> u32 {
|
|
203
|
+
return (body << WATER_PARTICLE_LEVEL_BITS) | level;
|
|
204
|
+
}
|
|
205
|
+
|
|
96
206
|
fn water_particle_bucket(body: u32, level: u32, cell: vec2<i32>, table_size: u32) -> u32 {
|
|
97
|
-
let bits = pcg3d(vec3<u32>((body
|
|
207
|
+
let bits = pcg3d(vec3<u32>(water_particle_key(body, level), bitcast<u32>(cell.x), bitcast<u32>(cell.y)));
|
|
98
208
|
return bits.x & (table_size - 1u);
|
|
99
209
|
}
|
|
100
210
|
|
|
@@ -105,39 +215,54 @@ struct WaterParticleField {
|
|
|
105
215
|
displacement: vec2<f32>,
|
|
106
216
|
// columns ∂D/∂x and ∂D/∂z: (∂Dx/∂x, ∂Dz/∂x), (∂Dx/∂z, ∂Dz/∂z)
|
|
107
217
|
jacobian: mat2x2<f32>,
|
|
218
|
+
// the gradient of the displacement's signed length along the radius: the slope a quarter carrier cycle on, so that
|
|
219
|
+
// ½(|slope|² + |this|²) is the slope's mean square over a cycle
|
|
220
|
+
displacement_gradient: vec2<f32>,
|
|
108
221
|
}
|
|
109
222
|
|
|
110
|
-
// The packet at offset d from its particle
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
|
|
223
|
+
// The packet at offset d from its particle, which stands distance from its front's centre. ρ the radius from the
|
|
224
|
+
// centre and u = ρ − distance across the front, ψ the angle around the centre from the particle's direction:
|
|
225
|
+
// envelope E(u) over half_length either side, window T(ψ) over the dispersion β either side, carrier phase πu/r − phase.
|
|
226
|
+
// Height a·E·T·cos; horizontal displacement −a·E·T·sin along the radius (Gerstner-like orbits, Yuksel's longitudinal
|
|
227
|
+
// sign); with derivatives, ∇ψ = e_ψ/ρ.
|
|
228
|
+
fn water_particle_field(d: vec2<f32>, angle: f32, amplitude: f32, radius: f32, half_length: f32, dispersion: f32, distance: f32,
|
|
229
|
+
phase: f32) -> WaterParticleField {
|
|
114
230
|
var field: WaterParticleField;
|
|
115
231
|
let direction = water_particle_direction(angle);
|
|
116
232
|
let tangent = vec2<f32>(-direction.y, direction.x);
|
|
117
|
-
let
|
|
118
|
-
let
|
|
119
|
-
|
|
120
|
-
let
|
|
233
|
+
let q = d + direction * distance;
|
|
234
|
+
let rho = length(q);
|
|
235
|
+
// ρ − distance, without the cancellation
|
|
236
|
+
let u = (dot(d, d) + 2.0 * distance * dot(d, direction)) / max(rho + distance, 1e-30);
|
|
237
|
+
if (abs(u) >= half_length || !(rho > 0.0)) { return field; }
|
|
238
|
+
let psi = atan2(dot(d, tangent), distance + dot(d, direction));
|
|
239
|
+
if (abs(psi) >= dispersion) { return field; }
|
|
240
|
+
let radial = q / rho;
|
|
241
|
+
let around = vec2<f32>(-radial.y, radial.x);
|
|
242
|
+
let eu = WATER_PI * u / half_length;
|
|
121
243
|
let envelope = 0.5 * (1.0 + cos(eu));
|
|
122
|
-
let envelope_du = -0.
|
|
123
|
-
let tv = WATER_PI *
|
|
244
|
+
let envelope_du = -0.5 * WATER_PI / half_length * sin(eu);
|
|
245
|
+
let tv = WATER_PI * psi / dispersion;
|
|
124
246
|
let window = 0.5 * (1.0 + cos(tv));
|
|
125
|
-
let
|
|
247
|
+
let window_dpsi = -0.5 * WATER_PI / dispersion * sin(tv);
|
|
126
248
|
let carrier = WATER_PI * u / radius - phase;
|
|
127
249
|
let c = cos(carrier);
|
|
128
250
|
let s = sin(carrier);
|
|
129
251
|
let k = WATER_PI / radius;
|
|
130
252
|
let z = amplitude * envelope * window * c;
|
|
131
253
|
let z_u = amplitude * window * (envelope_du * c - envelope * s * k);
|
|
132
|
-
let
|
|
254
|
+
let z_psi = amplitude * envelope * window_dpsi * c;
|
|
133
255
|
let h = -amplitude * envelope * window * s;
|
|
134
256
|
let h_u = -amplitude * window * (envelope_du * s + envelope * c * k);
|
|
135
|
-
let
|
|
136
|
-
let gradient_h = h_u *
|
|
257
|
+
let h_psi = -amplitude * envelope * window_dpsi * s;
|
|
258
|
+
let gradient_h = h_u * radial + (h_psi / rho) * around;
|
|
259
|
+
// the radius turns as the point moves around the centre: ∂e_ρ/∂x_j = e_ψ (e_ψ)_j / ρ
|
|
260
|
+
let turn = (h / rho) * around;
|
|
137
261
|
field.height = z;
|
|
138
|
-
field.slope = z_u *
|
|
139
|
-
field.displacement = h *
|
|
140
|
-
field.jacobian = mat2x2<f32>(
|
|
262
|
+
field.slope = z_u * radial + (z_psi / rho) * around;
|
|
263
|
+
field.displacement = h * radial;
|
|
264
|
+
field.jacobian = mat2x2<f32>(radial * gradient_h.x + turn * around.x, radial * gradient_h.y + turn * around.y);
|
|
265
|
+
field.displacement_gradient = gradient_h;
|
|
141
266
|
return field;
|
|
142
267
|
}
|
|
143
268
|
`, [chunk_water_spectrum, chunk_pcg3d, WATER_PARTICLE_STRUCT.declaration_chunk, WATER_PARTICLE_ENTRY_STRUCT.declaration_chunk]);
|
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
export const WATER_PARTICLE_FADE_SECONDS: 0.15;
|
|
3
3
|
/**
|
|
4
4
|
* One frame of one wave particle (WATER_SURFACE_SIMULATION_PLAN_2026_10_06.md §5.1): it ages, spreads over its
|
|
5
|
-
* widening front, damps,
|
|
5
|
+
* widening front, damps, meets its body's authored outline, and dies or splits.
|
|
6
|
+
*
|
|
7
|
+
* At the outline the water shows the packet plus the shore's share of its mirror image in the outline (the image
|
|
8
|
+
* method, as Jeschke & Wojtan 2017 §4.4 keep a "ghost" of a reflected packet): the image is emitted the step the first
|
|
9
|
+
* of the packet's support leaves the body, and the packet carries on until the last of it has left it too. The body's
|
|
10
|
+
* outline clips what of either lies beyond it, so the water changes only as the two move.
|
|
6
11
|
*
|
|
7
12
|
* Whether a point is over the body is a down-ray into the body's own BLAS, so the outline is exactly the
|
|
8
13
|
* authored mesh's, holes for islands included. Bindings: `geometries`, `blas_lookup`, `blas_data`,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chunk_water_particle_step.d.ts","sourceRoot":"","sources":["../../../../../../../src/shade/renderer/water/surface/particles/chunk_water_particle_step.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"chunk_water_particle_step.d.ts","sourceRoot":"","sources":["../../../../../../../src/shade/renderer/water/surface/particles/chunk_water_particle_step.js"],"names":[],"mappings":"AAQA,qHAAqH;AACrH,+CAAgD;AAEhD;;;;;;;;;;;;;;GAcG;AACH,wCAFU,SAAS,CAsN8E;0BA9OvE,uCAAuC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CodeChunk } from "../../../shader/compiler/CodeChunk.js";
|
|
2
2
|
import { BVH_TRAVERSAL_STACK_SIZE } from "../../../shader/chunk/bvh/BVH_TRAVERSAL_STACK_SIZE.js";
|
|
3
3
|
import { chunk_ray_query_blas_nearest } from "../../../shader/chunk/geometry/ray/chunk_ray_query_blas_nearest.js";
|
|
4
|
+
import { chunk_geometry_read_triangle_positions } from "../../../shader/chunk/geometry/format/chunk_geometry_read_triangle_positions.js";
|
|
4
5
|
import { RAY_HIT_INFO } from "../../../shader/chunk/geometry/ray/RAY_HIT_INFO.js";
|
|
5
6
|
import { WATER_PARTICLE_STRUCT } from "./WATER_PARTICLE_STRUCT.js";
|
|
6
7
|
import { chunk_water_particle } from "./chunk_water_particle.js";
|
|
@@ -10,7 +11,12 @@ export const WATER_PARTICLE_FADE_SECONDS = 0.15;
|
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* One frame of one wave particle (WATER_SURFACE_SIMULATION_PLAN_2026_10_06.md §5.1): it ages, spreads over its
|
|
13
|
-
* widening front, damps,
|
|
14
|
+
* widening front, damps, meets its body's authored outline, and dies or splits.
|
|
15
|
+
*
|
|
16
|
+
* At the outline the water shows the packet plus the shore's share of its mirror image in the outline (the image
|
|
17
|
+
* method, as Jeschke & Wojtan 2017 §4.4 keep a "ghost" of a reflected packet): the image is emitted the step the first
|
|
18
|
+
* of the packet's support leaves the body, and the packet carries on until the last of it has left it too. The body's
|
|
19
|
+
* outline clips what of either lies beyond it, so the water changes only as the two move.
|
|
14
20
|
*
|
|
15
21
|
* Whether a point is over the body is a down-ray into the body's own BLAS, so the outline is exactly the
|
|
16
22
|
* authored mesh's, holes for islands included. Bindings: `geometries`, `blas_lookup`, `blas_data`,
|
|
@@ -22,8 +28,11 @@ export const chunk_water_particle_step = CodeChunk.from(/* language=WGSL */ `
|
|
|
22
28
|
const WATER_PARTICLE_FADE_SECONDS: f32 = ${WATER_PARTICLE_FADE_SECONDS};
|
|
23
29
|
|
|
24
30
|
struct WaterParticleStep {
|
|
31
|
+
// the particle a step on
|
|
25
32
|
particle: ${WATER_PARTICLE_STRUCT.wgsl_ref},
|
|
26
|
-
//
|
|
33
|
+
// the mirror image it leaves, when it leaves one
|
|
34
|
+
image: ${WATER_PARTICLE_STRUCT.wgsl_ref},
|
|
35
|
+
// the particles it leaves, each split into three when they split: 0, 1, 2 (it, then its image), 3 or 6
|
|
27
36
|
children: u32,
|
|
28
37
|
}
|
|
29
38
|
|
|
@@ -37,6 +46,50 @@ fn water_particle_over_body(geometry_index: u32, local: vec2<f32>) -> ${RAY_HIT_
|
|
|
37
46
|
top - box[1] + margin), geometry_index, &stack, 0u);
|
|
38
47
|
}
|
|
39
48
|
|
|
49
|
+
// The points of a packet's support the step tests against the outline, on its arc ρ from its front's centre: the k-th
|
|
50
|
+
// of its axis and its window's ends, β either side of it. The support is the annular sector of two radii either side of
|
|
51
|
+
// the front and β either side of the axis, so a straight outline meets first the point of its leading arc whose
|
|
52
|
+
// direction is nearest the outline's normal, an end unless the normal is within β of the axis, and leaves last the end
|
|
53
|
+
// of its trailing arc farthest from it: at any incidence, these three find both, or within the arc's sagitta.
|
|
54
|
+
fn water_particle_support_point(particle: ${WATER_PARTICLE_STRUCT.wgsl_ref}, k: u32, rho: f32) -> vec2<f32> {
|
|
55
|
+
let turn = select(0.0, select(particle.dispersion, -particle.dispersion, k == 1u), k > 0u);
|
|
56
|
+
return particle.origin + water_particle_direction(particle.angle + turn) * rho;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Whether a triangle seen from above holds a point strictly inside it, in the body's local xz.
|
|
60
|
+
fn water_triangle_covers(a: vec2<f32>, b: vec2<f32>, c: vec2<f32>, point: vec2<f32>) -> bool {
|
|
61
|
+
let side = sign((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x));
|
|
62
|
+
let ab = (b.x - a.x) * (point.y - a.y) - (b.y - a.y) * (point.x - a.x);
|
|
63
|
+
let bc = (c.x - b.x) * (point.y - b.y) - (c.y - b.y) * (point.x - b.x);
|
|
64
|
+
let ca = (a.x - c.x) * (point.y - c.y) - (a.y - c.y) * (point.x - c.x);
|
|
65
|
+
return side * ab > 0.0 && side * bc > 0.0 && side * ca > 0.0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// The k-th tested point of a particle's leading arc once it has stepped dt on: what water_particle_advance tests.
|
|
69
|
+
fn water_particle_leading_point(particle: ${WATER_PARTICLE_STRUCT.wgsl_ref}, dt: f32, k: u32) -> vec2<f32> {
|
|
70
|
+
var stepped = particle;
|
|
71
|
+
stepped.age += dt;
|
|
72
|
+
return water_particle_support_point(stepped, k, water_particle_distance(stepped) + water_particle_half_length(stepped));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// What is already known of a particle's leading arc a step on, bit k for its k-th tested point: whether that is known,
|
|
76
|
+
// and whether it is over the body; and the triangle under the axis's point when that is. Zero is nothing known. A
|
|
77
|
+
// particle's window's ends are its neighbours' axis points when they are of its front, so the update pass hands each
|
|
78
|
+
// what its neighbours in the subgroup found.
|
|
79
|
+
struct WaterParticleArcHint {
|
|
80
|
+
known: u32,
|
|
81
|
+
over: u32,
|
|
82
|
+
triangle: u32,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Whether any of an arc's tested points is over the body.
|
|
86
|
+
fn water_particle_arc_over(particle: ${WATER_PARTICLE_STRUCT.wgsl_ref}, geometry_index: u32, scale: vec2<f32>, rho: f32) -> bool {
|
|
87
|
+
for (var k = 0u; k < 3u; k++) {
|
|
88
|
+
if (water_particle_over_body(geometry_index, water_particle_support_point(particle, k, rho) / scale).t >= 0.0) { return true; }
|
|
89
|
+
}
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
|
|
40
93
|
// The line of the edge of a triangle nearest a point inside it, in the metric xz frame: a point on it and a
|
|
41
94
|
// unit normal. Which side the normal points to does not matter: a mirror in a line is the same for either.
|
|
42
95
|
struct WaterOutlineLine {
|
|
@@ -62,54 +115,119 @@ fn water_outline_line(geometry_index: u32, triangle: u32, point: vec2<f32>, scal
|
|
|
62
115
|
return line;
|
|
63
116
|
}
|
|
64
117
|
|
|
118
|
+
// The particle's mirror image in a line of the outline: its front's centre and its direction mirrored. Not yet reflected.
|
|
119
|
+
fn water_particle_mirrored(particle: ${WATER_PARTICLE_STRUCT.wgsl_ref}, line: WaterOutlineLine) -> ${WATER_PARTICLE_STRUCT.wgsl_ref} {
|
|
120
|
+
var image = particle;
|
|
121
|
+
let direction = water_particle_direction(particle.angle);
|
|
122
|
+
let mirrored = direction - 2.0 * dot(direction, line.normal) * line.normal;
|
|
123
|
+
image.origin -= 2.0 * dot(particle.origin - line.point, line.normal) * line.normal;
|
|
124
|
+
image.angle = atan2(mirrored.y, mirrored.x);
|
|
125
|
+
image.reflected = 0u;
|
|
126
|
+
return image;
|
|
127
|
+
}
|
|
128
|
+
|
|
65
129
|
// Advance a particle by dt. scale turns the body's metric frame into its local one; reflection is the
|
|
66
|
-
// outline's amplitude reflection coefficient; threshold is the steepness |a|/r below which it dies
|
|
130
|
+
// outline's amplitude reflection coefficient; threshold is the steepness |a|/r below which it dies; hint is what is
|
|
131
|
+
// already known of its leading arc.
|
|
67
132
|
fn water_particle_advance(input: ${WATER_PARTICLE_STRUCT.wgsl_ref}, dt: f32, geometry_index: u32, scale: vec2<f32>,
|
|
68
|
-
reflection: f32, threshold: f32) -> WaterParticleStep {
|
|
133
|
+
reflection: f32, threshold: f32, hint: WaterParticleArcHint) -> WaterParticleStep {
|
|
69
134
|
var particle = input;
|
|
70
|
-
let before = water_particle_position(particle);
|
|
71
135
|
let distance_before = water_particle_distance(particle);
|
|
136
|
+
let half_length_before = water_particle_half_length(particle);
|
|
72
137
|
particle.age += dt;
|
|
73
138
|
let distance_after = water_particle_distance(particle);
|
|
74
|
-
// energy spread over the widening circumference
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (water_particle_over_body(geometry_index, after / scale).t < 0.0) {
|
|
79
|
-
let start = water_particle_over_body(geometry_index, before / scale);
|
|
80
|
-
if (!(reflection > 0.0) || start.t < 0.0) { return WaterParticleStep(particle, 0u); }
|
|
81
|
-
// where along this frame's step it left the mesh
|
|
82
|
-
var lo = 0.0;
|
|
83
|
-
var hi = 1.0;
|
|
84
|
-
var last = start;
|
|
85
|
-
for (var halving = 0u; halving < 6u; halving++) {
|
|
86
|
-
let mid = 0.5 * (lo + hi);
|
|
87
|
-
let probe = water_particle_over_body(geometry_index, mix(before, after, mid) / scale);
|
|
88
|
-
if (probe.t >= 0.0) { lo = mid; last = probe; } else { hi = mid; }
|
|
89
|
-
}
|
|
90
|
-
// the mirror image of its front in the outline's own line, wherever along the step it was crossed
|
|
91
|
-
let line = water_outline_line(geometry_index, last.triangle, mix(before, after, lo), scale);
|
|
92
|
-
let normal = line.normal;
|
|
93
|
-
let direction = water_particle_direction(particle.angle);
|
|
94
|
-
let reflected = direction - 2.0 * dot(direction, normal) * normal;
|
|
95
|
-
particle.origin -= 2.0 * dot(particle.origin - line.point, normal) * normal;
|
|
96
|
-
particle.angle = atan2(reflected.y, reflected.x);
|
|
97
|
-
particle.amplitude *= reflection;
|
|
98
|
-
// a corner can send the mirror image off the mesh too
|
|
99
|
-
if (water_particle_over_body(geometry_index, water_particle_position(particle) / scale).t < 0.0) {
|
|
100
|
-
return WaterParticleStep(particle, 0u);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
139
|
+
// energy spread over the widening circumference and the lengthening envelope (Jeschke & Wojtan 2017 eq. 17), and the
|
|
140
|
+
// water's own damping
|
|
141
|
+
particle.amplitude *= sqrt(max(distance_before, 1e-30) / max(distance_after, 1e-30)
|
|
142
|
+
* half_length_before / water_particle_half_length(particle)) * exp(-water_particle_damping(particle.radius) * dt);
|
|
103
143
|
// below the threshold a packet fades out rather than vanishing: its amplitude decays on top of the water's own
|
|
104
144
|
// damping until it is a tenth of the threshold's height, where it dies
|
|
105
145
|
let floor = threshold * particle.radius;
|
|
106
146
|
if (!(abs(particle.amplitude) >= floor)) {
|
|
107
147
|
particle.amplitude *= exp(-dt / WATER_PARTICLE_FADE_SECONDS);
|
|
108
|
-
if (!(abs(particle.amplitude) >= 0.1 * floor)) { return WaterParticleStep(particle, 0u); }
|
|
148
|
+
if (!(abs(particle.amplitude) >= 0.1 * floor)) { return WaterParticleStep(particle, particle, 0u); }
|
|
149
|
+
}
|
|
150
|
+
// its support: its envelope's half-length either side of its front, its window's angle either side of its axis
|
|
151
|
+
let reach = water_particle_half_length(particle);
|
|
152
|
+
// whether any of it may still be over the body
|
|
153
|
+
var remains = true;
|
|
154
|
+
var image = particle;
|
|
155
|
+
var mirrored = false;
|
|
156
|
+
if (particle.reflected == 0u) {
|
|
157
|
+
// which of its leading arc's tested points are over the body: what the hint knows, then the axis's by a down-ray,
|
|
158
|
+
// the ends by the triangle under it, which covers them away from the outline, and the rest by a down-ray each
|
|
159
|
+
let ahead = distance_after + reach;
|
|
160
|
+
var known = hint.known;
|
|
161
|
+
var over = hint.over & known;
|
|
162
|
+
var triangle = hint.triangle;
|
|
163
|
+
if ((known & 1u) == 0u) {
|
|
164
|
+
let hit = water_particle_over_body(geometry_index, water_particle_support_point(particle, 0u, ahead) / scale);
|
|
165
|
+
known |= 1u;
|
|
166
|
+
over |= select(0u, 1u, hit.t >= 0.0);
|
|
167
|
+
triangle = hit.triangle;
|
|
168
|
+
}
|
|
169
|
+
if ((over & 1u) != 0u && (known & 6u) != 6u) {
|
|
170
|
+
var a: vec3<f32>;
|
|
171
|
+
var b: vec3<f32>;
|
|
172
|
+
var c: vec3<f32>;
|
|
173
|
+
geometry_read_triangle_positions(&a, &b, &c, geometries[geometry_index], triangle);
|
|
174
|
+
for (var k = 1u; k < 3u; k++) {
|
|
175
|
+
if (((known >> k) & 1u) == 0u && water_triangle_covers(a.xz, b.xz, c.xz, water_particle_support_point(particle, k, ahead) / scale)) {
|
|
176
|
+
known |= 1u << k;
|
|
177
|
+
over |= 1u << k;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
for (var k = 1u; k < 3u; k++) {
|
|
182
|
+
if (((known >> k) & 1u) == 0u
|
|
183
|
+
&& water_particle_over_body(geometry_index, water_particle_support_point(particle, k, ahead) / scale).t >= 0.0) { over |= 1u << k; }
|
|
184
|
+
}
|
|
185
|
+
// the first of them to leave the body: one off it whose radius was over it where its trailing arc was when the
|
|
186
|
+
// step began. With none over it there, the point is not leaving the body but entering it, as a mirror image's
|
|
187
|
+
// do: every radius of one starts beyond its outline.
|
|
188
|
+
var crossing = 3u;
|
|
189
|
+
var back = vec2<f32>(0.0);
|
|
190
|
+
var last: ${RAY_HIT_INFO.wgsl_ref};
|
|
191
|
+
for (var k = 0u; k < 3u && crossing == 3u; k++) {
|
|
192
|
+
if (((over >> k) & 1u) != 0u) { continue; }
|
|
193
|
+
back = water_particle_support_point(particle, k, distance_before - reach);
|
|
194
|
+
last = water_particle_over_body(geometry_index, back / scale);
|
|
195
|
+
if (last.t >= 0.0) { crossing = k; }
|
|
196
|
+
}
|
|
197
|
+
if (crossing < 3u) {
|
|
198
|
+
// it is kept this step: if all of it has left the body too, it dies at the next, unseen beyond the outline
|
|
199
|
+
particle.reflected = 1u;
|
|
200
|
+
// where from there that radius leaves the body
|
|
201
|
+
let leading = water_particle_support_point(particle, crossing, ahead);
|
|
202
|
+
var lo = 0.0;
|
|
203
|
+
var hi = 1.0;
|
|
204
|
+
for (var halving = 0u; halving < 10u; halving++) {
|
|
205
|
+
let mid = 0.5 * (lo + hi);
|
|
206
|
+
let probe = water_particle_over_body(geometry_index, mix(back, leading, mid) / scale);
|
|
207
|
+
if (probe.t >= 0.0) { lo = mid; last = probe; } else { hi = mid; }
|
|
208
|
+
}
|
|
209
|
+
// the mirror image of its front in the outline's own line, wherever that was crossed
|
|
210
|
+
image = water_particle_mirrored(particle, water_outline_line(geometry_index, last.triangle, mix(back, leading, lo), scale));
|
|
211
|
+
image.amplitude *= reflection;
|
|
212
|
+
mirrored = abs(image.amplitude) >= 0.1 * floor;
|
|
213
|
+
} else if (over == 0u) {
|
|
214
|
+
// with none of them over the body and none leaving it, all of it is beyond the outline: a splash off the
|
|
215
|
+
// mesh, or a mirror image landing off it at a corner (one entering the body has the point it was mirrored
|
|
216
|
+
// from over it)
|
|
217
|
+
remains = false;
|
|
218
|
+
}
|
|
219
|
+
} else {
|
|
220
|
+
remains = water_particle_arc_over(particle, geometry_index, scale, distance_after - reach);
|
|
109
221
|
}
|
|
110
|
-
// Yuksel's subdivision, at a coarser spacing: the
|
|
111
|
-
|
|
112
|
-
|
|
222
|
+
// Yuksel's subdivision, at a coarser spacing: the three windows of a third of the angle sum to the front's height
|
|
223
|
+
// as the parents' did, so the surface does not change. A window splits once its arc along the front is longer than
|
|
224
|
+
// its envelope is across it, so a packet's support stays about round, whatever its stretch. An image is as wide as
|
|
225
|
+
// its packet and splits with it.
|
|
226
|
+
let split = select(1u, 3u, water_particle_width(particle) > water_particle_half_length(particle));
|
|
227
|
+
// a reflected packet lives until the last of it has left the body
|
|
228
|
+
if (!remains) { return WaterParticleStep(particle, particle, 0u); }
|
|
229
|
+
if (mirrored) { return WaterParticleStep(particle, image, 2u * split); }
|
|
230
|
+
return WaterParticleStep(particle, particle, split);
|
|
113
231
|
}
|
|
114
232
|
|
|
115
233
|
// The death threshold: a packet flatter than the body's own wind waves is not drawn, and the fuller the pool,
|
|
@@ -118,4 +236,4 @@ fn water_particle_threshold(slope_variance: f32, live: u32, capacity: u32) -> f3
|
|
|
118
236
|
let pressure = f32(live) / f32(max(capacity, 1u));
|
|
119
237
|
return max(0.003, 0.1 * sqrt(max(slope_variance, 0.0))) / max(1.0 - pressure, 0.05);
|
|
120
238
|
}
|
|
121
|
-
`, [chunk_water_particle, chunk_ray_query_blas_nearest]);
|
|
239
|
+
`, [chunk_water_particle, chunk_ray_query_blas_nearest, chunk_geometry_read_triangle_positions]);
|