@scenography/plugin-water 0.2.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.
@@ -0,0 +1,42 @@
1
+ let _scenography_scene = require("@scenography/scene");
2
+ //#region src/data/validate.ts
3
+ const validateWater = (water) => (0, _scenography_scene.validate)([
4
+ (0, _scenography_scene.existsProp)("size", "Water is missing a size."),
5
+ (0, _scenography_scene.propIs)("size", _scenography_scene.isVec2, "size must be [width, depth]."),
6
+ (0, _scenography_scene.propIs)("center", _scenography_scene.isVec2, "center must be [x, z]."),
7
+ (0, _scenography_scene.propIs)("level", _scenography_scene.isFiniteNumber, "level must be a finite number."),
8
+ ...[
9
+ "waveHeight",
10
+ "waveScale",
11
+ "waveSpeed"
12
+ ].map((key) => (0, _scenography_scene.propIs)(key, _scenography_scene.isNonNegativeNumber, `${key} must be a non-negative number.`)),
13
+ (0, _scenography_scene.propIs)("depthScale", _scenography_scene.isPositiveNumber, "depthScale must be a positive number."),
14
+ (0, _scenography_scene.propIs)("foam", _scenography_scene.isNonNegativeNumber, "foam must be a non-negative number.")
15
+ ])(water);
16
+ //#endregion
17
+ //#region src/data/plugin.ts
18
+ /**
19
+ * The data half of the water plugin: owns the `water` scene key (a singleton)
20
+ * and validates it. Pure — no three.js; pass it to `defineScene` so scenes
21
+ * using water validate without loading the runtime.
22
+ */
23
+ const waterData = {
24
+ kind: "water",
25
+ cardinality: "single",
26
+ validate: (water) => validateWater(water)
27
+ };
28
+ //#endregion
29
+ Object.defineProperty(exports, "validateWater", {
30
+ enumerable: true,
31
+ get: function() {
32
+ return validateWater;
33
+ }
34
+ });
35
+ Object.defineProperty(exports, "waterData", {
36
+ enumerable: true,
37
+ get: function() {
38
+ return waterData;
39
+ }
40
+ });
41
+
42
+ //# sourceMappingURL=data-BI-VQXjw.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-BI-VQXjw.cjs","names":["isVec2","isFiniteNumber","isNonNegativeNumber","isPositiveNumber"],"sources":["../src/data/validate.ts","../src/data/plugin.ts"],"sourcesContent":["import {\n existsProp,\n isFiniteNumber,\n isNonNegativeNumber,\n isPositiveNumber,\n isVec2,\n propIs,\n validate,\n type ValidationResult,\n} from \"@scenography/scene\";\n\nimport type { Water } from \"./water\";\n\nexport const validateWater = (water: Water): ValidationResult =>\n validate<Water>([\n existsProp(\"size\", \"Water is missing a size.\"),\n propIs(\"size\", isVec2, \"size must be [width, depth].\"),\n propIs(\"center\", isVec2, \"center must be [x, z].\"),\n propIs(\"level\", isFiniteNumber, \"level must be a finite number.\"),\n ...([\"waveHeight\", \"waveScale\", \"waveSpeed\"] as const).map((key) =>\n propIs(key, isNonNegativeNumber, `${key} must be a non-negative number.`),\n ),\n propIs(\"depthScale\", isPositiveNumber, \"depthScale must be a positive number.\"),\n propIs(\"foam\", isNonNegativeNumber, \"foam must be a non-negative number.\"),\n ])(water);\n","import type { DataPlugin } from \"@scenography/scene\";\n\nimport { validateWater } from \"./validate\";\nimport type { Water } from \"./water\";\n\n/**\n * The data half of the water plugin: owns the `water` scene key (a singleton)\n * and validates it. Pure — no three.js; pass it to `defineScene` so scenes\n * using water validate without loading the runtime.\n */\nexport const waterData: DataPlugin<Water> = {\n kind: \"water\",\n cardinality: \"single\",\n validate: (water) => validateWater(water),\n};\n"],"mappings":";;AAaA,MAAa,iBAAiB,WAAA,GAAA,mBAAA,SAAA,CACZ;oCACH,QAAQ,0BAA0B;gCACtC,QAAQA,mBAAAA,QAAQ,8BAA8B;gCAC9C,UAAUA,mBAAAA,QAAQ,wBAAwB;gCAC1C,SAASC,mBAAAA,gBAAgB,gCAAgC;CAChE,GAAI;EAAC;EAAc;EAAa;CAAW,CAAC,CAAW,KAAK,SAAA,GAAA,mBAAA,OAAA,CACnD,KAAKC,mBAAAA,qBAAqB,GAAG,IAAI,gCAAgC,CAC1E;gCACO,cAAcC,mBAAAA,kBAAkB,uCAAuC;gCACvE,QAAQD,mBAAAA,qBAAqB,qCAAqC;AAC3E,CAAC,CAAC,CAAC,KAAK;;;;;;;;ACdV,MAAa,YAA+B;CAC1C,MAAM;CACN,aAAa;CACb,WAAW,UAAU,cAAc,KAAK;AAC1C"}
@@ -0,0 +1,31 @@
1
+ import { existsProp, isFiniteNumber, isNonNegativeNumber, isPositiveNumber, isVec2, propIs, validate } from "@scenography/scene";
2
+ //#region src/data/validate.ts
3
+ const validateWater = (water) => validate([
4
+ existsProp("size", "Water is missing a size."),
5
+ propIs("size", isVec2, "size must be [width, depth]."),
6
+ propIs("center", isVec2, "center must be [x, z]."),
7
+ propIs("level", isFiniteNumber, "level must be a finite number."),
8
+ ...[
9
+ "waveHeight",
10
+ "waveScale",
11
+ "waveSpeed"
12
+ ].map((key) => propIs(key, isNonNegativeNumber, `${key} must be a non-negative number.`)),
13
+ propIs("depthScale", isPositiveNumber, "depthScale must be a positive number."),
14
+ propIs("foam", isNonNegativeNumber, "foam must be a non-negative number.")
15
+ ])(water);
16
+ //#endregion
17
+ //#region src/data/plugin.ts
18
+ /**
19
+ * The data half of the water plugin: owns the `water` scene key (a singleton)
20
+ * and validates it. Pure — no three.js; pass it to `defineScene` so scenes
21
+ * using water validate without loading the runtime.
22
+ */
23
+ const waterData = {
24
+ kind: "water",
25
+ cardinality: "single",
26
+ validate: (water) => validateWater(water)
27
+ };
28
+ //#endregion
29
+ export { validateWater as n, waterData as t };
30
+
31
+ //# sourceMappingURL=data-CAd_goXP.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-CAd_goXP.mjs","names":[],"sources":["../src/data/validate.ts","../src/data/plugin.ts"],"sourcesContent":["import {\n existsProp,\n isFiniteNumber,\n isNonNegativeNumber,\n isPositiveNumber,\n isVec2,\n propIs,\n validate,\n type ValidationResult,\n} from \"@scenography/scene\";\n\nimport type { Water } from \"./water\";\n\nexport const validateWater = (water: Water): ValidationResult =>\n validate<Water>([\n existsProp(\"size\", \"Water is missing a size.\"),\n propIs(\"size\", isVec2, \"size must be [width, depth].\"),\n propIs(\"center\", isVec2, \"center must be [x, z].\"),\n propIs(\"level\", isFiniteNumber, \"level must be a finite number.\"),\n ...([\"waveHeight\", \"waveScale\", \"waveSpeed\"] as const).map((key) =>\n propIs(key, isNonNegativeNumber, `${key} must be a non-negative number.`),\n ),\n propIs(\"depthScale\", isPositiveNumber, \"depthScale must be a positive number.\"),\n propIs(\"foam\", isNonNegativeNumber, \"foam must be a non-negative number.\"),\n ])(water);\n","import type { DataPlugin } from \"@scenography/scene\";\n\nimport { validateWater } from \"./validate\";\nimport type { Water } from \"./water\";\n\n/**\n * The data half of the water plugin: owns the `water` scene key (a singleton)\n * and validates it. Pure — no three.js; pass it to `defineScene` so scenes\n * using water validate without loading the runtime.\n */\nexport const waterData: DataPlugin<Water> = {\n kind: \"water\",\n cardinality: \"single\",\n validate: (water) => validateWater(water),\n};\n"],"mappings":";;AAaA,MAAa,iBAAiB,UAC5B,SAAgB;CACd,WAAW,QAAQ,0BAA0B;CAC7C,OAAO,QAAQ,QAAQ,8BAA8B;CACrD,OAAO,UAAU,QAAQ,wBAAwB;CACjD,OAAO,SAAS,gBAAgB,gCAAgC;CAChE,GAAI;EAAC;EAAc;EAAa;CAAW,CAAC,CAAW,KAAK,QAC1D,OAAO,KAAK,qBAAqB,GAAG,IAAI,gCAAgC,CAC1E;CACA,OAAO,cAAc,kBAAkB,uCAAuC;CAC9E,OAAO,QAAQ,qBAAqB,qCAAqC;AAC3E,CAAC,CAAC,CAAC,KAAK;;;;;;;;ACdV,MAAa,YAA+B;CAC1C,MAAM;CACN,aAAa;CACb,WAAW,UAAU,cAAc,KAAK;AAC1C"}
package/dist/data.cjs ADDED
@@ -0,0 +1,4 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_data = require("./data-BI-VQXjw.cjs");
3
+ exports.validateWater = require_data.validateWater;
4
+ exports.waterData = require_data.waterData;
@@ -0,0 +1,2 @@
1
+ import { n as validateWater, r as Water, t as waterData } from "./index-BLdh03xr.cjs";
2
+ export { type Water, validateWater, waterData };
@@ -0,0 +1,2 @@
1
+ import { n as validateWater, r as Water, t as waterData } from "./index-BLdh03xr.mjs";
2
+ export { type Water, validateWater, waterData };
package/dist/data.mjs ADDED
@@ -0,0 +1,2 @@
1
+ import { n as validateWater, t as waterData } from "./data-CAd_goXP.mjs";
2
+ export { validateWater, waterData };
@@ -0,0 +1,59 @@
1
+ import { DataPlugin, ValidationResult, Vec2 } from "@scenography/scene";
2
+
3
+ //#region src/data/water.d.ts
4
+ /**
5
+ * A flat body of water — a transparent surface that refracts whatever opaque
6
+ * geometry sits below it (so the sea floor shows through). Pure data: the plane,
7
+ * its level, a color the water tints toward, and an optional ripple. The runtime
8
+ * builds it with a physically-based transmissive material.
9
+ */
10
+ interface Water {
11
+ /** Plane extent `[width (x), depth (z)]` in world units. */
12
+ size: Vec2;
13
+ /** Center on the ground plane `[x, z]`. Defaults to `[0, 0]`. */
14
+ center?: Vec2;
15
+ /** Surface level (world Y). Defaults to `0`. */
16
+ level?: number;
17
+ /** Color the water tints toward (CSS). Defaults to a sea green-blue. */
18
+ color?: string;
19
+ /** Animate the surface with FBM waves (evolving normals, not a scrolling map). Defaults to `true`. */
20
+ ripple?: boolean;
21
+ /** Wave amplitude — how bumpy the surface normals get. Default `6`. */
22
+ waveHeight?: number;
23
+ /** Wave spatial frequency — higher is choppier/smaller waves. Default `0.02`. */
24
+ waveScale?: number;
25
+ /** Wave animation speed. Default `0.6`. */
26
+ waveSpeed?: number;
27
+ /**
28
+ * Deep-water tint (CSS). The surface leans toward it with depth (depth is read
29
+ * from the floor below — its base `y` plus heightmap relief). Defaults to a
30
+ * deep teal.
31
+ */
32
+ depthColor?: string;
33
+ /** Depth (world units) over which color approaches `depthColor`. Default `100`. */
34
+ depthScale?: number;
35
+ /** Shoreline foam band width (world units of depth). `0` = none. Default `12`. */
36
+ foam?: number;
37
+ /** Foam color (CSS). Default white. */
38
+ foamColor?: string;
39
+ }
40
+ declare module "@scenography/scene" {
41
+ interface Scene {
42
+ /** A body of water (sea, pool…) — provided by `@scenography/plugin-water`. */
43
+ water?: Water;
44
+ }
45
+ }
46
+ //#endregion
47
+ //#region src/data/validate.d.ts
48
+ declare const validateWater: (water: Water) => ValidationResult;
49
+ //#endregion
50
+ //#region src/data/plugin.d.ts
51
+ /**
52
+ * The data half of the water plugin: owns the `water` scene key (a singleton)
53
+ * and validates it. Pure — no three.js; pass it to `defineScene` so scenes
54
+ * using water validate without loading the runtime.
55
+ */
56
+ declare const waterData: DataPlugin<Water>;
57
+ //#endregion
58
+ export { validateWater as n, Water as r, waterData as t };
59
+ //# sourceMappingURL=index-BLdh03xr.d.cts.map
@@ -0,0 +1,59 @@
1
+ import { DataPlugin, ValidationResult, Vec2 } from "@scenography/scene";
2
+
3
+ //#region src/data/water.d.ts
4
+ /**
5
+ * A flat body of water — a transparent surface that refracts whatever opaque
6
+ * geometry sits below it (so the sea floor shows through). Pure data: the plane,
7
+ * its level, a color the water tints toward, and an optional ripple. The runtime
8
+ * builds it with a physically-based transmissive material.
9
+ */
10
+ interface Water {
11
+ /** Plane extent `[width (x), depth (z)]` in world units. */
12
+ size: Vec2;
13
+ /** Center on the ground plane `[x, z]`. Defaults to `[0, 0]`. */
14
+ center?: Vec2;
15
+ /** Surface level (world Y). Defaults to `0`. */
16
+ level?: number;
17
+ /** Color the water tints toward (CSS). Defaults to a sea green-blue. */
18
+ color?: string;
19
+ /** Animate the surface with FBM waves (evolving normals, not a scrolling map). Defaults to `true`. */
20
+ ripple?: boolean;
21
+ /** Wave amplitude — how bumpy the surface normals get. Default `6`. */
22
+ waveHeight?: number;
23
+ /** Wave spatial frequency — higher is choppier/smaller waves. Default `0.02`. */
24
+ waveScale?: number;
25
+ /** Wave animation speed. Default `0.6`. */
26
+ waveSpeed?: number;
27
+ /**
28
+ * Deep-water tint (CSS). The surface leans toward it with depth (depth is read
29
+ * from the floor below — its base `y` plus heightmap relief). Defaults to a
30
+ * deep teal.
31
+ */
32
+ depthColor?: string;
33
+ /** Depth (world units) over which color approaches `depthColor`. Default `100`. */
34
+ depthScale?: number;
35
+ /** Shoreline foam band width (world units of depth). `0` = none. Default `12`. */
36
+ foam?: number;
37
+ /** Foam color (CSS). Default white. */
38
+ foamColor?: string;
39
+ }
40
+ declare module "@scenography/scene" {
41
+ interface Scene {
42
+ /** A body of water (sea, pool…) — provided by `@scenography/plugin-water`. */
43
+ water?: Water;
44
+ }
45
+ }
46
+ //#endregion
47
+ //#region src/data/validate.d.ts
48
+ declare const validateWater: (water: Water) => ValidationResult;
49
+ //#endregion
50
+ //#region src/data/plugin.d.ts
51
+ /**
52
+ * The data half of the water plugin: owns the `water` scene key (a singleton)
53
+ * and validates it. Pure — no three.js; pass it to `defineScene` so scenes
54
+ * using water validate without loading the runtime.
55
+ */
56
+ declare const waterData: DataPlugin<Water>;
57
+ //#endregion
58
+ export { validateWater as n, Water as r, waterData as t };
59
+ //# sourceMappingURL=index-BLdh03xr.d.mts.map
package/dist/index.cjs ADDED
@@ -0,0 +1,244 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_data = require("./data-BI-VQXjw.cjs");
3
+ let _scenography_engine = require("@scenography/engine");
4
+ let three = require("three");
5
+ //#region src/buildWater.ts
6
+ const WAVE_GLSL = `
7
+ varying vec3 vWaterWorld;
8
+ uniform float uTime, uWaterLevel, uFloorY, uFloorHeightScale, uDepthScale, uFoam;
9
+ uniform float uWaveHeight, uWaveScale, uWaveSpeed;
10
+ uniform vec2 uFloorCenter, uFloorSize;
11
+ uniform sampler2D uFloorHeightmap;
12
+ uniform vec3 uDepthColor, uFoamColor;
13
+
14
+ float wHash(vec2 p){ float h = dot(p, vec2(127.1, 311.7)); return fract(sin(h) * 43758.5453123); }
15
+ float wNoise(vec2 p){
16
+ vec2 i = floor(p), f = fract(p);
17
+ vec2 u = f * f * (3.0 - 2.0 * f);
18
+ return -1.0 + 2.0 * mix(mix(wHash(i + vec2(0.0,0.0)), wHash(i + vec2(1.0,0.0)), u.x),
19
+ mix(wHash(i + vec2(0.0,1.0)), wHash(i + vec2(1.0,1.0)), u.x), u.y);
20
+ }
21
+ float seaOctave(vec2 uv, float choppy){
22
+ uv += wNoise(uv);
23
+ vec2 wv = 1.0 - abs(sin(uv));
24
+ vec2 swv = abs(cos(uv));
25
+ wv = mix(wv, swv, wv);
26
+ return pow(1.0 - pow(wv.x * wv.y, 0.65), choppy);
27
+ }
28
+ float waveHeight(vec2 p){
29
+ float t = uTime * uWaveSpeed;
30
+ vec2 uv = p * uWaveScale; uv.x *= 0.75;
31
+ float freq = 1.0, amp = 1.0, choppy = 4.0, h = 0.0;
32
+ mat2 m = mat2(1.6, 1.2, -1.2, 1.6);
33
+ for(int i = 0; i < 3; i++){
34
+ float d = seaOctave((uv + t) * freq, choppy) + seaOctave((uv - t) * freq, choppy);
35
+ h += d * amp;
36
+ uv = m * uv; freq *= 1.9; amp *= 0.22; choppy = mix(choppy, 1.0, 0.2);
37
+ }
38
+ return h * uWaveHeight;
39
+ }
40
+ `;
41
+ /**
42
+ * Build a `Water` surface: a horizontal plane with a transmissive physical
43
+ * material (refracts the sea floor so the pebbles show through), upgraded with a
44
+ * custom shader that (1) ripples the surface normal with FBM waves that evolve in
45
+ * place, (2) tints by depth (the bottom is read from the `floor`'s base `y` plus
46
+ * its heightmap relief — no depth prepass), and (3) paints an irregular,
47
+ * advancing/receding shoreline foam line. Set
48
+ * `ripple: false` for a still, glassy surface.
49
+ */
50
+ function buildWater(water, floor) {
51
+ const [width, depth] = water.size;
52
+ const [cx, cz] = water.center ?? [0, 0];
53
+ const level = water.level ?? 0;
54
+ const rippleOn = water.ripple !== false;
55
+ const material = new three.MeshPhysicalMaterial({
56
+ color: "#ffffff",
57
+ roughness: .12,
58
+ metalness: 0,
59
+ transmission: 1,
60
+ ior: 1.33,
61
+ thickness: 100,
62
+ attenuationColor: new three.Color(water.color ?? "#3b7d78"),
63
+ attenuationDistance: 400
64
+ });
65
+ const [fcx, fcz] = floor?.center ?? [0, 0];
66
+ const [fw, fd] = floor?.size ?? [1, 1];
67
+ const floorHeightScale = floor?.heightmap ? floor.heightScale ?? 0 : 0;
68
+ let bottomMap;
69
+ if (floor?.heightmap && floorHeightScale !== 0) bottomMap = new three.TextureLoader().load(floor.heightmap);
70
+ else {
71
+ bottomMap = new three.DataTexture(new Uint8Array([
72
+ 0,
73
+ 0,
74
+ 0,
75
+ 255
76
+ ]), 1, 1);
77
+ bottomMap.needsUpdate = true;
78
+ }
79
+ const reflectionTarget = new three.WebGLRenderTarget(1, 1);
80
+ const uniforms = {
81
+ uTime: { value: 0 },
82
+ uWaterLevel: { value: level },
83
+ uFloorY: { value: floor?.y ?? 0 },
84
+ uFloorCenter: { value: new three.Vector2(fcx, fcz) },
85
+ uFloorSize: { value: new three.Vector2(fw, fd) },
86
+ uFloorHeightmap: { value: bottomMap },
87
+ uFloorHeightScale: { value: floorHeightScale },
88
+ uDepthColor: { value: new three.Color(water.depthColor ?? "#0d3b40") },
89
+ uDepthScale: { value: water.depthScale ?? 100 },
90
+ uFoam: { value: water.foam ?? 12 },
91
+ uFoamColor: { value: new three.Color(water.foamColor ?? "#eaf6f6") },
92
+ uWaveHeight: { value: rippleOn ? water.waveHeight ?? 6 : 0 },
93
+ uWaveScale: { value: water.waveScale ?? .02 },
94
+ uWaveSpeed: { value: water.waveSpeed ?? .6 },
95
+ uReflection: { value: reflectionTarget.texture },
96
+ uReflectionMatrix: { value: new three.Matrix4() }
97
+ };
98
+ material.onBeforeCompile = (shader) => {
99
+ Object.assign(shader.uniforms, uniforms);
100
+ shader.vertexShader = shader.vertexShader.replace("#include <common>", "#include <common>\nvarying vec3 vWaterWorld;\nvarying vec4 vReflectCoord;\nuniform mat4 uReflectionMatrix;").replace("#include <begin_vertex>", `#include <begin_vertex>
101
+ vWaterWorld = (modelMatrix * vec4(transformed, 1.0)).xyz;
102
+ vReflectCoord = uReflectionMatrix * vec4(transformed, 1.0);`);
103
+ shader.fragmentShader = shader.fragmentShader.replace("#include <common>", `#include <common>\n${WAVE_GLSL}\nvarying vec4 vReflectCoord;\nuniform sampler2D uReflection;\nvec2 gWaveSlope = vec2(0.0);`).replace("#include <normal_fragment_maps>", `#include <normal_fragment_maps>
104
+ if (uWaveHeight > 0.0) {
105
+ vec2 wp = vWaterWorld.xz;
106
+ float e = 2.0;
107
+ float hC = waveHeight(wp);
108
+ float dhdx = (waveHeight(wp + vec2(e, 0.0)) - hC) / e;
109
+ float dhdz = (waveHeight(wp + vec2(0.0, e)) - hC) / e;
110
+ vec3 wn = normalize(vec3(-dhdx, 1.0, -dhdz));
111
+ normal = normalize((viewMatrix * vec4(wn, 0.0)).xyz);
112
+ gWaveSlope = vec2(dhdx, dhdz); // fed to the reflection for ripples
113
+ }`).replace("#include <opaque_fragment>", `#include <opaque_fragment>
114
+ {
115
+ // Floor world (x, z) → heightmap UV. Matches the floor builder's
116
+ // mapping; v is flipped because three.js textures flip Y on upload.
117
+ vec2 huv = clamp((vWaterWorld.xz - uFloorCenter) / uFloorSize + 0.5, 0.0, 1.0);
118
+ float bottom = uFloorY + texture2D(uFloorHeightmap, huv).r * uFloorHeightScale;
119
+ float wdepth = max(uWaterLevel - bottom, 0.0);
120
+ float deep = 1.0 - exp(-wdepth / uDepthScale);
121
+ gl_FragColor.rgb = mix(gl_FragColor.rgb, uDepthColor, deep);
122
+ // Planar reflection: the mirrored scene (walls occlude the sky) sampled
123
+ // at this fragment's projected position, rippled by the wave slope and
124
+ // Fresnel-weighted so it dominates at grazing angles. This replaces the
125
+ // sky-only environment reflection, so the glint respects the walls.
126
+ vec2 rUv = vReflectCoord.xy / vReflectCoord.w + gWaveSlope * 0.06;
127
+ vec3 reflColor = texture2D(uReflection, rUv).rgb;
128
+ float fres = clamp(pow(1.0 - abs(dot(normalize(vViewPosition), normal)), 5.0), 0.0, 1.0);
129
+ gl_FragColor.rgb = mix(gl_FragColor.rgb, reflColor, mix(0.15, 0.95, fres));
130
+ float foam = 0.0;
131
+ if (uFoam > 0.0) {
132
+ // A thin lacy band hugging the waterline (depth 0..uFoam), not a wash
133
+ // across open water. A little noise wiggles its inner edge so the
134
+ // straight plane cut reads as an irregular shoreline; marching sine
135
+ // fronts add waves running up the shore.
136
+ float wiggle = wNoise(vWaterWorld.xz * 0.03 + uTime * 0.1) * 0.5 + 0.5;
137
+ float wd = max(wdepth + (wiggle - 0.5) * uFoam * 0.6, 0.0);
138
+ float band = 1.0 - smoothstep(0.0, uFoam, wd);
139
+ float lace = 0.5 + 0.5 * wNoise(vWaterWorld.xz * 0.1 - uTime * 0.25);
140
+ float fronts = 0.5 + 0.5 * sin(wd * 0.2 - uTime * 1.5);
141
+ foam = smoothstep(0.12, 0.5, band * band * lace * fronts);
142
+ gl_FragColor.rgb = mix(gl_FragColor.rgb, uFoamColor, foam);
143
+ }
144
+ gl_FragColor.a = max(gl_FragColor.a, max(deep * 0.7, foam));
145
+ }`);
146
+ };
147
+ material.customProgramCacheKey = () => "scenography-water";
148
+ const mesh = new three.Mesh(new three.PlaneGeometry(width, depth), material);
149
+ mesh.rotation.x = -Math.PI / 2;
150
+ mesh.position.set(cx, level, cz);
151
+ mesh.name = "water";
152
+ mesh.receiveShadow = true;
153
+ const update = (deltaMs) => {
154
+ uniforms.uTime.value += deltaMs / 1e3;
155
+ };
156
+ const reflectionCamera = new three.PerspectiveCamera();
157
+ const reflectorNormal = new three.Vector3(0, 1, 0);
158
+ const reflectorPos = new three.Vector3(cx, level, cz);
159
+ const reflectorPlane = new three.Plane();
160
+ const camPos = new three.Vector3();
161
+ const rot = new three.Matrix4();
162
+ const view = new three.Vector3();
163
+ const lookAt = new three.Vector3();
164
+ const target = new three.Vector3();
165
+ const clip = new three.Vector4();
166
+ const q = new three.Vector4();
167
+ const size = new three.Vector2();
168
+ const beforeRender = (renderer, scene, camera) => {
169
+ renderer.getDrawingBufferSize(size);
170
+ if (reflectionTarget.width !== size.x || reflectionTarget.height !== size.y) reflectionTarget.setSize(Math.max(1, size.x), Math.max(1, size.y));
171
+ camera.updateMatrixWorld();
172
+ mesh.updateMatrixWorld();
173
+ camPos.setFromMatrixPosition(camera.matrixWorld);
174
+ view.subVectors(reflectorPos, camPos);
175
+ if (view.dot(reflectorNormal) > 0) return;
176
+ view.reflect(reflectorNormal).negate().add(reflectorPos);
177
+ rot.extractRotation(camera.matrixWorld);
178
+ lookAt.set(0, 0, -1).applyMatrix4(rot).add(camPos);
179
+ target.subVectors(reflectorPos, lookAt).reflect(reflectorNormal).negate().add(reflectorPos);
180
+ reflectionCamera.position.copy(view);
181
+ reflectionCamera.up.set(0, 1, 0).applyMatrix4(rot).reflect(reflectorNormal);
182
+ reflectionCamera.lookAt(target);
183
+ reflectionCamera.far = camera.far;
184
+ reflectionCamera.updateMatrixWorld();
185
+ reflectionCamera.projectionMatrix.copy(camera.projectionMatrix);
186
+ const m = uniforms.uReflectionMatrix.value;
187
+ m.set(.5, 0, 0, .5, 0, .5, 0, .5, 0, 0, .5, .5, 0, 0, 0, 1);
188
+ m.multiply(reflectionCamera.projectionMatrix);
189
+ m.multiply(reflectionCamera.matrixWorldInverse);
190
+ m.multiply(mesh.matrixWorld);
191
+ reflectorPlane.setFromNormalAndCoplanarPoint(reflectorNormal, reflectorPos);
192
+ reflectorPlane.applyMatrix4(reflectionCamera.matrixWorldInverse);
193
+ clip.set(reflectorPlane.normal.x, reflectorPlane.normal.y, reflectorPlane.normal.z, reflectorPlane.constant);
194
+ const e = reflectionCamera.projectionMatrix.elements;
195
+ q.set((Math.sign(clip.x) + e[8]) / e[0], (Math.sign(clip.y) + e[9]) / e[5], -1, (1 + e[10]) / e[14]);
196
+ clip.multiplyScalar(2 / clip.dot(q));
197
+ e[2] = clip.x;
198
+ e[6] = clip.y;
199
+ e[10] = clip.z + 1;
200
+ e[14] = clip.w;
201
+ const prevTarget = renderer.getRenderTarget();
202
+ const prevShadowAuto = renderer.shadowMap.autoUpdate;
203
+ mesh.visible = false;
204
+ renderer.shadowMap.autoUpdate = false;
205
+ renderer.setRenderTarget(reflectionTarget);
206
+ renderer.clear();
207
+ renderer.render(scene, reflectionCamera);
208
+ renderer.setRenderTarget(prevTarget);
209
+ renderer.shadowMap.autoUpdate = prevShadowAuto;
210
+ mesh.visible = true;
211
+ };
212
+ const dispose = () => {
213
+ reflectionTarget.dispose();
214
+ };
215
+ return {
216
+ object: mesh,
217
+ update,
218
+ beforeRender,
219
+ dispose
220
+ };
221
+ }
222
+ //#endregion
223
+ //#region src/plugin.ts
224
+ /**
225
+ * The water plugin: materializes the `water` scene key as a transparent,
226
+ * depth-tinted surface with FBM waves and shoreline foam. Consumes the
227
+ * `terrain` capability (published by the floor) so the depth/foam shader can
228
+ * sample the same heightmap the ground is displaced by — and rebuilds when the
229
+ * floor changes (`dependsOn`).
230
+ */
231
+ const water = () => ({
232
+ data: require_data.waterData,
233
+ dependsOn: [_scenography_engine.TERRAIN],
234
+ build: (definition, ctx) => {
235
+ return buildWater(definition, ctx.capabilities.get(_scenography_engine.TERRAIN)?.definition);
236
+ }
237
+ });
238
+ //#endregion
239
+ exports.buildWater = buildWater;
240
+ exports.validateWater = require_data.validateWater;
241
+ exports.water = water;
242
+ exports.waterData = require_data.waterData;
243
+
244
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["MeshPhysicalMaterial","Color","TextureLoader","DataTexture","WebGLRenderTarget","Vector2","Matrix4","Mesh","PlaneGeometry","PerspectiveCamera","Vector3","Plane","Vector4","waterData","TERRAIN"],"sources":["../src/buildWater.ts","../src/plugin.ts"],"sourcesContent":["import {\n Color,\n DataTexture,\n Matrix4,\n Mesh,\n MeshPhysicalMaterial,\n PerspectiveCamera,\n Plane,\n PlaneGeometry,\n TextureLoader,\n Vector2,\n Vector3,\n Vector4,\n WebGLRenderTarget,\n type Object3D,\n type PerspectiveCamera as PerspectiveCameraType,\n type Scene as ThreeScene,\n type WebGLRenderer,\n type WebGLProgramParametersWithUniforms,\n} from \"three\";\nimport type { TerrainDefinition } from \"@scenography/engine\";\n\nimport type { Water } from \"./data\";\n\n/** A built water surface: the mesh plus its per-frame update and reflection pass. */\nexport interface BuiltWater {\n object: Object3D;\n update: (deltaMs: number) => void;\n beforeRender: (renderer: WebGLRenderer, scene: ThreeScene, camera: PerspectiveCameraType) => void;\n dispose: () => void;\n}\n\n// FBM wave + noise helpers, borrowed from \"Seascape\" by Alexander Alekseev (TDM,\n// CC BY-NC-SA 3.0): waves evolve in place (not a scrolling texture) so the calm\n// surface doesn't look like a repeating slide. Injected into the fragment shader.\nconst WAVE_GLSL = /* glsl */ `\n varying vec3 vWaterWorld;\n uniform float uTime, uWaterLevel, uFloorY, uFloorHeightScale, uDepthScale, uFoam;\n uniform float uWaveHeight, uWaveScale, uWaveSpeed;\n uniform vec2 uFloorCenter, uFloorSize;\n uniform sampler2D uFloorHeightmap;\n uniform vec3 uDepthColor, uFoamColor;\n\n float wHash(vec2 p){ float h = dot(p, vec2(127.1, 311.7)); return fract(sin(h) * 43758.5453123); }\n float wNoise(vec2 p){\n vec2 i = floor(p), f = fract(p);\n vec2 u = f * f * (3.0 - 2.0 * f);\n return -1.0 + 2.0 * mix(mix(wHash(i + vec2(0.0,0.0)), wHash(i + vec2(1.0,0.0)), u.x),\n mix(wHash(i + vec2(0.0,1.0)), wHash(i + vec2(1.0,1.0)), u.x), u.y);\n }\n float seaOctave(vec2 uv, float choppy){\n uv += wNoise(uv);\n vec2 wv = 1.0 - abs(sin(uv));\n vec2 swv = abs(cos(uv));\n wv = mix(wv, swv, wv);\n return pow(1.0 - pow(wv.x * wv.y, 0.65), choppy);\n }\n float waveHeight(vec2 p){\n float t = uTime * uWaveSpeed;\n vec2 uv = p * uWaveScale; uv.x *= 0.75;\n float freq = 1.0, amp = 1.0, choppy = 4.0, h = 0.0;\n mat2 m = mat2(1.6, 1.2, -1.2, 1.6);\n for(int i = 0; i < 3; i++){\n float d = seaOctave((uv + t) * freq, choppy) + seaOctave((uv - t) * freq, choppy);\n h += d * amp;\n uv = m * uv; freq *= 1.9; amp *= 0.22; choppy = mix(choppy, 1.0, 0.2);\n }\n return h * uWaveHeight;\n }\n`;\n\n/**\n * Build a `Water` surface: a horizontal plane with a transmissive physical\n * material (refracts the sea floor so the pebbles show through), upgraded with a\n * custom shader that (1) ripples the surface normal with FBM waves that evolve in\n * place, (2) tints by depth (the bottom is read from the `floor`'s base `y` plus\n * its heightmap relief — no depth prepass), and (3) paints an irregular,\n * advancing/receding shoreline foam line. Set\n * `ripple: false` for a still, glassy surface.\n */\nexport function buildWater(water: Water, floor?: TerrainDefinition): BuiltWater {\n const [width, depth] = water.size;\n const [cx, cz] = water.center ?? [0, 0];\n const level = water.level ?? 0;\n const rippleOn = water.ripple !== false;\n\n const material = new MeshPhysicalMaterial({\n color: \"#ffffff\",\n roughness: 0.12,\n metalness: 0,\n transmission: 1,\n ior: 1.33,\n thickness: 100,\n attenuationColor: new Color(water.color ?? \"#3b7d78\"),\n attenuationDistance: 400,\n });\n\n // The depth/foam shader samples the floor's heightmap to know the bottom under\n // each fragment (there is no depth prepass). A 1×1 black texture keeps the\n // sampler bound when the floor is flat (bottom = uFloorY everywhere).\n const [fcx, fcz] = floor?.center ?? [0, 0];\n const [fw, fd] = floor?.size ?? [1, 1];\n const floorHeightScale = floor?.heightmap ? floor.heightScale ?? 0 : 0;\n let bottomMap;\n if (floor?.heightmap && floorHeightScale !== 0) {\n bottomMap = new TextureLoader().load(floor.heightmap);\n } else {\n bottomMap = new DataTexture(new Uint8Array([0, 0, 0, 255]), 1, 1);\n bottomMap.needsUpdate = true;\n }\n // Planar reflection: the scene rendered mirrored across the water plane, so\n // walls (and everything else) occlude the sky in the reflection.\n const reflectionTarget = new WebGLRenderTarget(1, 1);\n const uniforms = {\n uTime: { value: 0 },\n uWaterLevel: { value: level },\n uFloorY: { value: floor?.y ?? 0 },\n uFloorCenter: { value: new Vector2(fcx, fcz) },\n uFloorSize: { value: new Vector2(fw, fd) },\n uFloorHeightmap: { value: bottomMap },\n uFloorHeightScale: { value: floorHeightScale },\n uDepthColor: { value: new Color(water.depthColor ?? \"#0d3b40\") },\n uDepthScale: { value: water.depthScale ?? 100 },\n uFoam: { value: water.foam ?? 12 },\n uFoamColor: { value: new Color(water.foamColor ?? \"#eaf6f6\") },\n uWaveHeight: { value: rippleOn ? water.waveHeight ?? 6 : 0 },\n uWaveScale: { value: water.waveScale ?? 0.02 },\n uWaveSpeed: { value: water.waveSpeed ?? 0.6 },\n uReflection: { value: reflectionTarget.texture },\n uReflectionMatrix: { value: new Matrix4() },\n };\n\n material.onBeforeCompile = (shader: WebGLProgramParametersWithUniforms): void => {\n Object.assign(shader.uniforms, uniforms);\n\n shader.vertexShader = shader.vertexShader\n .replace(\n \"#include <common>\",\n \"#include <common>\\nvarying vec3 vWaterWorld;\\nvarying vec4 vReflectCoord;\\nuniform mat4 uReflectionMatrix;\",\n )\n .replace(\n \"#include <begin_vertex>\",\n `#include <begin_vertex>\n vWaterWorld = (modelMatrix * vec4(transformed, 1.0)).xyz;\n vReflectCoord = uReflectionMatrix * vec4(transformed, 1.0);`,\n );\n\n shader.fragmentShader = shader.fragmentShader\n .replace(\n \"#include <common>\",\n `#include <common>\\n${WAVE_GLSL}\\nvarying vec4 vReflectCoord;\\nuniform sampler2D uReflection;\\nvec2 gWaveSlope = vec2(0.0);`,\n )\n // Replace the flat surface normal with the FBM wave normal (world → view),\n // so refraction and sun glints move organically. Runs after the map normals.\n .replace(\n \"#include <normal_fragment_maps>\",\n `#include <normal_fragment_maps>\n if (uWaveHeight > 0.0) {\n vec2 wp = vWaterWorld.xz;\n float e = 2.0;\n float hC = waveHeight(wp);\n float dhdx = (waveHeight(wp + vec2(e, 0.0)) - hC) / e;\n float dhdz = (waveHeight(wp + vec2(0.0, e)) - hC) / e;\n vec3 wn = normalize(vec3(-dhdx, 1.0, -dhdz));\n normal = normalize((viewMatrix * vec4(wn, 0.0)).xyz);\n gWaveSlope = vec2(dhdx, dhdz); // fed to the reflection for ripples\n }`,\n )\n // Depth tint + irregular, breathing shoreline foam, after the final color.\n .replace(\n \"#include <opaque_fragment>\",\n `#include <opaque_fragment>\n {\n // Floor world (x, z) → heightmap UV. Matches the floor builder's\n // mapping; v is flipped because three.js textures flip Y on upload.\n vec2 huv = clamp((vWaterWorld.xz - uFloorCenter) / uFloorSize + 0.5, 0.0, 1.0);\n float bottom = uFloorY + texture2D(uFloorHeightmap, huv).r * uFloorHeightScale;\n float wdepth = max(uWaterLevel - bottom, 0.0);\n float deep = 1.0 - exp(-wdepth / uDepthScale);\n gl_FragColor.rgb = mix(gl_FragColor.rgb, uDepthColor, deep);\n // Planar reflection: the mirrored scene (walls occlude the sky) sampled\n // at this fragment's projected position, rippled by the wave slope and\n // Fresnel-weighted so it dominates at grazing angles. This replaces the\n // sky-only environment reflection, so the glint respects the walls.\n vec2 rUv = vReflectCoord.xy / vReflectCoord.w + gWaveSlope * 0.06;\n vec3 reflColor = texture2D(uReflection, rUv).rgb;\n float fres = clamp(pow(1.0 - abs(dot(normalize(vViewPosition), normal)), 5.0), 0.0, 1.0);\n gl_FragColor.rgb = mix(gl_FragColor.rgb, reflColor, mix(0.15, 0.95, fres));\n float foam = 0.0;\n if (uFoam > 0.0) {\n // A thin lacy band hugging the waterline (depth 0..uFoam), not a wash\n // across open water. A little noise wiggles its inner edge so the\n // straight plane cut reads as an irregular shoreline; marching sine\n // fronts add waves running up the shore.\n float wiggle = wNoise(vWaterWorld.xz * 0.03 + uTime * 0.1) * 0.5 + 0.5;\n float wd = max(wdepth + (wiggle - 0.5) * uFoam * 0.6, 0.0);\n float band = 1.0 - smoothstep(0.0, uFoam, wd);\n float lace = 0.5 + 0.5 * wNoise(vWaterWorld.xz * 0.1 - uTime * 0.25);\n float fronts = 0.5 + 0.5 * sin(wd * 0.2 - uTime * 1.5);\n foam = smoothstep(0.12, 0.5, band * band * lace * fronts);\n gl_FragColor.rgb = mix(gl_FragColor.rgb, uFoamColor, foam);\n }\n gl_FragColor.a = max(gl_FragColor.a, max(deep * 0.7, foam));\n }`,\n );\n };\n // Keep this material's program separate from other physical materials'.\n material.customProgramCacheKey = (): string => \"scenography-water\";\n\n const mesh = new Mesh(new PlaneGeometry(width, depth), material);\n // PlaneGeometry faces +Z; lay it flat at the water level.\n mesh.rotation.x = -Math.PI / 2;\n mesh.position.set(cx, level, cz);\n mesh.name = \"water\";\n // Receive shadows so the sun's specular glint is cut where something (a wall)\n // casts a shadow onto the surface, instead of shining through it.\n mesh.receiveShadow = true;\n\n const update = (deltaMs: number): void => {\n uniforms.uTime.value += deltaMs / 1000;\n };\n\n // Reusable objects for the per-frame reflection pass (mirror the main camera\n // across the water plane, then render the scene into `reflectionTarget`).\n const reflectionCamera = new PerspectiveCamera();\n const reflectorNormal = new Vector3(0, 1, 0);\n const reflectorPos = new Vector3(cx, level, cz);\n const reflectorPlane = new Plane();\n const camPos = new Vector3();\n const rot = new Matrix4();\n const view = new Vector3();\n const lookAt = new Vector3();\n const target = new Vector3();\n const clip = new Vector4();\n const q = new Vector4();\n const size = new Vector2();\n\n const beforeRender = (\n renderer: WebGLRenderer,\n scene: ThreeScene,\n camera: PerspectiveCameraType,\n ): void => {\n renderer.getDrawingBufferSize(size);\n if (reflectionTarget.width !== size.x || reflectionTarget.height !== size.y) {\n reflectionTarget.setSize(Math.max(1, size.x), Math.max(1, size.y));\n }\n camera.updateMatrixWorld();\n mesh.updateMatrixWorld();\n camPos.setFromMatrixPosition(camera.matrixWorld);\n view.subVectors(reflectorPos, camPos);\n if (view.dot(reflectorNormal) > 0) return; // camera below the surface — skip\n view.reflect(reflectorNormal).negate().add(reflectorPos);\n rot.extractRotation(camera.matrixWorld);\n lookAt.set(0, 0, -1).applyMatrix4(rot).add(camPos);\n target.subVectors(reflectorPos, lookAt).reflect(reflectorNormal).negate().add(reflectorPos);\n reflectionCamera.position.copy(view);\n reflectionCamera.up.set(0, 1, 0).applyMatrix4(rot).reflect(reflectorNormal);\n reflectionCamera.lookAt(target);\n reflectionCamera.far = camera.far;\n reflectionCamera.updateMatrixWorld();\n reflectionCamera.projectionMatrix.copy(camera.projectionMatrix);\n\n // Texture matrix: bias · proj · viewInv · meshWorld → sample by fragment.\n const m = uniforms.uReflectionMatrix.value;\n m.set(0.5, 0, 0, 0.5, 0, 0.5, 0, 0.5, 0, 0, 0.5, 0.5, 0, 0, 0, 1);\n m.multiply(reflectionCamera.projectionMatrix);\n m.multiply(reflectionCamera.matrixWorldInverse);\n m.multiply(mesh.matrixWorld);\n\n // Oblique near-plane clip at the water surface, so geometry below it isn't\n // mirrored into the reflection (standard Reflector trick).\n reflectorPlane.setFromNormalAndCoplanarPoint(reflectorNormal, reflectorPos);\n reflectorPlane.applyMatrix4(reflectionCamera.matrixWorldInverse);\n clip.set(reflectorPlane.normal.x, reflectorPlane.normal.y, reflectorPlane.normal.z, reflectorPlane.constant);\n const e = reflectionCamera.projectionMatrix.elements;\n q.set((Math.sign(clip.x) + e[8]!) / e[0]!, (Math.sign(clip.y) + e[9]!) / e[5]!, -1, (1 + e[10]!) / e[14]!);\n clip.multiplyScalar(2 / clip.dot(q));\n e[2] = clip.x;\n e[6] = clip.y;\n e[10] = clip.z + 1;\n e[14] = clip.w;\n\n const prevTarget = renderer.getRenderTarget();\n const prevShadowAuto = renderer.shadowMap.autoUpdate;\n mesh.visible = false; // don't mirror the water into itself\n renderer.shadowMap.autoUpdate = false; // reuse the main shadow map\n renderer.setRenderTarget(reflectionTarget);\n renderer.clear();\n renderer.render(scene, reflectionCamera);\n renderer.setRenderTarget(prevTarget);\n renderer.shadowMap.autoUpdate = prevShadowAuto;\n mesh.visible = true;\n };\n\n const dispose = (): void => {\n reflectionTarget.dispose();\n };\n\n return { object: mesh, update, beforeRender, dispose };\n}\n","import { TERRAIN, type EnginePlugin, type TerrainCapability } from \"@scenography/engine\";\n\nimport { buildWater } from \"./buildWater\";\nimport { waterData } from \"./data\";\nimport type { Water } from \"./data\";\n\n/**\n * The water plugin: materializes the `water` scene key as a transparent,\n * depth-tinted surface with FBM waves and shoreline foam. Consumes the\n * `terrain` capability (published by the floor) so the depth/foam shader can\n * sample the same heightmap the ground is displaced by — and rebuilds when the\n * floor changes (`dependsOn`).\n */\nexport const water = (): EnginePlugin<Water> => ({\n data: waterData,\n dependsOn: [TERRAIN],\n build: (definition, ctx) => {\n const terrain = ctx.capabilities.get<TerrainCapability>(TERRAIN);\n return buildWater(definition, terrain?.definition);\n },\n});\n"],"mappings":";;;;;AAmCA,MAAM,YAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6C7B,SAAgB,WAAW,OAAc,OAAuC;CAC9E,MAAM,CAAC,OAAO,SAAS,MAAM;CAC7B,MAAM,CAAC,IAAI,MAAM,MAAM,UAAU,CAAC,GAAG,CAAC;CACtC,MAAM,QAAQ,MAAM,SAAS;CAC7B,MAAM,WAAW,MAAM,WAAW;CAElC,MAAM,WAAW,IAAIA,MAAAA,qBAAqB;EACxC,OAAO;EACP,WAAW;EACX,WAAW;EACX,cAAc;EACd,KAAK;EACL,WAAW;EACX,kBAAkB,IAAIC,MAAAA,MAAM,MAAM,SAAS,SAAS;EACpD,qBAAqB;CACvB,CAAC;CAKD,MAAM,CAAC,KAAK,OAAO,OAAO,UAAU,CAAC,GAAG,CAAC;CACzC,MAAM,CAAC,IAAI,MAAM,OAAO,QAAQ,CAAC,GAAG,CAAC;CACrC,MAAM,mBAAmB,OAAO,YAAY,MAAM,eAAe,IAAI;CACrE,IAAI;CACJ,IAAI,OAAO,aAAa,qBAAqB,GAC3C,YAAY,IAAIC,MAAAA,cAAc,CAAC,CAAC,KAAK,MAAM,SAAS;MAC/C;EACL,YAAY,IAAIC,MAAAA,YAAY,IAAI,WAAW;GAAC;GAAG;GAAG;GAAG;EAAG,CAAC,GAAG,GAAG,CAAC;EAChE,UAAU,cAAc;CAC1B;CAGA,MAAM,mBAAmB,IAAIC,MAAAA,kBAAkB,GAAG,CAAC;CACnD,MAAM,WAAW;EACf,OAAO,EAAE,OAAO,EAAE;EAClB,aAAa,EAAE,OAAO,MAAM;EAC5B,SAAS,EAAE,OAAO,OAAO,KAAK,EAAE;EAChC,cAAc,EAAE,OAAO,IAAIC,MAAAA,QAAQ,KAAK,GAAG,EAAE;EAC7C,YAAY,EAAE,OAAO,IAAIA,MAAAA,QAAQ,IAAI,EAAE,EAAE;EACzC,iBAAiB,EAAE,OAAO,UAAU;EACpC,mBAAmB,EAAE,OAAO,iBAAiB;EAC7C,aAAa,EAAE,OAAO,IAAIJ,MAAAA,MAAM,MAAM,cAAc,SAAS,EAAE;EAC/D,aAAa,EAAE,OAAO,MAAM,cAAc,IAAI;EAC9C,OAAO,EAAE,OAAO,MAAM,QAAQ,GAAG;EACjC,YAAY,EAAE,OAAO,IAAIA,MAAAA,MAAM,MAAM,aAAa,SAAS,EAAE;EAC7D,aAAa,EAAE,OAAO,WAAW,MAAM,cAAc,IAAI,EAAE;EAC3D,YAAY,EAAE,OAAO,MAAM,aAAa,IAAK;EAC7C,YAAY,EAAE,OAAO,MAAM,aAAa,GAAI;EAC5C,aAAa,EAAE,OAAO,iBAAiB,QAAQ;EAC/C,mBAAmB,EAAE,OAAO,IAAIK,MAAAA,QAAQ,EAAE;CAC5C;CAEA,SAAS,mBAAmB,WAAqD;EAC/E,OAAO,OAAO,OAAO,UAAU,QAAQ;EAEvC,OAAO,eAAe,OAAO,aAC1B,QACC,qBACA,4GACF,CAAC,CACA,QACC,2BACA;;oEAGF;EAEF,OAAO,iBAAiB,OAAO,eAC5B,QACC,qBACA,sBAAsB,UAAU,4FAClC,CAAC,CAGA,QACC,mCACA;;;;;;;;;;UAWF,CAAC,CAEA,QACC,8BACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAiCF;CACJ;CAEA,SAAS,8BAAsC;CAE/C,MAAM,OAAO,IAAIC,MAAAA,KAAK,IAAIC,MAAAA,cAAc,OAAO,KAAK,GAAG,QAAQ;CAE/D,KAAK,SAAS,IAAI,CAAC,KAAK,KAAK;CAC7B,KAAK,SAAS,IAAI,IAAI,OAAO,EAAE;CAC/B,KAAK,OAAO;CAGZ,KAAK,gBAAgB;CAErB,MAAM,UAAU,YAA0B;EACxC,SAAS,MAAM,SAAS,UAAU;CACpC;CAIA,MAAM,mBAAmB,IAAIC,MAAAA,kBAAkB;CAC/C,MAAM,kBAAkB,IAAIC,MAAAA,QAAQ,GAAG,GAAG,CAAC;CAC3C,MAAM,eAAe,IAAIA,MAAAA,QAAQ,IAAI,OAAO,EAAE;CAC9C,MAAM,iBAAiB,IAAIC,MAAAA,MAAM;CACjC,MAAM,SAAS,IAAID,MAAAA,QAAQ;CAC3B,MAAM,MAAM,IAAIJ,MAAAA,QAAQ;CACxB,MAAM,OAAO,IAAII,MAAAA,QAAQ;CACzB,MAAM,SAAS,IAAIA,MAAAA,QAAQ;CAC3B,MAAM,SAAS,IAAIA,MAAAA,QAAQ;CAC3B,MAAM,OAAO,IAAIE,MAAAA,QAAQ;CACzB,MAAM,IAAI,IAAIA,MAAAA,QAAQ;CACtB,MAAM,OAAO,IAAIP,MAAAA,QAAQ;CAEzB,MAAM,gBACJ,UACA,OACA,WACS;EACT,SAAS,qBAAqB,IAAI;EAClC,IAAI,iBAAiB,UAAU,KAAK,KAAK,iBAAiB,WAAW,KAAK,GACxE,iBAAiB,QAAQ,KAAK,IAAI,GAAG,KAAK,CAAC,GAAG,KAAK,IAAI,GAAG,KAAK,CAAC,CAAC;EAEnE,OAAO,kBAAkB;EACzB,KAAK,kBAAkB;EACvB,OAAO,sBAAsB,OAAO,WAAW;EAC/C,KAAK,WAAW,cAAc,MAAM;EACpC,IAAI,KAAK,IAAI,eAAe,IAAI,GAAG;EACnC,KAAK,QAAQ,eAAe,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,YAAY;EACvD,IAAI,gBAAgB,OAAO,WAAW;EACtC,OAAO,IAAI,GAAG,GAAG,EAAE,CAAC,CAAC,aAAa,GAAG,CAAC,CAAC,IAAI,MAAM;EACjD,OAAO,WAAW,cAAc,MAAM,CAAC,CAAC,QAAQ,eAAe,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,YAAY;EAC1F,iBAAiB,SAAS,KAAK,IAAI;EACnC,iBAAiB,GAAG,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,aAAa,GAAG,CAAC,CAAC,QAAQ,eAAe;EAC1E,iBAAiB,OAAO,MAAM;EAC9B,iBAAiB,MAAM,OAAO;EAC9B,iBAAiB,kBAAkB;EACnC,iBAAiB,iBAAiB,KAAK,OAAO,gBAAgB;EAG9D,MAAM,IAAI,SAAS,kBAAkB;EACrC,EAAE,IAAI,IAAK,GAAG,GAAG,IAAK,GAAG,IAAK,GAAG,IAAK,GAAG,GAAG,IAAK,IAAK,GAAG,GAAG,GAAG,CAAC;EAChE,EAAE,SAAS,iBAAiB,gBAAgB;EAC5C,EAAE,SAAS,iBAAiB,kBAAkB;EAC9C,EAAE,SAAS,KAAK,WAAW;EAI3B,eAAe,8BAA8B,iBAAiB,YAAY;EAC1E,eAAe,aAAa,iBAAiB,kBAAkB;EAC/D,KAAK,IAAI,eAAe,OAAO,GAAG,eAAe,OAAO,GAAG,eAAe,OAAO,GAAG,eAAe,QAAQ;EAC3G,MAAM,IAAI,iBAAiB,iBAAiB;EAC5C,EAAE,KAAK,KAAK,KAAK,KAAK,CAAC,IAAI,EAAE,MAAO,EAAE,KAAM,KAAK,KAAK,KAAK,CAAC,IAAI,EAAE,MAAO,EAAE,IAAK,KAAK,IAAI,EAAE,OAAQ,EAAE,GAAI;EACzG,KAAK,eAAe,IAAI,KAAK,IAAI,CAAC,CAAC;EACnC,EAAE,KAAK,KAAK;EACZ,EAAE,KAAK,KAAK;EACZ,EAAE,MAAM,KAAK,IAAI;EACjB,EAAE,MAAM,KAAK;EAEb,MAAM,aAAa,SAAS,gBAAgB;EAC5C,MAAM,iBAAiB,SAAS,UAAU;EAC1C,KAAK,UAAU;EACf,SAAS,UAAU,aAAa;EAChC,SAAS,gBAAgB,gBAAgB;EACzC,SAAS,MAAM;EACf,SAAS,OAAO,OAAO,gBAAgB;EACvC,SAAS,gBAAgB,UAAU;EACnC,SAAS,UAAU,aAAa;EAChC,KAAK,UAAU;CACjB;CAEA,MAAM,gBAAsB;EAC1B,iBAAiB,QAAQ;CAC3B;CAEA,OAAO;EAAE,QAAQ;EAAM;EAAQ;EAAc;CAAQ;AACvD;;;;;;;;;;AC9RA,MAAa,eAAoC;CAC/C,MAAMQ,aAAAA;CACN,WAAW,CAACC,oBAAAA,OAAO;CACnB,QAAQ,YAAY,QAAQ;EAE1B,OAAO,WAAW,YADF,IAAI,aAAa,IAAuBA,oBAAAA,OACpB,CAAC,EAAE,UAAU;CACnD;AACF"}
@@ -0,0 +1,35 @@
1
+ import { n as validateWater, r as Water, t as waterData } from "./index-BLdh03xr.cjs";
2
+ import { EnginePlugin, TerrainDefinition } from "@scenography/engine";
3
+ import { Object3D, PerspectiveCamera, Scene, WebGLRenderer } from "three";
4
+
5
+ //#region src/plugin.d.ts
6
+ /**
7
+ * The water plugin: materializes the `water` scene key as a transparent,
8
+ * depth-tinted surface with FBM waves and shoreline foam. Consumes the
9
+ * `terrain` capability (published by the floor) so the depth/foam shader can
10
+ * sample the same heightmap the ground is displaced by — and rebuilds when the
11
+ * floor changes (`dependsOn`).
12
+ */
13
+ declare const water: () => EnginePlugin<Water>;
14
+ //#endregion
15
+ //#region src/buildWater.d.ts
16
+ /** A built water surface: the mesh plus its per-frame update and reflection pass. */
17
+ interface BuiltWater {
18
+ object: Object3D;
19
+ update: (deltaMs: number) => void;
20
+ beforeRender: (renderer: WebGLRenderer, scene: Scene, camera: PerspectiveCamera) => void;
21
+ dispose: () => void;
22
+ }
23
+ /**
24
+ * Build a `Water` surface: a horizontal plane with a transmissive physical
25
+ * material (refracts the sea floor so the pebbles show through), upgraded with a
26
+ * custom shader that (1) ripples the surface normal with FBM waves that evolve in
27
+ * place, (2) tints by depth (the bottom is read from the `floor`'s base `y` plus
28
+ * its heightmap relief — no depth prepass), and (3) paints an irregular,
29
+ * advancing/receding shoreline foam line. Set
30
+ * `ripple: false` for a still, glassy surface.
31
+ */
32
+ declare function buildWater(water: Water, floor?: TerrainDefinition): BuiltWater;
33
+ //#endregion
34
+ export { type BuiltWater, type Water, buildWater, validateWater, water, waterData };
35
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1,35 @@
1
+ import { n as validateWater, r as Water, t as waterData } from "./index-BLdh03xr.mjs";
2
+ import { EnginePlugin, TerrainDefinition } from "@scenography/engine";
3
+ import { Object3D, PerspectiveCamera, Scene, WebGLRenderer } from "three";
4
+
5
+ //#region src/plugin.d.ts
6
+ /**
7
+ * The water plugin: materializes the `water` scene key as a transparent,
8
+ * depth-tinted surface with FBM waves and shoreline foam. Consumes the
9
+ * `terrain` capability (published by the floor) so the depth/foam shader can
10
+ * sample the same heightmap the ground is displaced by — and rebuilds when the
11
+ * floor changes (`dependsOn`).
12
+ */
13
+ declare const water: () => EnginePlugin<Water>;
14
+ //#endregion
15
+ //#region src/buildWater.d.ts
16
+ /** A built water surface: the mesh plus its per-frame update and reflection pass. */
17
+ interface BuiltWater {
18
+ object: Object3D;
19
+ update: (deltaMs: number) => void;
20
+ beforeRender: (renderer: WebGLRenderer, scene: Scene, camera: PerspectiveCamera) => void;
21
+ dispose: () => void;
22
+ }
23
+ /**
24
+ * Build a `Water` surface: a horizontal plane with a transmissive physical
25
+ * material (refracts the sea floor so the pebbles show through), upgraded with a
26
+ * custom shader that (1) ripples the surface normal with FBM waves that evolve in
27
+ * place, (2) tints by depth (the bottom is read from the `floor`'s base `y` plus
28
+ * its heightmap relief — no depth prepass), and (3) paints an irregular,
29
+ * advancing/receding shoreline foam line. Set
30
+ * `ripple: false` for a still, glassy surface.
31
+ */
32
+ declare function buildWater(water: Water, floor?: TerrainDefinition): BuiltWater;
33
+ //#endregion
34
+ export { type BuiltWater, type Water, buildWater, validateWater, water, waterData };
35
+ //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs ADDED
@@ -0,0 +1,240 @@
1
+ import { n as validateWater, t as waterData } from "./data-CAd_goXP.mjs";
2
+ import { TERRAIN } from "@scenography/engine";
3
+ import { Color, DataTexture, Matrix4, Mesh, MeshPhysicalMaterial, PerspectiveCamera, Plane, PlaneGeometry, TextureLoader, Vector2, Vector3, Vector4, WebGLRenderTarget } from "three";
4
+ //#region src/buildWater.ts
5
+ const WAVE_GLSL = `
6
+ varying vec3 vWaterWorld;
7
+ uniform float uTime, uWaterLevel, uFloorY, uFloorHeightScale, uDepthScale, uFoam;
8
+ uniform float uWaveHeight, uWaveScale, uWaveSpeed;
9
+ uniform vec2 uFloorCenter, uFloorSize;
10
+ uniform sampler2D uFloorHeightmap;
11
+ uniform vec3 uDepthColor, uFoamColor;
12
+
13
+ float wHash(vec2 p){ float h = dot(p, vec2(127.1, 311.7)); return fract(sin(h) * 43758.5453123); }
14
+ float wNoise(vec2 p){
15
+ vec2 i = floor(p), f = fract(p);
16
+ vec2 u = f * f * (3.0 - 2.0 * f);
17
+ return -1.0 + 2.0 * mix(mix(wHash(i + vec2(0.0,0.0)), wHash(i + vec2(1.0,0.0)), u.x),
18
+ mix(wHash(i + vec2(0.0,1.0)), wHash(i + vec2(1.0,1.0)), u.x), u.y);
19
+ }
20
+ float seaOctave(vec2 uv, float choppy){
21
+ uv += wNoise(uv);
22
+ vec2 wv = 1.0 - abs(sin(uv));
23
+ vec2 swv = abs(cos(uv));
24
+ wv = mix(wv, swv, wv);
25
+ return pow(1.0 - pow(wv.x * wv.y, 0.65), choppy);
26
+ }
27
+ float waveHeight(vec2 p){
28
+ float t = uTime * uWaveSpeed;
29
+ vec2 uv = p * uWaveScale; uv.x *= 0.75;
30
+ float freq = 1.0, amp = 1.0, choppy = 4.0, h = 0.0;
31
+ mat2 m = mat2(1.6, 1.2, -1.2, 1.6);
32
+ for(int i = 0; i < 3; i++){
33
+ float d = seaOctave((uv + t) * freq, choppy) + seaOctave((uv - t) * freq, choppy);
34
+ h += d * amp;
35
+ uv = m * uv; freq *= 1.9; amp *= 0.22; choppy = mix(choppy, 1.0, 0.2);
36
+ }
37
+ return h * uWaveHeight;
38
+ }
39
+ `;
40
+ /**
41
+ * Build a `Water` surface: a horizontal plane with a transmissive physical
42
+ * material (refracts the sea floor so the pebbles show through), upgraded with a
43
+ * custom shader that (1) ripples the surface normal with FBM waves that evolve in
44
+ * place, (2) tints by depth (the bottom is read from the `floor`'s base `y` plus
45
+ * its heightmap relief — no depth prepass), and (3) paints an irregular,
46
+ * advancing/receding shoreline foam line. Set
47
+ * `ripple: false` for a still, glassy surface.
48
+ */
49
+ function buildWater(water, floor) {
50
+ const [width, depth] = water.size;
51
+ const [cx, cz] = water.center ?? [0, 0];
52
+ const level = water.level ?? 0;
53
+ const rippleOn = water.ripple !== false;
54
+ const material = new MeshPhysicalMaterial({
55
+ color: "#ffffff",
56
+ roughness: .12,
57
+ metalness: 0,
58
+ transmission: 1,
59
+ ior: 1.33,
60
+ thickness: 100,
61
+ attenuationColor: new Color(water.color ?? "#3b7d78"),
62
+ attenuationDistance: 400
63
+ });
64
+ const [fcx, fcz] = floor?.center ?? [0, 0];
65
+ const [fw, fd] = floor?.size ?? [1, 1];
66
+ const floorHeightScale = floor?.heightmap ? floor.heightScale ?? 0 : 0;
67
+ let bottomMap;
68
+ if (floor?.heightmap && floorHeightScale !== 0) bottomMap = new TextureLoader().load(floor.heightmap);
69
+ else {
70
+ bottomMap = new DataTexture(new Uint8Array([
71
+ 0,
72
+ 0,
73
+ 0,
74
+ 255
75
+ ]), 1, 1);
76
+ bottomMap.needsUpdate = true;
77
+ }
78
+ const reflectionTarget = new WebGLRenderTarget(1, 1);
79
+ const uniforms = {
80
+ uTime: { value: 0 },
81
+ uWaterLevel: { value: level },
82
+ uFloorY: { value: floor?.y ?? 0 },
83
+ uFloorCenter: { value: new Vector2(fcx, fcz) },
84
+ uFloorSize: { value: new Vector2(fw, fd) },
85
+ uFloorHeightmap: { value: bottomMap },
86
+ uFloorHeightScale: { value: floorHeightScale },
87
+ uDepthColor: { value: new Color(water.depthColor ?? "#0d3b40") },
88
+ uDepthScale: { value: water.depthScale ?? 100 },
89
+ uFoam: { value: water.foam ?? 12 },
90
+ uFoamColor: { value: new Color(water.foamColor ?? "#eaf6f6") },
91
+ uWaveHeight: { value: rippleOn ? water.waveHeight ?? 6 : 0 },
92
+ uWaveScale: { value: water.waveScale ?? .02 },
93
+ uWaveSpeed: { value: water.waveSpeed ?? .6 },
94
+ uReflection: { value: reflectionTarget.texture },
95
+ uReflectionMatrix: { value: new Matrix4() }
96
+ };
97
+ material.onBeforeCompile = (shader) => {
98
+ Object.assign(shader.uniforms, uniforms);
99
+ shader.vertexShader = shader.vertexShader.replace("#include <common>", "#include <common>\nvarying vec3 vWaterWorld;\nvarying vec4 vReflectCoord;\nuniform mat4 uReflectionMatrix;").replace("#include <begin_vertex>", `#include <begin_vertex>
100
+ vWaterWorld = (modelMatrix * vec4(transformed, 1.0)).xyz;
101
+ vReflectCoord = uReflectionMatrix * vec4(transformed, 1.0);`);
102
+ shader.fragmentShader = shader.fragmentShader.replace("#include <common>", `#include <common>\n${WAVE_GLSL}\nvarying vec4 vReflectCoord;\nuniform sampler2D uReflection;\nvec2 gWaveSlope = vec2(0.0);`).replace("#include <normal_fragment_maps>", `#include <normal_fragment_maps>
103
+ if (uWaveHeight > 0.0) {
104
+ vec2 wp = vWaterWorld.xz;
105
+ float e = 2.0;
106
+ float hC = waveHeight(wp);
107
+ float dhdx = (waveHeight(wp + vec2(e, 0.0)) - hC) / e;
108
+ float dhdz = (waveHeight(wp + vec2(0.0, e)) - hC) / e;
109
+ vec3 wn = normalize(vec3(-dhdx, 1.0, -dhdz));
110
+ normal = normalize((viewMatrix * vec4(wn, 0.0)).xyz);
111
+ gWaveSlope = vec2(dhdx, dhdz); // fed to the reflection for ripples
112
+ }`).replace("#include <opaque_fragment>", `#include <opaque_fragment>
113
+ {
114
+ // Floor world (x, z) → heightmap UV. Matches the floor builder's
115
+ // mapping; v is flipped because three.js textures flip Y on upload.
116
+ vec2 huv = clamp((vWaterWorld.xz - uFloorCenter) / uFloorSize + 0.5, 0.0, 1.0);
117
+ float bottom = uFloorY + texture2D(uFloorHeightmap, huv).r * uFloorHeightScale;
118
+ float wdepth = max(uWaterLevel - bottom, 0.0);
119
+ float deep = 1.0 - exp(-wdepth / uDepthScale);
120
+ gl_FragColor.rgb = mix(gl_FragColor.rgb, uDepthColor, deep);
121
+ // Planar reflection: the mirrored scene (walls occlude the sky) sampled
122
+ // at this fragment's projected position, rippled by the wave slope and
123
+ // Fresnel-weighted so it dominates at grazing angles. This replaces the
124
+ // sky-only environment reflection, so the glint respects the walls.
125
+ vec2 rUv = vReflectCoord.xy / vReflectCoord.w + gWaveSlope * 0.06;
126
+ vec3 reflColor = texture2D(uReflection, rUv).rgb;
127
+ float fres = clamp(pow(1.0 - abs(dot(normalize(vViewPosition), normal)), 5.0), 0.0, 1.0);
128
+ gl_FragColor.rgb = mix(gl_FragColor.rgb, reflColor, mix(0.15, 0.95, fres));
129
+ float foam = 0.0;
130
+ if (uFoam > 0.0) {
131
+ // A thin lacy band hugging the waterline (depth 0..uFoam), not a wash
132
+ // across open water. A little noise wiggles its inner edge so the
133
+ // straight plane cut reads as an irregular shoreline; marching sine
134
+ // fronts add waves running up the shore.
135
+ float wiggle = wNoise(vWaterWorld.xz * 0.03 + uTime * 0.1) * 0.5 + 0.5;
136
+ float wd = max(wdepth + (wiggle - 0.5) * uFoam * 0.6, 0.0);
137
+ float band = 1.0 - smoothstep(0.0, uFoam, wd);
138
+ float lace = 0.5 + 0.5 * wNoise(vWaterWorld.xz * 0.1 - uTime * 0.25);
139
+ float fronts = 0.5 + 0.5 * sin(wd * 0.2 - uTime * 1.5);
140
+ foam = smoothstep(0.12, 0.5, band * band * lace * fronts);
141
+ gl_FragColor.rgb = mix(gl_FragColor.rgb, uFoamColor, foam);
142
+ }
143
+ gl_FragColor.a = max(gl_FragColor.a, max(deep * 0.7, foam));
144
+ }`);
145
+ };
146
+ material.customProgramCacheKey = () => "scenography-water";
147
+ const mesh = new Mesh(new PlaneGeometry(width, depth), material);
148
+ mesh.rotation.x = -Math.PI / 2;
149
+ mesh.position.set(cx, level, cz);
150
+ mesh.name = "water";
151
+ mesh.receiveShadow = true;
152
+ const update = (deltaMs) => {
153
+ uniforms.uTime.value += deltaMs / 1e3;
154
+ };
155
+ const reflectionCamera = new PerspectiveCamera();
156
+ const reflectorNormal = new Vector3(0, 1, 0);
157
+ const reflectorPos = new Vector3(cx, level, cz);
158
+ const reflectorPlane = new Plane();
159
+ const camPos = new Vector3();
160
+ const rot = new Matrix4();
161
+ const view = new Vector3();
162
+ const lookAt = new Vector3();
163
+ const target = new Vector3();
164
+ const clip = new Vector4();
165
+ const q = new Vector4();
166
+ const size = new Vector2();
167
+ const beforeRender = (renderer, scene, camera) => {
168
+ renderer.getDrawingBufferSize(size);
169
+ if (reflectionTarget.width !== size.x || reflectionTarget.height !== size.y) reflectionTarget.setSize(Math.max(1, size.x), Math.max(1, size.y));
170
+ camera.updateMatrixWorld();
171
+ mesh.updateMatrixWorld();
172
+ camPos.setFromMatrixPosition(camera.matrixWorld);
173
+ view.subVectors(reflectorPos, camPos);
174
+ if (view.dot(reflectorNormal) > 0) return;
175
+ view.reflect(reflectorNormal).negate().add(reflectorPos);
176
+ rot.extractRotation(camera.matrixWorld);
177
+ lookAt.set(0, 0, -1).applyMatrix4(rot).add(camPos);
178
+ target.subVectors(reflectorPos, lookAt).reflect(reflectorNormal).negate().add(reflectorPos);
179
+ reflectionCamera.position.copy(view);
180
+ reflectionCamera.up.set(0, 1, 0).applyMatrix4(rot).reflect(reflectorNormal);
181
+ reflectionCamera.lookAt(target);
182
+ reflectionCamera.far = camera.far;
183
+ reflectionCamera.updateMatrixWorld();
184
+ reflectionCamera.projectionMatrix.copy(camera.projectionMatrix);
185
+ const m = uniforms.uReflectionMatrix.value;
186
+ m.set(.5, 0, 0, .5, 0, .5, 0, .5, 0, 0, .5, .5, 0, 0, 0, 1);
187
+ m.multiply(reflectionCamera.projectionMatrix);
188
+ m.multiply(reflectionCamera.matrixWorldInverse);
189
+ m.multiply(mesh.matrixWorld);
190
+ reflectorPlane.setFromNormalAndCoplanarPoint(reflectorNormal, reflectorPos);
191
+ reflectorPlane.applyMatrix4(reflectionCamera.matrixWorldInverse);
192
+ clip.set(reflectorPlane.normal.x, reflectorPlane.normal.y, reflectorPlane.normal.z, reflectorPlane.constant);
193
+ const e = reflectionCamera.projectionMatrix.elements;
194
+ q.set((Math.sign(clip.x) + e[8]) / e[0], (Math.sign(clip.y) + e[9]) / e[5], -1, (1 + e[10]) / e[14]);
195
+ clip.multiplyScalar(2 / clip.dot(q));
196
+ e[2] = clip.x;
197
+ e[6] = clip.y;
198
+ e[10] = clip.z + 1;
199
+ e[14] = clip.w;
200
+ const prevTarget = renderer.getRenderTarget();
201
+ const prevShadowAuto = renderer.shadowMap.autoUpdate;
202
+ mesh.visible = false;
203
+ renderer.shadowMap.autoUpdate = false;
204
+ renderer.setRenderTarget(reflectionTarget);
205
+ renderer.clear();
206
+ renderer.render(scene, reflectionCamera);
207
+ renderer.setRenderTarget(prevTarget);
208
+ renderer.shadowMap.autoUpdate = prevShadowAuto;
209
+ mesh.visible = true;
210
+ };
211
+ const dispose = () => {
212
+ reflectionTarget.dispose();
213
+ };
214
+ return {
215
+ object: mesh,
216
+ update,
217
+ beforeRender,
218
+ dispose
219
+ };
220
+ }
221
+ //#endregion
222
+ //#region src/plugin.ts
223
+ /**
224
+ * The water plugin: materializes the `water` scene key as a transparent,
225
+ * depth-tinted surface with FBM waves and shoreline foam. Consumes the
226
+ * `terrain` capability (published by the floor) so the depth/foam shader can
227
+ * sample the same heightmap the ground is displaced by — and rebuilds when the
228
+ * floor changes (`dependsOn`).
229
+ */
230
+ const water = () => ({
231
+ data: waterData,
232
+ dependsOn: [TERRAIN],
233
+ build: (definition, ctx) => {
234
+ return buildWater(definition, ctx.capabilities.get(TERRAIN)?.definition);
235
+ }
236
+ });
237
+ //#endregion
238
+ export { buildWater, validateWater, water, waterData };
239
+
240
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/buildWater.ts","../src/plugin.ts"],"sourcesContent":["import {\n Color,\n DataTexture,\n Matrix4,\n Mesh,\n MeshPhysicalMaterial,\n PerspectiveCamera,\n Plane,\n PlaneGeometry,\n TextureLoader,\n Vector2,\n Vector3,\n Vector4,\n WebGLRenderTarget,\n type Object3D,\n type PerspectiveCamera as PerspectiveCameraType,\n type Scene as ThreeScene,\n type WebGLRenderer,\n type WebGLProgramParametersWithUniforms,\n} from \"three\";\nimport type { TerrainDefinition } from \"@scenography/engine\";\n\nimport type { Water } from \"./data\";\n\n/** A built water surface: the mesh plus its per-frame update and reflection pass. */\nexport interface BuiltWater {\n object: Object3D;\n update: (deltaMs: number) => void;\n beforeRender: (renderer: WebGLRenderer, scene: ThreeScene, camera: PerspectiveCameraType) => void;\n dispose: () => void;\n}\n\n// FBM wave + noise helpers, borrowed from \"Seascape\" by Alexander Alekseev (TDM,\n// CC BY-NC-SA 3.0): waves evolve in place (not a scrolling texture) so the calm\n// surface doesn't look like a repeating slide. Injected into the fragment shader.\nconst WAVE_GLSL = /* glsl */ `\n varying vec3 vWaterWorld;\n uniform float uTime, uWaterLevel, uFloorY, uFloorHeightScale, uDepthScale, uFoam;\n uniform float uWaveHeight, uWaveScale, uWaveSpeed;\n uniform vec2 uFloorCenter, uFloorSize;\n uniform sampler2D uFloorHeightmap;\n uniform vec3 uDepthColor, uFoamColor;\n\n float wHash(vec2 p){ float h = dot(p, vec2(127.1, 311.7)); return fract(sin(h) * 43758.5453123); }\n float wNoise(vec2 p){\n vec2 i = floor(p), f = fract(p);\n vec2 u = f * f * (3.0 - 2.0 * f);\n return -1.0 + 2.0 * mix(mix(wHash(i + vec2(0.0,0.0)), wHash(i + vec2(1.0,0.0)), u.x),\n mix(wHash(i + vec2(0.0,1.0)), wHash(i + vec2(1.0,1.0)), u.x), u.y);\n }\n float seaOctave(vec2 uv, float choppy){\n uv += wNoise(uv);\n vec2 wv = 1.0 - abs(sin(uv));\n vec2 swv = abs(cos(uv));\n wv = mix(wv, swv, wv);\n return pow(1.0 - pow(wv.x * wv.y, 0.65), choppy);\n }\n float waveHeight(vec2 p){\n float t = uTime * uWaveSpeed;\n vec2 uv = p * uWaveScale; uv.x *= 0.75;\n float freq = 1.0, amp = 1.0, choppy = 4.0, h = 0.0;\n mat2 m = mat2(1.6, 1.2, -1.2, 1.6);\n for(int i = 0; i < 3; i++){\n float d = seaOctave((uv + t) * freq, choppy) + seaOctave((uv - t) * freq, choppy);\n h += d * amp;\n uv = m * uv; freq *= 1.9; amp *= 0.22; choppy = mix(choppy, 1.0, 0.2);\n }\n return h * uWaveHeight;\n }\n`;\n\n/**\n * Build a `Water` surface: a horizontal plane with a transmissive physical\n * material (refracts the sea floor so the pebbles show through), upgraded with a\n * custom shader that (1) ripples the surface normal with FBM waves that evolve in\n * place, (2) tints by depth (the bottom is read from the `floor`'s base `y` plus\n * its heightmap relief — no depth prepass), and (3) paints an irregular,\n * advancing/receding shoreline foam line. Set\n * `ripple: false` for a still, glassy surface.\n */\nexport function buildWater(water: Water, floor?: TerrainDefinition): BuiltWater {\n const [width, depth] = water.size;\n const [cx, cz] = water.center ?? [0, 0];\n const level = water.level ?? 0;\n const rippleOn = water.ripple !== false;\n\n const material = new MeshPhysicalMaterial({\n color: \"#ffffff\",\n roughness: 0.12,\n metalness: 0,\n transmission: 1,\n ior: 1.33,\n thickness: 100,\n attenuationColor: new Color(water.color ?? \"#3b7d78\"),\n attenuationDistance: 400,\n });\n\n // The depth/foam shader samples the floor's heightmap to know the bottom under\n // each fragment (there is no depth prepass). A 1×1 black texture keeps the\n // sampler bound when the floor is flat (bottom = uFloorY everywhere).\n const [fcx, fcz] = floor?.center ?? [0, 0];\n const [fw, fd] = floor?.size ?? [1, 1];\n const floorHeightScale = floor?.heightmap ? floor.heightScale ?? 0 : 0;\n let bottomMap;\n if (floor?.heightmap && floorHeightScale !== 0) {\n bottomMap = new TextureLoader().load(floor.heightmap);\n } else {\n bottomMap = new DataTexture(new Uint8Array([0, 0, 0, 255]), 1, 1);\n bottomMap.needsUpdate = true;\n }\n // Planar reflection: the scene rendered mirrored across the water plane, so\n // walls (and everything else) occlude the sky in the reflection.\n const reflectionTarget = new WebGLRenderTarget(1, 1);\n const uniforms = {\n uTime: { value: 0 },\n uWaterLevel: { value: level },\n uFloorY: { value: floor?.y ?? 0 },\n uFloorCenter: { value: new Vector2(fcx, fcz) },\n uFloorSize: { value: new Vector2(fw, fd) },\n uFloorHeightmap: { value: bottomMap },\n uFloorHeightScale: { value: floorHeightScale },\n uDepthColor: { value: new Color(water.depthColor ?? \"#0d3b40\") },\n uDepthScale: { value: water.depthScale ?? 100 },\n uFoam: { value: water.foam ?? 12 },\n uFoamColor: { value: new Color(water.foamColor ?? \"#eaf6f6\") },\n uWaveHeight: { value: rippleOn ? water.waveHeight ?? 6 : 0 },\n uWaveScale: { value: water.waveScale ?? 0.02 },\n uWaveSpeed: { value: water.waveSpeed ?? 0.6 },\n uReflection: { value: reflectionTarget.texture },\n uReflectionMatrix: { value: new Matrix4() },\n };\n\n material.onBeforeCompile = (shader: WebGLProgramParametersWithUniforms): void => {\n Object.assign(shader.uniforms, uniforms);\n\n shader.vertexShader = shader.vertexShader\n .replace(\n \"#include <common>\",\n \"#include <common>\\nvarying vec3 vWaterWorld;\\nvarying vec4 vReflectCoord;\\nuniform mat4 uReflectionMatrix;\",\n )\n .replace(\n \"#include <begin_vertex>\",\n `#include <begin_vertex>\n vWaterWorld = (modelMatrix * vec4(transformed, 1.0)).xyz;\n vReflectCoord = uReflectionMatrix * vec4(transformed, 1.0);`,\n );\n\n shader.fragmentShader = shader.fragmentShader\n .replace(\n \"#include <common>\",\n `#include <common>\\n${WAVE_GLSL}\\nvarying vec4 vReflectCoord;\\nuniform sampler2D uReflection;\\nvec2 gWaveSlope = vec2(0.0);`,\n )\n // Replace the flat surface normal with the FBM wave normal (world → view),\n // so refraction and sun glints move organically. Runs after the map normals.\n .replace(\n \"#include <normal_fragment_maps>\",\n `#include <normal_fragment_maps>\n if (uWaveHeight > 0.0) {\n vec2 wp = vWaterWorld.xz;\n float e = 2.0;\n float hC = waveHeight(wp);\n float dhdx = (waveHeight(wp + vec2(e, 0.0)) - hC) / e;\n float dhdz = (waveHeight(wp + vec2(0.0, e)) - hC) / e;\n vec3 wn = normalize(vec3(-dhdx, 1.0, -dhdz));\n normal = normalize((viewMatrix * vec4(wn, 0.0)).xyz);\n gWaveSlope = vec2(dhdx, dhdz); // fed to the reflection for ripples\n }`,\n )\n // Depth tint + irregular, breathing shoreline foam, after the final color.\n .replace(\n \"#include <opaque_fragment>\",\n `#include <opaque_fragment>\n {\n // Floor world (x, z) → heightmap UV. Matches the floor builder's\n // mapping; v is flipped because three.js textures flip Y on upload.\n vec2 huv = clamp((vWaterWorld.xz - uFloorCenter) / uFloorSize + 0.5, 0.0, 1.0);\n float bottom = uFloorY + texture2D(uFloorHeightmap, huv).r * uFloorHeightScale;\n float wdepth = max(uWaterLevel - bottom, 0.0);\n float deep = 1.0 - exp(-wdepth / uDepthScale);\n gl_FragColor.rgb = mix(gl_FragColor.rgb, uDepthColor, deep);\n // Planar reflection: the mirrored scene (walls occlude the sky) sampled\n // at this fragment's projected position, rippled by the wave slope and\n // Fresnel-weighted so it dominates at grazing angles. This replaces the\n // sky-only environment reflection, so the glint respects the walls.\n vec2 rUv = vReflectCoord.xy / vReflectCoord.w + gWaveSlope * 0.06;\n vec3 reflColor = texture2D(uReflection, rUv).rgb;\n float fres = clamp(pow(1.0 - abs(dot(normalize(vViewPosition), normal)), 5.0), 0.0, 1.0);\n gl_FragColor.rgb = mix(gl_FragColor.rgb, reflColor, mix(0.15, 0.95, fres));\n float foam = 0.0;\n if (uFoam > 0.0) {\n // A thin lacy band hugging the waterline (depth 0..uFoam), not a wash\n // across open water. A little noise wiggles its inner edge so the\n // straight plane cut reads as an irregular shoreline; marching sine\n // fronts add waves running up the shore.\n float wiggle = wNoise(vWaterWorld.xz * 0.03 + uTime * 0.1) * 0.5 + 0.5;\n float wd = max(wdepth + (wiggle - 0.5) * uFoam * 0.6, 0.0);\n float band = 1.0 - smoothstep(0.0, uFoam, wd);\n float lace = 0.5 + 0.5 * wNoise(vWaterWorld.xz * 0.1 - uTime * 0.25);\n float fronts = 0.5 + 0.5 * sin(wd * 0.2 - uTime * 1.5);\n foam = smoothstep(0.12, 0.5, band * band * lace * fronts);\n gl_FragColor.rgb = mix(gl_FragColor.rgb, uFoamColor, foam);\n }\n gl_FragColor.a = max(gl_FragColor.a, max(deep * 0.7, foam));\n }`,\n );\n };\n // Keep this material's program separate from other physical materials'.\n material.customProgramCacheKey = (): string => \"scenography-water\";\n\n const mesh = new Mesh(new PlaneGeometry(width, depth), material);\n // PlaneGeometry faces +Z; lay it flat at the water level.\n mesh.rotation.x = -Math.PI / 2;\n mesh.position.set(cx, level, cz);\n mesh.name = \"water\";\n // Receive shadows so the sun's specular glint is cut where something (a wall)\n // casts a shadow onto the surface, instead of shining through it.\n mesh.receiveShadow = true;\n\n const update = (deltaMs: number): void => {\n uniforms.uTime.value += deltaMs / 1000;\n };\n\n // Reusable objects for the per-frame reflection pass (mirror the main camera\n // across the water plane, then render the scene into `reflectionTarget`).\n const reflectionCamera = new PerspectiveCamera();\n const reflectorNormal = new Vector3(0, 1, 0);\n const reflectorPos = new Vector3(cx, level, cz);\n const reflectorPlane = new Plane();\n const camPos = new Vector3();\n const rot = new Matrix4();\n const view = new Vector3();\n const lookAt = new Vector3();\n const target = new Vector3();\n const clip = new Vector4();\n const q = new Vector4();\n const size = new Vector2();\n\n const beforeRender = (\n renderer: WebGLRenderer,\n scene: ThreeScene,\n camera: PerspectiveCameraType,\n ): void => {\n renderer.getDrawingBufferSize(size);\n if (reflectionTarget.width !== size.x || reflectionTarget.height !== size.y) {\n reflectionTarget.setSize(Math.max(1, size.x), Math.max(1, size.y));\n }\n camera.updateMatrixWorld();\n mesh.updateMatrixWorld();\n camPos.setFromMatrixPosition(camera.matrixWorld);\n view.subVectors(reflectorPos, camPos);\n if (view.dot(reflectorNormal) > 0) return; // camera below the surface — skip\n view.reflect(reflectorNormal).negate().add(reflectorPos);\n rot.extractRotation(camera.matrixWorld);\n lookAt.set(0, 0, -1).applyMatrix4(rot).add(camPos);\n target.subVectors(reflectorPos, lookAt).reflect(reflectorNormal).negate().add(reflectorPos);\n reflectionCamera.position.copy(view);\n reflectionCamera.up.set(0, 1, 0).applyMatrix4(rot).reflect(reflectorNormal);\n reflectionCamera.lookAt(target);\n reflectionCamera.far = camera.far;\n reflectionCamera.updateMatrixWorld();\n reflectionCamera.projectionMatrix.copy(camera.projectionMatrix);\n\n // Texture matrix: bias · proj · viewInv · meshWorld → sample by fragment.\n const m = uniforms.uReflectionMatrix.value;\n m.set(0.5, 0, 0, 0.5, 0, 0.5, 0, 0.5, 0, 0, 0.5, 0.5, 0, 0, 0, 1);\n m.multiply(reflectionCamera.projectionMatrix);\n m.multiply(reflectionCamera.matrixWorldInverse);\n m.multiply(mesh.matrixWorld);\n\n // Oblique near-plane clip at the water surface, so geometry below it isn't\n // mirrored into the reflection (standard Reflector trick).\n reflectorPlane.setFromNormalAndCoplanarPoint(reflectorNormal, reflectorPos);\n reflectorPlane.applyMatrix4(reflectionCamera.matrixWorldInverse);\n clip.set(reflectorPlane.normal.x, reflectorPlane.normal.y, reflectorPlane.normal.z, reflectorPlane.constant);\n const e = reflectionCamera.projectionMatrix.elements;\n q.set((Math.sign(clip.x) + e[8]!) / e[0]!, (Math.sign(clip.y) + e[9]!) / e[5]!, -1, (1 + e[10]!) / e[14]!);\n clip.multiplyScalar(2 / clip.dot(q));\n e[2] = clip.x;\n e[6] = clip.y;\n e[10] = clip.z + 1;\n e[14] = clip.w;\n\n const prevTarget = renderer.getRenderTarget();\n const prevShadowAuto = renderer.shadowMap.autoUpdate;\n mesh.visible = false; // don't mirror the water into itself\n renderer.shadowMap.autoUpdate = false; // reuse the main shadow map\n renderer.setRenderTarget(reflectionTarget);\n renderer.clear();\n renderer.render(scene, reflectionCamera);\n renderer.setRenderTarget(prevTarget);\n renderer.shadowMap.autoUpdate = prevShadowAuto;\n mesh.visible = true;\n };\n\n const dispose = (): void => {\n reflectionTarget.dispose();\n };\n\n return { object: mesh, update, beforeRender, dispose };\n}\n","import { TERRAIN, type EnginePlugin, type TerrainCapability } from \"@scenography/engine\";\n\nimport { buildWater } from \"./buildWater\";\nimport { waterData } from \"./data\";\nimport type { Water } from \"./data\";\n\n/**\n * The water plugin: materializes the `water` scene key as a transparent,\n * depth-tinted surface with FBM waves and shoreline foam. Consumes the\n * `terrain` capability (published by the floor) so the depth/foam shader can\n * sample the same heightmap the ground is displaced by — and rebuilds when the\n * floor changes (`dependsOn`).\n */\nexport const water = (): EnginePlugin<Water> => ({\n data: waterData,\n dependsOn: [TERRAIN],\n build: (definition, ctx) => {\n const terrain = ctx.capabilities.get<TerrainCapability>(TERRAIN);\n return buildWater(definition, terrain?.definition);\n },\n});\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@scenography/plugin-water",
3
+ "version": "0.2.0",
4
+ "description": "Water plugin for scenography: a transparent, depth-tinted sea/pool surface with FBM waves and shoreline foam. Data half importable without three.js.",
5
+ "license": "MIT",
6
+ "files": [
7
+ "dist"
8
+ ],
9
+ "type": "module",
10
+ "main": "./dist/index.cjs",
11
+ "module": "./dist/index.mjs",
12
+ "types": "./dist/index.d.cts",
13
+ "exports": {
14
+ ".": {
15
+ "import": {
16
+ "types": "./dist/index.d.mts",
17
+ "default": "./dist/index.mjs"
18
+ },
19
+ "require": {
20
+ "types": "./dist/index.d.cts",
21
+ "default": "./dist/index.cjs"
22
+ }
23
+ },
24
+ "./data": {
25
+ "import": {
26
+ "types": "./dist/data.d.mts",
27
+ "default": "./dist/data.mjs"
28
+ },
29
+ "require": {
30
+ "types": "./dist/data.d.cts",
31
+ "default": "./dist/data.cjs"
32
+ }
33
+ }
34
+ },
35
+ "dependencies": {
36
+ "@scenography/scene": "0.2.0"
37
+ },
38
+ "devDependencies": {
39
+ "@types/three": "^0.160.0",
40
+ "three": "^0.160.0",
41
+ "@scenography/engine": "0.2.0"
42
+ },
43
+ "peerDependencies": {
44
+ "three": ">=0.160.0",
45
+ "@scenography/engine": "0.2.0"
46
+ },
47
+ "scripts": {
48
+ "build": "tsdown",
49
+ "dev": "tsdown --watch",
50
+ "typecheck": "tsc --noEmit"
51
+ }
52
+ }