incanto 0.15.0 → 0.16.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.
@@ -10269,6 +10269,120 @@ void main() {
10269
10269
  }
10270
10270
  `;
10271
10271
  //#endregion
10272
+ //#region src/3d/water/caustics.ts
10273
+ /**
10274
+ * Underwater caustics — the dancing refracted-light pattern the water surface
10275
+ * throws onto everything below it. The engine has no post-processing pipeline,
10276
+ * so the renderer does this as ONE depth-aware composite pass, and ONLY while
10277
+ * the camera is genuinely underwater (so normal above-water rendering — even
10278
+ * standing at the water's edge — is completely untouched):
10279
+ *
10280
+ * 1. render the scene to an offscreen target (color + depth)
10281
+ * 2. draw this fullscreen material, which reconstructs each pixel's WORLD
10282
+ * position from the depth buffer and adds an animated caustic highlight
10283
+ * wherever that world point sits below the water surface
10284
+ *
10285
+ * Because the pattern is keyed to reconstructed world XZ, it STICKS to the
10286
+ * floor/walls/props (it isn't a flat screen overlay) and fades with depth +
10287
+ * view distance so it melts into the underwater fog.
10288
+ */
10289
+ /** Fullscreen triangle/quad — the vertex stage ignores the camera entirely. */
10290
+ const CAUSTICS_VERT = `
10291
+ varying vec2 vUv;
10292
+ void main() {
10293
+ vUv = uv;
10294
+ gl_Position = vec4(position.xy, 0.0, 1.0);
10295
+ }
10296
+ `;
10297
+ /**
10298
+ * The classic animated caustic pattern (layered moving cells), 0..1 — SHARED
10299
+ * between the underwater composite and Water3D's above-water caustics (the
10300
+ * refracted bottom shimmer), so both views of the same water agree.
10301
+ */
10302
+ const CAUSTIC_GLSL = `
10303
+ float caustic(vec2 uv, float t) {
10304
+ vec2 p = mod(uv * 6.28318, 6.28318) - 250.0;
10305
+ vec2 i = p;
10306
+ float c = 1.0;
10307
+ float inten = 0.005;
10308
+ for (int n = 0; n < 5; n++) {
10309
+ float ti = t * (1.0 - (3.5 / float(n + 1)));
10310
+ i = p + vec2(cos(ti - i.x) + sin(ti + i.y), sin(ti - i.y) + cos(ti + i.x));
10311
+ c += 1.0 / length(vec2(p.x / (sin(i.x + ti) / inten), p.y / (cos(i.y + ti) / inten)));
10312
+ }
10313
+ c /= 5.0;
10314
+ c = 1.17 - pow(c, 1.4);
10315
+ return clamp(pow(abs(c), 8.0), 0.0, 1.0);
10316
+ }
10317
+ `;
10318
+ const CAUSTICS_FRAG = `
10319
+ precision highp float;
10320
+ varying vec2 vUv;
10321
+ uniform sampler2D tColor;
10322
+ uniform sampler2D tDepth;
10323
+ uniform mat4 uInvViewProj;
10324
+ uniform vec3 uCameraPos;
10325
+ uniform float uWaterLevel;
10326
+ uniform float uTime;
10327
+ uniform vec3 uCausticColor;
10328
+ uniform float uCausticIntensity;
10329
+ uniform float uCausticScale;
10330
+ uniform float uMaxDist;
10331
+
10332
+ ${CAUSTIC_GLSL}
10333
+
10334
+ void main() {
10335
+ vec4 color = texture2D(tColor, vUv);
10336
+ float depth = texture2D(tDepth, vUv).x;
10337
+ // depth == 1 is the far plane (sky/background) — nothing to light there
10338
+ if (depth < 1.0) {
10339
+ vec4 ndc = vec4(vUv * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0);
10340
+ vec4 world = uInvViewProj * ndc;
10341
+ world.xyz /= world.w;
10342
+ float below = uWaterLevel - world.y;
10343
+ if (below > 0.0) {
10344
+ float dist = distance(world.xyz, uCameraPos);
10345
+ // caustics — fade with distance (melt into the fog) + a touch with depth
10346
+ float distFade = 1.0 - clamp(dist / uMaxDist, 0.0, 1.0);
10347
+ float depthFade = 1.0 - clamp(below / (uMaxDist * 0.5), 0.0, 0.6);
10348
+ // two octaves at offset speeds = the shimmering interference of real caustics
10349
+ float c =
10350
+ caustic(world.xz * uCausticScale, uTime) * 0.65 +
10351
+ caustic(world.xz * uCausticScale * 1.7 + 30.0, uTime * 0.8) * 0.35;
10352
+ color.rgb += uCausticColor * (c * uCausticIntensity * distFade * distFade * depthFade);
10353
+ }
10354
+ }
10355
+ gl_FragColor = color;
10356
+ }
10357
+ `;
10358
+ /** Build the fullscreen composite mesh (a 2×2 clip-space quad). */
10359
+ function createCausticsQuad() {
10360
+ const material = new ShaderMaterial({
10361
+ vertexShader: CAUSTICS_VERT,
10362
+ fragmentShader: CAUSTICS_FRAG,
10363
+ depthTest: false,
10364
+ depthWrite: false,
10365
+ uniforms: {
10366
+ tColor: { value: null },
10367
+ tDepth: { value: null },
10368
+ uInvViewProj: { value: new Matrix4() },
10369
+ uCameraPos: { value: new Vector3() },
10370
+ uWaterLevel: { value: 0 },
10371
+ uTime: { value: 0 },
10372
+ uCausticColor: { value: new Color("#cdeeff") },
10373
+ uCausticIntensity: { value: .55 },
10374
+ uCausticScale: { value: .32 },
10375
+ uMaxDist: { value: 22 }
10376
+ }
10377
+ });
10378
+ const mesh = new Mesh(new PlaneGeometry(2, 2), material);
10379
+ mesh.frustumCulled = false;
10380
+ return {
10381
+ mesh,
10382
+ uniforms: material.uniforms
10383
+ };
10384
+ }
10385
+ //#endregion
10272
10386
  //#region src/3d/water/shaders.ts
10273
10387
  /**
10274
10388
  * The Water3D 'fancy' shaders — ported VERBATIM from
@@ -10576,6 +10690,16 @@ uniform float uWhitecaps;
10576
10690
  uniform float uWaveEnvelope;
10577
10691
  uniform float uSwellAmplitude;
10578
10692
 
10693
+ // incanto presets: above-water CAUSTICS — the dancing light web on the
10694
+ // submerged bottom, seen THROUGH the surface (the pool/shallow-sea look).
10695
+ // Intensity 0 = off. The pattern fn is shared with the underwater composite.
10696
+ uniform float uCausticsAbove;
10697
+ // incanto presets: per-preset reflection ceiling (the old hard 0.6) — lakes
10698
+ // push toward mirror, pools stay glassy-clear.
10699
+ uniform float uReflectivityMax;
10700
+
10701
+ ${CAUSTIC_GLSL}
10702
+
10579
10703
  // 🎨 Contact surface color customization uniforms
10580
10704
  uniform vec3 uEdgeColor;
10581
10705
  uniform float uEdgeIntensity;
@@ -11031,6 +11155,22 @@ void main() {
11031
11155
  // underwater scene. Real water blurs what it refracts; the crisp
11032
11156
  // half-res grab pixel-speckled at every silhouette and ripple edge.
11033
11157
  vec3 sceneColor = texture2D(uSceneColor, refractedUV, 1.75).rgb;
11158
+ // incanto presets: above-water caustics — reconstruct the sampled
11159
+ // bottom's world position along the view ray and light it with the
11160
+ // shared caustic web. Strongest through the first meters of water
11161
+ // (a pool floor dances, the deep sea does not), distance-faded so far
11162
+ // shallows never shimmer-alias.
11163
+ if (uCausticsAbove > 0.0) {
11164
+ vec3 bottomWorld = cameraPosition + viewDirection * refrT;
11165
+ float below = uWaterCenter.y - bottomWorld.y;
11166
+ if (below > 0.05) {
11167
+ float reach = 1.0 - smoothstep(0.2, 9.0, below);
11168
+ float causticFade = 1.0 - smoothstep(60.0, 200.0, camDist);
11169
+ float cw = caustic(bottomWorld.xz * 0.42, uTime * 0.7) * 0.65
11170
+ + caustic(bottomWorld.xz * 0.72 + 30.0, uTime * 0.55) * 0.35;
11171
+ sceneColor += vec3(0.87, 0.96, 1.0) * (cw * uCausticsAbove * reach * causticFade);
11172
+ }
11173
+ }
11034
11174
  // opacity keeps its dial under in-shader compositing: it scales how
11035
11175
  // strongly the water body hides the Beer-attenuated scene behind it —
11036
11176
  // per channel, so the shallows tint instead of fading uniformly
@@ -11071,7 +11211,7 @@ void main() {
11071
11211
  // mirror (dark scenes otherwise paint the near edge black). refinement
11072
11212
  // pass: 0.75 → 0.6 — the reference sea stays teal even at range; an HDR
11073
11213
  // sky at 3/4 mirror washed the whole far field toward white.
11074
- float reflectivity = clamp(fresnel, 0.0, 0.6) * uUseEnvironmentMap;
11214
+ float reflectivity = clamp(fresnel, 0.0, uReflectivityMax) * uUseEnvironmentMap;
11075
11215
  vec3 finalColor = mix(bodyColor, reflectionColor.rgb, reflectivity);
11076
11216
 
11077
11217
  // incanto v2: sun specular off the detail-perturbed normal.
@@ -11394,6 +11534,108 @@ const COLOR_KEYS = [
11394
11534
  "surface",
11395
11535
  "peak"
11396
11536
  ];
11537
+ const WATER_PRESET_NAMES = [
11538
+ "custom",
11539
+ "ocean",
11540
+ "pool",
11541
+ "lake",
11542
+ "pond"
11543
+ ];
11544
+ /**
11545
+ * The four curated water types — `preset: 'ocean' | 'pool' | 'lake' | 'pond'`
11546
+ * is THE one-choice dial: each bundle sets every wave/color/clarity/caustics
11547
+ * knob to a hand-tuned sea state. A preset value applies ONLY while the prop
11548
+ * still sits at its schema default (same convention as the env-sun handoff),
11549
+ * so any explicitly authored prop wins — pick the type, then override details
11550
+ * to taste.
11551
+ *
11552
+ * - ocean: traveling swell + whitecaps, wide turquoise→deep-blue absorption
11553
+ * - pool: near-flat crystal water, strong above-water caustics on the floor
11554
+ * - lake: calm mirror — high reflectivity, murkier green-teal depths
11555
+ * - pond: still, mossy, opaque quickly — the garden pond
11556
+ */
11557
+ const WATER_PRESETS = {
11558
+ ocean: {
11559
+ waveHeight: .06,
11560
+ swell: .55,
11561
+ swellWavelength: 42,
11562
+ whitecaps: .45,
11563
+ absorption: .2,
11564
+ opacity: .85,
11565
+ detailStrength: .3,
11566
+ colors: {
11567
+ trough: "#0c3d58",
11568
+ surface: "#1a7d9c",
11569
+ peak: "#9adfe0"
11570
+ },
11571
+ color: "#1a7d9c",
11572
+ causticsAbove: .35,
11573
+ reflectionInterval: 400
11574
+ },
11575
+ pool: {
11576
+ waveHeight: .006,
11577
+ swell: 0,
11578
+ whitecaps: 0,
11579
+ absorption: .3,
11580
+ opacity: .5,
11581
+ detailStrength: .16,
11582
+ sunIntensity: 1.25,
11583
+ colors: {
11584
+ trough: "#1e8dbe",
11585
+ surface: "#3fb6dc",
11586
+ peak: "#bdeef7"
11587
+ },
11588
+ color: "#3fb6dc",
11589
+ causticsAbove: 1,
11590
+ reflectionInterval: 600,
11591
+ _look: {
11592
+ fresnelScale: .85,
11593
+ reflectivityMax: .5
11594
+ }
11595
+ },
11596
+ lake: {
11597
+ waveHeight: .028,
11598
+ swell: .14,
11599
+ swellWavelength: 24,
11600
+ whitecaps: 0,
11601
+ absorption: .5,
11602
+ opacity: .88,
11603
+ detailStrength: .3,
11604
+ colors: {
11605
+ trough: "#12343a",
11606
+ surface: "#25565c",
11607
+ peak: "#7fb2ab"
11608
+ },
11609
+ color: "#25565c",
11610
+ causticsAbove: .15,
11611
+ reflectionInterval: 300,
11612
+ _look: {
11613
+ fresnelScale: 1.25,
11614
+ reflectivityMax: .78
11615
+ }
11616
+ },
11617
+ pond: {
11618
+ waveHeight: .012,
11619
+ swell: 0,
11620
+ whitecaps: 0,
11621
+ absorption: .55,
11622
+ opacity: .88,
11623
+ detailStrength: .2,
11624
+ sunIntensity: .9,
11625
+ colors: {
11626
+ trough: "#24402a",
11627
+ surface: "#436b4d",
11628
+ peak: "#8fae8e"
11629
+ },
11630
+ color: "#436b4d",
11631
+ causticsAbove: .2,
11632
+ reflectionInterval: 800,
11633
+ _look: {
11634
+ fresnelScale: 1.1,
11635
+ reflectivityMax: .68
11636
+ }
11637
+ }
11638
+ };
11397
11639
  /** Refinement-pass palette — teal-leaning sea blues sampled off the reference
11398
11640
  * calm sea (deep open water ≈ #2b7993, mid teal ≈ #16878a). The old ported
11399
11641
  * palette (#1f5b8a/#52a0d0/#aee3ff) read as saturated pool blue. */
@@ -11545,7 +11787,12 @@ var Water3D = class Water3D extends Node3D {
11545
11787
  swell: { default: 0 },
11546
11788
  swellDirectionDeg: { default: 0 },
11547
11789
  swellWavelength: { default: 30 },
11548
- whitecaps: { default: 0 }
11790
+ whitecaps: { default: 0 },
11791
+ preset: {
11792
+ default: "custom",
11793
+ options: WATER_PRESET_NAMES
11794
+ },
11795
+ causticsAbove: { default: 0 }
11549
11796
  };
11550
11797
  /** [width, depth] in meters (the surface lies on XZ). */
11551
11798
  size = [40, 40];
@@ -11615,6 +11862,40 @@ var Water3D = class Water3D extends Node3D {
11615
11862
  /** Open-water whitecap foam on the tallest crests, 0–1 (fancy only). Works
11616
11863
  * best with `swell` — the caps ride the swell's crest lines. */
11617
11864
  whitecaps = 0;
11865
+ /**
11866
+ * THE one-choice water type: 'ocean' | 'pool' | 'lake' | 'pond' bundles
11867
+ * every wave/color/clarity/caustics knob into a hand-tuned sea state
11868
+ * ('custom', the default, changes nothing). A preset value applies ONLY
11869
+ * where a prop still sits at its schema default — set the type first, then
11870
+ * override any detail prop to taste and your value wins.
11871
+ */
11872
+ preset = "custom";
11873
+ /**
11874
+ * Above-water CAUSTICS intensity (0 = off): the dancing light web on the
11875
+ * submerged bottom, seen THROUGH the surface — the pool / tropical-shallows
11876
+ * look. Needs `refraction` (fancy quality). Presets set it; override freely.
11877
+ */
11878
+ causticsAbove = 0;
11879
+ /** Preset-layered prop read (see {@link WATER_PRESETS}): the preset value
11880
+ * applies only while the prop sits at its schema default. */
11881
+ rp(key) {
11882
+ const bundle = WATER_PRESETS[this.preset];
11883
+ const self = this;
11884
+ if (bundle && key in bundle) {
11885
+ const def = Water3D.props[key]?.default;
11886
+ const cur = self[key];
11887
+ if (typeof cur === "object" && cur !== null ? JSON.stringify(cur) === JSON.stringify(def) : cur === def) return bundle[key];
11888
+ }
11889
+ return self[key];
11890
+ }
11891
+ /** Per-preset internal look constants (fresnel curve, reflection ceiling). */
11892
+ presetLook() {
11893
+ const look = WATER_PRESETS[this.preset]?._look;
11894
+ return {
11895
+ fresnelScale: look?.fresnelScale ?? FANCY_LOOK.fresnelScale,
11896
+ reflectivityMax: look?.reflectivityMax ?? .6
11897
+ };
11898
+ }
11618
11899
  /** Loader hook: degenerate planes and bad enums fail at LOAD, not at render. */
11619
11900
  static validateJson(node) {
11620
11901
  const w = node;
@@ -11639,6 +11920,11 @@ var Water3D = class Water3D extends Node3D {
11639
11920
  if (!(w.swell >= 0) || !Number.isFinite(w.swell)) throw new IncantoError("BAD_FORMAT", `Water3D '${node.name}' swell must be >= 0 meters (0 = off), got ${JSON.stringify(w.swell)}.`, { prop: "swell" });
11640
11921
  if (!(w.swellWavelength >= 2)) throw new IncantoError("BAD_FORMAT", `Water3D '${node.name}' swellWavelength must be >= 2 meters, got ${JSON.stringify(w.swellWavelength)}.`, { prop: "swellWavelength" });
11641
11922
  if (!(w.whitecaps >= 0) || !(w.whitecaps <= 1)) throw new IncantoError("BAD_FORMAT", `Water3D '${node.name}' whitecaps must be in [0, 1], got ${JSON.stringify(w.whitecaps)}.`, { prop: "whitecaps" });
11923
+ if (!WATER_PRESET_NAMES.includes(w.preset)) throw new IncantoError("BAD_FORMAT", `Water3D '${node.name}' preset must be one of [${WATER_PRESET_NAMES.join(", ")}], got '${w.preset}'.`, {
11924
+ prop: "preset",
11925
+ validOptions: WATER_PRESET_NAMES
11926
+ });
11927
+ if (!(w.causticsAbove >= 0) || !(w.causticsAbove <= 3)) throw new IncantoError("BAD_FORMAT", `Water3D '${node.name}' causticsAbove must be in [0, 3] (0 = off), got ${JSON.stringify(w.causticsAbove)}.`, { prop: "causticsAbove" });
11642
11928
  const uw = w.underwater;
11643
11929
  if (typeof uw !== "boolean") {
11644
11930
  if (typeof uw !== "object" || uw === null || Array.isArray(uw)) throw new IncantoError("BAD_FORMAT", `Water3D '${node.name}' underwater must be true, false or an object ({ color?, visibility? }), got ${JSON.stringify(uw)}.`, { prop: "underwater" });
@@ -11718,7 +12004,7 @@ var Water3D = class Water3D extends Node3D {
11718
12004
  this.cubeCamera = new CubeCamera(near, 1e3, this.cubeTarget);
11719
12005
  this.lastReflectionAt = 0;
11720
12006
  }
11721
- if (this.lastReflectionAt === 0 || now - this.lastReflectionAt >= this.reflectionInterval) {
12007
+ if (this.lastReflectionAt === 0 || now - this.lastReflectionAt >= this.rp("reflectionInterval")) {
11722
12008
  this.lastReflectionAt = now;
11723
12009
  const center = worldTranslationOf(this);
11724
12010
  const cam = this.cubeCamera;
@@ -11805,7 +12091,7 @@ var Water3D = class Water3D extends Node3D {
11805
12091
  halfW: (this.size[0] ?? 1) / 2,
11806
12092
  halfD: (this.size[1] ?? 1) / 2
11807
12093
  }, UNDERWATER_MARGIN)) return null;
11808
- const cfg = resolveUnderwater(this.underwater, this.color);
12094
+ const cfg = resolveUnderwater(this.underwater, this.rp("color"));
11809
12095
  return cfg.enabled ? {
11810
12096
  ...cfg,
11811
12097
  surfaceY: center.y
@@ -11851,10 +12137,10 @@ var Water3D = class Water3D extends Node3D {
11851
12137
  fogFar: { value: 1e3 },
11852
12138
  fogDensity: { value: 25e-5 },
11853
12139
  uTime: { value: 0 },
11854
- uOpacity: { value: this.opacity },
12140
+ uOpacity: { value: this.rp("opacity") },
11855
12141
  uEnvironmentMap: { value: null },
11856
12142
  uUseEnvironmentMap: { value: 0 },
11857
- uWavesAmplitude: { value: this.waveHeight * AMPLITUDE_PER_WAVE_HEIGHT },
12143
+ uWavesAmplitude: { value: this.rp("waveHeight") * AMPLITUDE_PER_WAVE_HEIGHT },
11858
12144
  uWavesSpeed: { value: FANCY_WAVE.speed },
11859
12145
  uWavesFrequency: { value: FANCY_WAVE.frequency },
11860
12146
  uWavesPersistence: { value: FANCY_WAVE.persistence },
@@ -11876,9 +12162,9 @@ var Water3D = class Water3D extends Node3D {
11876
12162
  uSplashFoam: { value: this.splash ? 1 : 0 },
11877
12163
  uSunDirection: { value: new Vector3(.5, .8, .3) },
11878
12164
  uSunColor: { value: new Color(this.sunColor) },
11879
- uSunIntensity: { value: this.sunIntensity },
11880
- uDetailStrength: { value: this.detailStrength },
11881
- uAbsorption: { value: this.absorption },
12165
+ uSunIntensity: { value: this.rp("sunIntensity") },
12166
+ uDetailStrength: { value: this.rp("detailStrength") },
12167
+ uAbsorption: { value: this.rp("absorption") },
11882
12168
  uUseSceneDepth: { value: 0 },
11883
12169
  uUseRefraction: { value: 0 },
11884
12170
  uSceneColor: { value: null },
@@ -11893,11 +12179,13 @@ var Water3D = class Water3D extends Node3D {
11893
12179
  uEdgeColor: { value: new Color(FOAM_LOOK.edgeColor) },
11894
12180
  uEdgeIntensity: { value: FOAM_LOOK.edgeIntensity },
11895
12181
  uEdgeWidth: { value: FOAM_LOOK.edgeWidth },
11896
- uSwellAmplitude: { value: this.swell },
12182
+ uSwellAmplitude: { value: this.rp("swell") },
11897
12183
  uSwellDir: { value: new Vector2(0, 1) },
11898
- uSwellWavelength: { value: this.swellWavelength },
11899
- uWhitecaps: { value: this.whitecaps },
11900
- uWaveEnvelope: { value: 0 }
12184
+ uSwellWavelength: { value: this.rp("swellWavelength") },
12185
+ uWhitecaps: { value: this.rp("whitecaps") },
12186
+ uWaveEnvelope: { value: 0 },
12187
+ uCausticsAbove: { value: this.rp("causticsAbove") },
12188
+ uReflectivityMax: { value: .6 }
11901
12189
  },
11902
12190
  transparent: true,
11903
12191
  depthTest: true,
@@ -11918,14 +12206,14 @@ var Water3D = class Water3D extends Node3D {
11918
12206
  fogFar: { value: 1e3 },
11919
12207
  fogDensity: { value: 25e-5 },
11920
12208
  uTime: { value: 0 },
11921
- uOpacity: { value: this.opacity },
11922
- uColor: { value: new Color(this.color) },
11923
- uSurfaceColor: { value: new Color(this.color) },
11924
- uSkyColor: { value: new Color(this.color) },
12209
+ uOpacity: { value: this.rp("opacity") },
12210
+ uColor: { value: new Color(this.rp("color")) },
12211
+ uSurfaceColor: { value: new Color(this.rp("color")) },
12212
+ uSkyColor: { value: new Color(this.rp("color")) },
11925
12213
  uSunDirection: { value: new Vector3(.5, .8, .3).normalize() },
11926
12214
  uSunColor: { value: new Color(this.sunColor) },
11927
- uSunIntensity: { value: this.sunIntensity },
11928
- uDetailStrength: { value: this.detailStrength },
12215
+ uSunIntensity: { value: this.rp("sunIntensity") },
12216
+ uDetailStrength: { value: this.rp("detailStrength") },
11929
12217
  uFresnelPower: { value: FANCY_LOOK.fresnelPower },
11930
12218
  uScale: { value: new Vector3(1, 1, 1) },
11931
12219
  uWaterCenter: { value: new Vector3() },
@@ -11946,8 +12234,8 @@ var Water3D = class Water3D extends Node3D {
11946
12234
  const d = this.size[1] ?? 1;
11947
12235
  const center = worldTranslationOf(this);
11948
12236
  if (u.uTime) u.uTime.value = this.time;
11949
- if (u.uOpacity) u.uOpacity.value = this.opacity;
11950
- if (u.uWavesAmplitude) u.uWavesAmplitude.value = this.waveHeight * AMPLITUDE_PER_WAVE_HEIGHT;
12237
+ if (u.uOpacity) u.uOpacity.value = this.rp("opacity");
12238
+ if (u.uWavesAmplitude) u.uWavesAmplitude.value = this.rp("waveHeight") * AMPLITUDE_PER_WAVE_HEIGHT;
11951
12239
  (u.uScale?.value)?.set(w / 2, d / 2, d / 2);
11952
12240
  (u.uWaterCenter?.value)?.set(center.x, center.y, center.z);
11953
12241
  const palette = this.resolvePalette();
@@ -11956,15 +12244,19 @@ var Water3D = class Water3D extends Node3D {
11956
12244
  (u.uPeakColor?.value)?.copy(palette.peak);
11957
12245
  (u.uSunDirection?.value)?.set(this.sunDirection[0] ?? 0, this.sunDirection[1] ?? 1, this.sunDirection[2] ?? 0).normalize();
11958
12246
  (u.uSunColor?.value)?.set(this.sunColor);
11959
- if (u.uSunIntensity) u.uSunIntensity.value = this.sunIntensity;
11960
- if (u.uDetailStrength) u.uDetailStrength.value = this.detailStrength;
11961
- if (u.uAbsorption) u.uAbsorption.value = this.absorption;
11962
- if (u.uSwellAmplitude) u.uSwellAmplitude.value = this.swell;
11963
- if (u.uSwellWavelength) u.uSwellWavelength.value = this.swellWavelength;
11964
- if (u.uWhitecaps) u.uWhitecaps.value = this.whitecaps;
12247
+ if (u.uSunIntensity) u.uSunIntensity.value = this.rp("sunIntensity");
12248
+ if (u.uDetailStrength) u.uDetailStrength.value = this.rp("detailStrength");
12249
+ if (u.uAbsorption) u.uAbsorption.value = this.rp("absorption");
12250
+ if (u.uSwellAmplitude) u.uSwellAmplitude.value = this.rp("swell");
12251
+ if (u.uSwellWavelength) u.uSwellWavelength.value = this.rp("swellWavelength");
12252
+ if (u.uWhitecaps) u.uWhitecaps.value = this.rp("whitecaps");
12253
+ if (u.uCausticsAbove) u.uCausticsAbove.value = this.rp("causticsAbove");
12254
+ const look = this.presetLook();
12255
+ if (u.uFresnelScale) u.uFresnelScale.value = look.fresnelScale;
12256
+ if (u.uReflectivityMax) u.uReflectivityMax.value = look.reflectivityMax;
11965
12257
  const swellRad = this.swellDirectionDeg * Math.PI / 180;
11966
12258
  (u.uSwellDir?.value)?.set(Math.sin(swellRad), Math.cos(swellRad));
11967
- if (u.uWaveEnvelope) u.uWaveEnvelope.value = this.waveHeight * AMPLITUDE_PER_WAVE_HEIGHT * 1.5 + this.swell * 1.03;
12259
+ if (u.uWaveEnvelope) u.uWaveEnvelope.value = this.rp("waveHeight") * AMPLITUDE_PER_WAVE_HEIGHT * 1.5 + this.rp("swell") * 1.03;
11968
12260
  if (!this.reflection && u.uUseEnvironmentMap) u.uUseEnvironmentMap.value = 0;
11969
12261
  if (!this.foam && u.uEnableFoam) u.uEnableFoam.value = 0;
11970
12262
  if (!this.refraction && u.uUseRefraction) u.uUseRefraction.value = 0;
@@ -11992,8 +12284,9 @@ var Water3D = class Water3D extends Node3D {
11992
12284
  }
11993
12285
  /** The fancy three-color ramp; a customized legacy `color` tints it. */
11994
12286
  resolvePalette() {
11995
- const overrides = this.colors;
11996
- const legacyTint = this.color !== Water3D.props.color?.default ? this.color : null;
12287
+ const overrides = this.rp("colors");
12288
+ const tint = this.rp("color");
12289
+ const legacyTint = tint !== Water3D.props.color?.default ? tint : null;
11997
12290
  const surface = overrides.surface ?? legacyTint ?? FANCY_COLORS.surface;
11998
12291
  paletteScratch.surface.set(surface);
11999
12292
  if (overrides.trough) paletteScratch.trough.set(overrides.trough);
@@ -12006,22 +12299,22 @@ var Water3D = class Water3D extends Node3D {
12006
12299
  }
12007
12300
  syncSimple(mesh) {
12008
12301
  const mat = mesh.material;
12009
- mat.opacity = this.opacity;
12302
+ mat.opacity = this.rp("opacity");
12010
12303
  const u = mat.uniforms;
12011
12304
  const w = this.size[0] ?? 1;
12012
12305
  const d = this.size[1] ?? 1;
12013
12306
  const center = worldTranslationOf(this, this._scratchA);
12014
12307
  if (u.uTime) u.uTime.value = this.time;
12015
- if (u.uOpacity) u.uOpacity.value = this.opacity;
12016
- (u.uSurfaceColor?.value)?.set(this.color).lerp(WHITE, .12);
12017
- (u.uColor?.value)?.set(this.color).lerp(BLACK, .4);
12018
- (u.uSkyColor?.value)?.set(this.color).lerp(SKY_PALE, .82);
12308
+ if (u.uOpacity) u.uOpacity.value = this.rp("opacity");
12309
+ (u.uSurfaceColor?.value)?.set(this.rp("color")).lerp(WHITE, .12);
12310
+ (u.uColor?.value)?.set(this.rp("color")).lerp(BLACK, .4);
12311
+ (u.uSkyColor?.value)?.set(this.rp("color")).lerp(SKY_PALE, .82);
12019
12312
  (u.uScale?.value)?.set(w / 2, d / 2, d / 2);
12020
12313
  (u.uWaterCenter?.value)?.set(center.x, center.y, center.z);
12021
12314
  (u.uSunDirection?.value)?.set(this.sunDirection[0] ?? 0, this.sunDirection[1] ?? 1, this.sunDirection[2] ?? 0).normalize();
12022
12315
  (u.uSunColor?.value)?.set(this.sunColor);
12023
- if (u.uSunIntensity) u.uSunIntensity.value = this.sunIntensity;
12024
- if (u.uDetailStrength) u.uDetailStrength.value = this.detailStrength;
12316
+ if (u.uSunIntensity) u.uSunIntensity.value = this.rp("sunIntensity");
12317
+ if (u.uDetailStrength) u.uDetailStrength.value = this.rp("detailStrength");
12025
12318
  if (u.uSplashFoam) u.uSplashFoam.value = this.splash ? 1 : 0;
12026
12319
  const uRip = u.uRipples?.value;
12027
12320
  const uFoam = u.uRippleFoam?.value;
@@ -12035,7 +12328,7 @@ var Water3D = class Water3D extends Node3D {
12035
12328
  const pos = geo.getAttribute("position");
12036
12329
  const norm = geo.getAttribute("normal");
12037
12330
  const t = this.time;
12038
- const wh = this.waveHeight;
12331
+ const wh = this.rp("waveHeight");
12039
12332
  const cx = center.x;
12040
12333
  const cz = center.z;
12041
12334
  const ripples = this._ripples;
@@ -12119,9 +12412,9 @@ var Water3D = class Water3D extends Node3D {
12119
12412
  /** Base wave height at world (x, z) — the CPU twin of the active shader.
12120
12413
  * Ripples are excluded on purpose: a splash must not re-trigger itself. */
12121
12414
  waveOffsetAt(x, z, center) {
12122
- if (this.quality === "simple") return simpleWaveHeight(x - center.x, z - center.z, this.time, this.waveHeight);
12415
+ if (this.quality === "simple") return simpleWaveHeight(x - center.x, z - center.z, this.time, this.rp("waveHeight"));
12123
12416
  return waveElevation(x, z, this.time, {
12124
- amplitude: this.waveHeight * AMPLITUDE_PER_WAVE_HEIGHT,
12417
+ amplitude: this.rp("waveHeight") * AMPLITUDE_PER_WAVE_HEIGHT,
12125
12418
  frequency: FANCY_WAVE.frequency,
12126
12419
  persistence: FANCY_WAVE.persistence,
12127
12420
  lacunarity: FANCY_WAVE.lacunarity,
@@ -12241,4 +12534,4 @@ function registerNodes3D() {
12241
12534
  registerNode(CharacterBody3D);
12242
12535
  }
12243
12536
  //#endregion
12244
- export { Area3D as A, movementState as B, BoneAttachment3D as C, TERRAIN_THEMES as D, DEFAULT_TERRAIN_TEXTURE_BASE as E, Node3D as F, validateCollider3D as I, QUARTER_PITCH as L, PhysicsBody3D as M, RigidBody3D as N, terrainThemeLayers as O, StaticBody3D as P, cameraRelative as R, BoneLookAt3D as S, Terrain3D as T, rigPose as V, Flowers3D as _, VoxelGrid3D as a, CharacterController3D as b, Particles3D as c, DirectionalLight3D as d, OmniLight3D as f, DENSITY_PRESETS as g, Foliage3D as h, VOXEL_PALETTE as i, CharacterBody3D as j, Joint3D as k, ModelInstance3D as l, MeshInstance3D as m, Water3D as n, Tree3D as o, InstancedMesh3D as p, WATER_MAX_RIPPLES as r, Trail3D as s, registerNodes3D as t, LoftMesh3D as u, resolveFlowerDensity as v, Billboard3D as w, Camera3D as x, FLOWER_VARIETIES as y, keyboardIntensity as z };
12537
+ export { Joint3D as A, keyboardIntensity as B, BoneLookAt3D as C, DEFAULT_TERRAIN_TEXTURE_BASE as D, Terrain3D as E, StaticBody3D as F, rigPose as H, Node3D as I, validateCollider3D as L, CharacterBody3D as M, PhysicsBody3D as N, TERRAIN_THEMES as O, RigidBody3D as P, QUARTER_PITCH as R, Camera3D as S, Billboard3D as T, movementState as V, DENSITY_PRESETS as _, VOXEL_PALETTE as a, FLOWER_VARIETIES as b, Trail3D as c, LoftMesh3D as d, DirectionalLight3D as f, Foliage3D as g, MeshInstance3D as h, WATER_MAX_RIPPLES as i, Area3D as j, terrainThemeLayers as k, Particles3D as l, InstancedMesh3D as m, Water3D as n, VoxelGrid3D as o, OmniLight3D as p, createCausticsQuad as r, Tree3D as s, registerNodes3D as t, ModelInstance3D as u, Flowers3D as v, BoneAttachment3D as w, CharacterController3D as x, resolveFlowerDensity as y, cameraRelative as z };
package/dist/test.js CHANGED
@@ -6,7 +6,7 @@ import { n as jsonEquals, t as jsonClone } from "./json-BLk7H2Qa.js";
6
6
  import { i as getNodeSchema, s as mergeStaticProps } from "./registry-BVJ2HbCn.js";
7
7
  import { n as registerGameplayBehaviors } from "./gameplay-sSqrUcnz.js";
8
8
  import { t as registerNodes2D } from "./register-Djwckzgx.js";
9
- import { t as registerNodes3D } from "./register-Bh_3Z39N.js";
9
+ import { t as registerNodes3D } from "./register-Lp2qn1Wq.js";
10
10
  import { t as registerNodesNet } from "./register-BFFE1Mh1.js";
11
11
  //#region src/test/index.ts
12
12
  /**
@@ -127,7 +127,7 @@ async function runScript(json, opts) {
127
127
  const { enablePhysics2D } = await import("./physics-2d-BiIdl51r.js").then((n) => n.r);
128
128
  await enablePhysics2D(engine);
129
129
  } else if (physics === "3d" || physics === "auto" && scene.dimension === "3d") {
130
- const { enablePhysics3D } = await import("./physics-3d-TmOuCy7J.js").then((n) => n.r);
130
+ const { enablePhysics3D } = await import("./physics-3d-CDlvC9Z_.js").then((n) => n.r);
131
131
  await enablePhysics3D(engine);
132
132
  }
133
133
  const failures = [];
@@ -232,7 +232,7 @@ async function createPlaySession(json, opts = {}) {
232
232
  const { enablePhysics2D } = await import("./physics-2d-BiIdl51r.js").then((n) => n.r);
233
233
  await enablePhysics2D(engine);
234
234
  } else if (physics === "3d" || physics === "auto" && scene.dimension === "3d") {
235
- const { enablePhysics3D } = await import("./physics-3d-TmOuCy7J.js").then((n) => n.r);
235
+ const { enablePhysics3D } = await import("./physics-3d-CDlvC9Z_.js").then((n) => n.r);
236
236
  await enablePhysics3D(engine);
237
237
  }
238
238
  const stepMs = 1e3 / (opts.fixedHz ?? 60);
@@ -1 +1 @@
1
- import{t as e}from"./index-CofH0yGY.js";async function t(t){return new n((await e(()=>import(`./GameServer-C56iOUgF.js`),[],import.meta.url)).GameServer,t)}var n=class{raw;active=new Map;reconnecting=!1;disposed=!1;constructor(e,t){this.raw=new e({...t})}get account(){return this.raw.account}get connected(){return this.raw.connected}connect(){return this.raw.connected?Promise.resolve(!0):(this.disposed=!1,this.rawConnect())}rawConnect(){return this.raw.connect({onDisconnect:()=>void this.reconnect()})}disconnect(){this.disposed=!0;for(let e of this.active.values())e.off();return this.active.clear(),this.raw.disconnect()}remoteFunction(e,t,n){return this.raw.remoteFunction(e,t,n)}track(e){let t=Symbol(`sub`),n={make:e,off:e()};return this.active.set(t,n),()=>{n.off(),this.active.delete(t)}}async reconnect(){if(!this.disposed&&!this.reconnecting){this.reconnecting=!0;try{await this.rawConnect();for(let e of this.active.values())e.off(),e.off=e.make()}finally{this.reconnecting=!1}}}subscribeRoomState(e,t){return this.track(()=>this.raw.subscribeRoomState(e,t))}subscribeRoomMyState(e,t){return this.track(()=>this.raw.subscribeRoomMyState(e,t))}subscribeRoomAllUserStates(e,t){return this.track(()=>this.raw.subscribeRoomAllUserStates(e,e=>{let n={};for(let t of e??[]){if(!t||typeof t.account!=`string`||t.__leaved)continue;let{account:e,__updated:r,__leaved:i,...a}=t;n[e]=a}t(n)}))}subscribeRoomCollection(e,t,n){return this.track(()=>this.raw.subscribeRoomCollection(e,t,({items:e})=>{let t={};for(let n of e??[])n&&typeof n.__id==`string`&&(t[n.__id]=n);n(t)}))}onRoomMessage(e,t,n){return this.track(()=>this.raw.onRoomMessage(e,t,n))}onRoomUserJoin(e,t){return this.track(()=>this.raw.onRoomUserJoin(e,t))}onRoomUserLeave(e,t){return this.track(()=>this.raw.onRoomUserLeave(e,t))}subscribeGlobalState(e){return this.track(()=>this.raw.subscribeGlobalState(e))}subscribeGlobalMyState(e){return this.track(()=>this.raw.subscribeGlobalMyState(e))}subscribeGlobalUserState(e,t){return this.track(()=>this.raw.subscribeGlobalUserState(e,t))}subscribeGlobalCollection(e,t){return this.track(()=>this.raw.subscribeGlobalCollection(e,({items:e})=>{let n={};for(let t of e??[])t&&typeof t.__id==`string`&&(n[t.__id]=t);t(n)}))}subscribeAsset(e,t){return this.track(()=>this.raw.subscribeAsset(e,t))}onGlobalMessage(e,t){return this.track(()=>this.raw.onGlobalMessage(e,t))}};export{t as createAgent8Server};
1
+ import{t as e}from"./index-N3Hnrv0w.js";async function t(t){return new n((await e(()=>import(`./GameServer-C56iOUgF.js`),[],import.meta.url)).GameServer,t)}var n=class{raw;active=new Map;reconnecting=!1;disposed=!1;constructor(e,t){this.raw=new e({...t})}get account(){return this.raw.account}get connected(){return this.raw.connected}connect(){return this.raw.connected?Promise.resolve(!0):(this.disposed=!1,this.rawConnect())}rawConnect(){return this.raw.connect({onDisconnect:()=>void this.reconnect()})}disconnect(){this.disposed=!0;for(let e of this.active.values())e.off();return this.active.clear(),this.raw.disconnect()}remoteFunction(e,t,n){return this.raw.remoteFunction(e,t,n)}track(e){let t=Symbol(`sub`),n={make:e,off:e()};return this.active.set(t,n),()=>{n.off(),this.active.delete(t)}}async reconnect(){if(!this.disposed&&!this.reconnecting){this.reconnecting=!0;try{await this.rawConnect();for(let e of this.active.values())e.off(),e.off=e.make()}finally{this.reconnecting=!1}}}subscribeRoomState(e,t){return this.track(()=>this.raw.subscribeRoomState(e,t))}subscribeRoomMyState(e,t){return this.track(()=>this.raw.subscribeRoomMyState(e,t))}subscribeRoomAllUserStates(e,t){return this.track(()=>this.raw.subscribeRoomAllUserStates(e,e=>{let n={};for(let t of e??[]){if(!t||typeof t.account!=`string`||t.__leaved)continue;let{account:e,__updated:r,__leaved:i,...a}=t;n[e]=a}t(n)}))}subscribeRoomCollection(e,t,n){return this.track(()=>this.raw.subscribeRoomCollection(e,t,({items:e})=>{let t={};for(let n of e??[])n&&typeof n.__id==`string`&&(t[n.__id]=n);n(t)}))}onRoomMessage(e,t,n){return this.track(()=>this.raw.onRoomMessage(e,t,n))}onRoomUserJoin(e,t){return this.track(()=>this.raw.onRoomUserJoin(e,t))}onRoomUserLeave(e,t){return this.track(()=>this.raw.onRoomUserLeave(e,t))}subscribeGlobalState(e){return this.track(()=>this.raw.subscribeGlobalState(e))}subscribeGlobalMyState(e){return this.track(()=>this.raw.subscribeGlobalMyState(e))}subscribeGlobalUserState(e,t){return this.track(()=>this.raw.subscribeGlobalUserState(e,t))}subscribeGlobalCollection(e,t){return this.track(()=>this.raw.subscribeGlobalCollection(e,({items:e})=>{let n={};for(let t of e??[])t&&typeof t.__id==`string`&&(n[t.__id]=t);t(n)}))}subscribeAsset(e,t){return this.track(()=>this.raw.subscribeAsset(e,t))}onGlobalMessage(e,t){return this.track(()=>this.raw.onGlobalMessage(e,t))}};export{t as createAgent8Server};