@3driseai/3drise-viewer 0.1.30 → 0.1.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/dist/animationsV2/ClipPlayer.d.ts.map +1 -1
  2. package/dist/animationsV2/ClipPlayer.js +14 -12
  3. package/dist/animationsV2/ClipsRuntime.d.ts +9 -0
  4. package/dist/animationsV2/ClipsRuntime.d.ts.map +1 -1
  5. package/dist/animationsV2/ClipsRuntime.js +54 -7
  6. package/dist/animationsV2/appliers.d.ts.map +1 -1
  7. package/dist/animationsV2/appliers.js +21 -2
  8. package/dist/animationsV2/clipUtils.d.ts.map +1 -1
  9. package/dist/animationsV2/clipUtils.js +4 -0
  10. package/dist/animationsV2/sequenceUtils.js +4 -5
  11. package/dist/animationsV2/sunSystemLive.d.ts +12 -0
  12. package/dist/animationsV2/sunSystemLive.d.ts.map +1 -0
  13. package/dist/animationsV2/sunSystemLive.js +8 -0
  14. package/dist/api/sceneHandle.d.ts +3 -1
  15. package/dist/api/sceneHandle.d.ts.map +1 -1
  16. package/dist/api/sceneHandle.js +2 -2
  17. package/dist/components/AdaptiveResolution.d.ts +9 -0
  18. package/dist/components/AdaptiveResolution.d.ts.map +1 -0
  19. package/dist/components/AdaptiveResolution.js +61 -0
  20. package/dist/components/CameraController.d.ts.map +1 -1
  21. package/dist/components/CameraController.js +24 -7
  22. package/dist/components/MoonDisc.d.ts.map +1 -1
  23. package/dist/components/MoonDisc.js +2 -1
  24. package/dist/components/OceanBuilder.d.ts +4 -1
  25. package/dist/components/OceanBuilder.d.ts.map +1 -1
  26. package/dist/components/OceanBuilder.js +2 -1
  27. package/dist/components/ParticlesGenerator.d.ts.map +1 -1
  28. package/dist/components/ParticlesGenerator.js +2 -0
  29. package/dist/components/SceneBuilder.d.ts.map +1 -1
  30. package/dist/components/SceneBuilder.js +5 -2
  31. package/dist/components/Skybox.d.ts.map +1 -1
  32. package/dist/components/Skybox.js +2 -1
  33. package/dist/components/SunLight.d.ts.map +1 -1
  34. package/dist/components/SunLight.js +3 -2
  35. package/dist/components/index.d.ts +1 -0
  36. package/dist/components/index.d.ts.map +1 -1
  37. package/dist/components/index.js +1 -0
  38. package/dist/components/lightBulb/LightBulb.d.ts.map +1 -1
  39. package/dist/components/lightBulb/LightBulb.js +7 -2
  40. package/dist/components/particles/FireworksParticles.d.ts +22 -0
  41. package/dist/components/particles/FireworksParticles.d.ts.map +1 -0
  42. package/dist/components/particles/FireworksParticles.js +1649 -0
  43. package/dist/components/particles/index.d.ts +2 -0
  44. package/dist/components/particles/index.d.ts.map +1 -1
  45. package/dist/components/particles/index.js +1 -0
  46. package/dist/components/terrain/TerrainSurface.d.ts.map +1 -1
  47. package/dist/components/terrain/TerrainSurface.js +26 -4
  48. package/dist/components/water/OceanSurface.d.ts.map +1 -1
  49. package/dist/components/water/OceanSurface.js +5 -1
  50. package/dist/components/water/types.d.ts +3 -1
  51. package/dist/components/water/types.d.ts.map +1 -1
  52. package/dist/hooks/useOceanMaterial.d.ts +6 -2
  53. package/dist/hooks/useOceanMaterial.d.ts.map +1 -1
  54. package/dist/hooks/useOceanMaterial.js +22 -2
  55. package/dist/hooks/useOrbitTargetFollow.d.ts +10 -0
  56. package/dist/hooks/useOrbitTargetFollow.d.ts.map +1 -0
  57. package/dist/hooks/useOrbitTargetFollow.js +194 -0
  58. package/dist/index.d.ts +1 -0
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +1 -0
  61. package/dist/shaders/glsl/terrainCore.glsl.d.ts +18 -3
  62. package/dist/shaders/glsl/terrainCore.glsl.d.ts.map +1 -1
  63. package/dist/shaders/glsl/terrainCore.glsl.js +42 -21
  64. package/dist/shaders/ocean/OceanSurfaceMaterial.d.ts +36 -11
  65. package/dist/shaders/ocean/OceanSurfaceMaterial.d.ts.map +1 -1
  66. package/dist/shaders/ocean/OceanSurfaceMaterial.js +96 -5
  67. package/dist/triggers/hosts/actions.d.ts.map +1 -1
  68. package/dist/triggers/hosts/actions.js +2 -0
  69. package/dist/triggers/hosts/timelineCues.js +1 -1
  70. package/dist/triggers/kinds/change.d.ts +4 -0
  71. package/dist/triggers/kinds/change.d.ts.map +1 -0
  72. package/dist/triggers/kinds/change.js +50 -0
  73. package/dist/triggers/kinds/index.d.ts +1 -0
  74. package/dist/triggers/kinds/index.d.ts.map +1 -1
  75. package/dist/triggers/kinds/index.js +1 -0
  76. package/dist/utils/CameraSingleton.d.ts +4 -0
  77. package/dist/utils/CameraSingleton.d.ts.map +1 -1
  78. package/dist/utils/CameraSingleton.js +4 -0
  79. package/dist/utils/deviceTier.d.ts +2 -0
  80. package/dist/utils/deviceTier.d.ts.map +1 -0
  81. package/dist/utils/deviceTier.js +16 -0
  82. package/dist/utils/environmentDrawOrder.d.ts +23 -0
  83. package/dist/utils/environmentDrawOrder.d.ts.map +1 -0
  84. package/dist/utils/environmentDrawOrder.js +22 -0
  85. package/dist/utils/terrainUniforms.d.ts +7 -0
  86. package/dist/utils/terrainUniforms.d.ts.map +1 -1
  87. package/dist/utils/terrainUniforms.js +15 -2
  88. package/package.json +2 -2
@@ -20,4 +20,6 @@ export { default as ShapeBurstParticles } from './ShapeBurstParticles';
20
20
  export type { ShapeBurstParticlesProps } from './ShapeBurstParticles';
21
21
  export { default as ShapeFlowParticles } from './ShapeFlowParticles';
22
22
  export type { ShapeFlowParticlesProps } from './ShapeFlowParticles';
23
+ export { default as FireworksParticles } from './FireworksParticles';
24
+ export type { FireworksParticlesProps } from './FireworksParticles';
23
25
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/particles/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AACzE,YAAY,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AACxE,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAC3E,YAAY,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AAC1E,OAAO,EAAE,OAAO,IAAI,2BAA2B,EAAE,MAAM,+BAA+B,CAAC;AACvF,YAAY,EAAE,gCAAgC,EAAE,MAAM,+BAA+B,CAAC;AACtF,OAAO,EAAE,OAAO,IAAI,yBAAyB,EAAE,MAAM,6BAA6B,CAAC;AACnF,YAAY,EAAE,8BAA8B,EAAE,MAAM,6BAA6B,CAAC;AAClF,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAC7E,YAAY,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAC5E,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AACzE,YAAY,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AACxE,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAC7E,YAAY,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAC5E,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACnE,YAAY,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAC/E,YAAY,EAAE,4BAA4B,EAAE,MAAM,2BAA2B,CAAC;AAC9E,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AACvE,YAAY,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AACrE,YAAY,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/particles/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AACzE,YAAY,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AACxE,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAC3E,YAAY,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AAC1E,OAAO,EAAE,OAAO,IAAI,2BAA2B,EAAE,MAAM,+BAA+B,CAAC;AACvF,YAAY,EAAE,gCAAgC,EAAE,MAAM,+BAA+B,CAAC;AACtF,OAAO,EAAE,OAAO,IAAI,yBAAyB,EAAE,MAAM,6BAA6B,CAAC;AACnF,YAAY,EAAE,8BAA8B,EAAE,MAAM,6BAA6B,CAAC;AAClF,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAC7E,YAAY,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAC5E,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AACzE,YAAY,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AACxE,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAC7E,YAAY,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAC5E,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACnE,YAAY,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAC/E,YAAY,EAAE,4BAA4B,EAAE,MAAM,2BAA2B,CAAC;AAC9E,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AACvE,YAAY,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AACrE,YAAY,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AACpE,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AACrE,YAAY,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC"}
@@ -10,3 +10,4 @@ export { default as NeonRainParticles } from './NeonRainParticles.js';
10
10
  export { default as MagneticBottleParticles } from './MagneticBottleParticles.js';
11
11
  export { default as ShapeBurstParticles } from './ShapeBurstParticles.js';
12
12
  export { default as ShapeFlowParticles } from './ShapeFlowParticles.js';
13
+ export { default as FireworksParticles } from './FireworksParticles.js';
@@ -1 +1 @@
1
- {"version":3,"file":"TerrainSurface.d.ts","sourceRoot":"","sources":["../../../src/components/terrain/TerrainSurface.tsx"],"names":[],"mappings":"AACA,OAAO,KAAqC,MAAM,OAAO,CAAC;AAkB1D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAEjD;;;;;;;;;;;;;;;;GAgBG;AACH,QAAA,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAkI/C,CAAC;AAEF,eAAe,cAAc,CAAC"}
1
+ {"version":3,"file":"TerrainSurface.d.ts","sourceRoot":"","sources":["../../../src/components/terrain/TerrainSurface.tsx"],"names":[],"mappings":"AACA,OAAO,KAAqC,MAAM,OAAO,CAAC;AAkB1D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAkBjD;;;;;;;;;;;;;;;;GAgBG;AACH,QAAA,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CA6I/C,CAAC;AAEF,eAAe,cAAc,CAAC"}
@@ -11,6 +11,21 @@ import { applyTerrainAmbient, applyTerrainTextures, applyTerrainUniforms } from
11
11
  import { useTerrainTextures } from '../../hooks/useTerrainTextures.js';
12
12
  import { getAnimatableProperties } from '@3driseai/3drise-core';
13
13
  import { applyConfigKeyframeAnimations, applyConfigMouseMove, } from '../../utils/configAnimations.js';
14
+ import { TERRAIN_DRAW_ORDER, ringDrawOrder } from '../../utils/environmentDrawOrder.js';
15
+ import { isMobileDevice } from '../../utils/deviceTier.js';
16
+ /**
17
+ * Phone / tablet tier, applied at render time only — the saved scene keeps its
18
+ * numbers, so the same project is full quality on a desktop. Measured on the
19
+ * desktop: textures within 100 m instead of 1500 saved ~2.8 ms, 5 octaves
20
+ * instead of 8 ~1.7 ms; the shadow march costs nothing measurable, so it stays.
21
+ * Fewer, coarser rings cut the vertex load by roughly half.
22
+ */
23
+ const MOBILE = {
24
+ maxSegments: 96,
25
+ maxLevels: 8,
26
+ maxOctaves: 5,
27
+ maxTextureDistance: 400,
28
+ };
14
29
  /**
15
30
  * The terrain renderer. One clipmap, one material, four landforms — they differ
16
31
  * by the shaping operator the height field applies, not by having separate
@@ -29,7 +44,8 @@ import { applyConfigKeyframeAnimations, applyConfigMouseMove, } from '../../util
29
44
  * the camera. Nothing here re-renders React.
30
45
  */
31
46
  const TerrainSurface = ({ id = '3drise-terrain', config, quality, skySettings, timeSettings, sunIntensity = 1, animations, mouseMove, onObjectReady, onObjectDispose, }) => {
32
- const clipmap = useTerrainClipmap(quality.levels, quality.segments, quality.cellSize);
47
+ const mobile = isMobileDevice();
48
+ const clipmap = useTerrainClipmap(mobile ? Math.min(quality.levels, MOBILE.maxLevels) : quality.levels, mobile ? Math.min(quality.segments, MOBILE.maxSegments) : quality.segments, quality.cellSize);
33
49
  const { state: sun, update: updateSun } = useCloudSun({ skySettings, timeSettings });
34
50
  const material = useMemo(() => new TerrainMaterial(), []);
35
51
  // The four ground textures, packed into arrays. Repacks only when a map URL
@@ -46,9 +62,9 @@ const TerrainSurface = ({ id = '3drise-terrain', config, quality, skySettings, t
46
62
  useEffect(() => { liveConfig.current = {}; }, [config]);
47
63
  useEffect(() => { liveQuality.current = {}; }, [quality]);
48
64
  // The `terrain` keyframe / pointer domains: config -> keyframes -> pointer ->
49
- // live drag, the clouds' order. Only the surface's lighting and colour knobs are
50
- // in the set — the registry marks every land-shape knob non-animatable, so the
51
- // ground itself never moves.
65
+ // live drag, the clouds' order. Every numeric / colour knob is in the set — shape
66
+ // and surface alike are shader uniforms, so the ground can rise or shift per frame;
67
+ // only the integer counts (octaves, strata) are structural in the registry.
52
68
  const animatable = useMemo(() => new Set(getAnimatableProperties({ type: 'terrain', config }, 'terrain').map(p => p.value)), [config.terrainType]);
53
69
  const override = useRef({});
54
70
  const startValues = useRef({});
@@ -91,6 +107,10 @@ const TerrainSurface = ({ id = '3drise-terrain', config, quality, skySettings, t
91
107
  applyConfigMouseMove(mouseMove.terrain.properties, config, o, state.pointer, animatable);
92
108
  }
93
109
  const cfg = { ...config, ...o, ...liveConfig.current };
110
+ if (mobile) {
111
+ cfg.octaves = Math.min(cfg.octaves, MOBILE.maxOctaves);
112
+ cfg.textureDistance = Math.min(cfg.textureDistance ?? 4000, MOBILE.maxTextureDistance);
113
+ }
94
114
  const qual = { ...quality, ...liveQuality.current };
95
115
  applyTerrainUniforms(material, cfg, qual);
96
116
  applyTerrainTextures(material, cfg, textures);
@@ -110,6 +130,8 @@ const TerrainSurface = ({ id = '3drise-terrain', config, quality, skySettings, t
110
130
  }
111
131
  });
112
132
  return (_jsx("group", { ref: rootRef, children: clipmap.levels.map((ring, i) => (_jsx("mesh", { ref: (m) => { meshRefs.current[i] = m; }, geometry: ring.geometry, material: material, receiveShadow: quality.receiveShadows,
133
+ // After the water, inner ring first: see environmentDrawOrder.
134
+ renderOrder: ringDrawOrder(TERRAIN_DRAW_ORDER, ring.level),
113
135
  // Displacement happens in the vertex shader, so the object-space
114
136
  // bounds describe a flat plane. Culling against them hides the
115
137
  // terrain the moment the camera tilts up.
@@ -1 +1 @@
1
- {"version":3,"file":"OceanSurface.d.ts","sourceRoot":"","sources":["../../../src/components/water/OceanSurface.tsx"],"names":[],"mappings":"AACA,OAAO,KAAiB,MAAM,OAAO,CAAC;AAItC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,QAAA,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CA2D3C,CAAC;AAEF,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"OceanSurface.d.ts","sourceRoot":"","sources":["../../../src/components/water/OceanSurface.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAiB,MAAM,OAAO,CAAC;AAItC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,QAAA,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CA+D3C,CAAC;AAEF,eAAe,YAAY,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { OCEAN_DRAW_ORDER, ringDrawOrder } from '../../utils/environmentDrawOrder.js';
2
3
  import { useRef } from 'react';
3
4
  import { useObjectReady } from '../../hooks/useObjectReady.js';
4
5
  import { useClipmap } from '../../hooks/useClipmap.js';
@@ -25,7 +26,7 @@ import { useOceanMaterial } from '../../hooks/useOceanMaterial.js';
25
26
  * a sea at 0. What connects them is computed — the shoreline is where the ground
26
27
  * crosses this surface — never configured.
27
28
  */
28
- const OceanSurface = ({ id = '3drise-water', config, quality, skySettings, timeSettings, sunIntensity = 1, animations, mouseMove, onObjectReady, onObjectDispose, }) => {
29
+ const OceanSurface = ({ id = '3drise-water', config, quality, skySettings, timeSettings, sunIntensity = 1, animations, mouseMove, terrain, onObjectReady, onObjectDispose, }) => {
29
30
  const clipmap = useClipmap(quality.levels, quality.segments, quality.cellSize);
30
31
  const meshRefs = useRef([]);
31
32
  const { material, rootRef } = useOceanMaterial({
@@ -37,6 +38,7 @@ const OceanSurface = ({ id = '3drise-water', config, quality, skySettings, timeS
37
38
  sunIntensity,
38
39
  animations,
39
40
  mouseMove,
41
+ terrain,
40
42
  onFrame: (cfg, _qual, state) => {
41
43
  // ONE origin for the whole pyramid, snapped to twice the finest cell.
42
44
  //
@@ -54,6 +56,8 @@ const OceanSurface = ({ id = '3drise-water', config, quality, skySettings, timeS
54
56
  });
55
57
  useObjectReady(rootRef, { id, type: 'ocean', config, quality }, onObjectReady, onObjectDispose);
56
58
  return (_jsx("group", { ref: rootRef, children: clipmap.levels.map((ring, i) => (_jsx("mesh", { ref: (m) => { meshRefs.current[i] = m; }, geometry: ring.geometry, material: material, receiveShadow: quality.receiveShadows,
59
+ // Before the terrain, inner ring first: see environmentDrawOrder.
60
+ renderOrder: ringDrawOrder(OCEAN_DRAW_ORDER, ring.level),
57
61
  // Displacement happens in the vertex shader, so the
58
62
  // object-space bounds describe a flat plane. Culling against
59
63
  // them hides the water the moment the camera tilts up.
@@ -1,6 +1,6 @@
1
1
  import type { SkySettings, TimeSettings } from '@3driseai/3drise-core';
2
2
  import type { ObjectReadyProps } from '../../types/objectReady';
3
- import type { OceanConfig, OceanMeshSettings, OceanQualityConfig, ObjectAnimations, MouseMoveInteractions } from '@3driseai/3drise-core';
3
+ import type { OceanConfig, OceanMeshSettings, OceanQualityConfig, ObjectAnimations, MouseMoveInteractions, TerrainConfig } from '@3driseai/3drise-core';
4
4
  /** Shared by every water type. The builder hands all four the same shape. */
5
5
  export interface OceanLayerProps extends ObjectReadyProps {
6
6
  /** Scene id, so a controller can findById() the water and drive it live. */
@@ -20,5 +20,7 @@ export interface OceanLayerProps extends ObjectReadyProps {
20
20
  animations?: ObjectAnimations;
21
21
  /** Pointer bindings; same `ocean` domain. */
22
22
  mouseMove?: MouseMoveInteractions;
23
+ /** The environment terrain, for the world ocean's shoreline and terrain shadow. */
24
+ terrain?: TerrainConfig | null;
23
25
  }
24
26
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/water/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAEzI,6EAA6E;AAC7E,MAAM,WAAW,eAAgB,SAAQ,gBAAgB;IACrD,4EAA4E;IAC5E,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,WAAW,CAAC;IACpB,OAAO,EAAE,kBAAkB,CAAC;IAC5B;;4DAEwD;IACxD,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sDAAsD;IACtD,YAAY,CAAC,EAAE,iBAAiB,CAAC;IACjC,mEAAmE;IACnE,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,6CAA6C;IAC7C,SAAS,CAAC,EAAE,qBAAqB,CAAC;CACrC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/water/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAExJ,6EAA6E;AAC7E,MAAM,WAAW,eAAgB,SAAQ,gBAAgB;IACrD,4EAA4E;IAC5E,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,WAAW,CAAC;IACpB,OAAO,EAAE,kBAAkB,CAAC;IAC5B;;4DAEwD;IACxD,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sDAAsD;IACtD,YAAY,CAAC,EAAE,iBAAiB,CAAC;IACjC,mEAAmE;IACnE,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,6CAA6C;IAC7C,SAAS,CAAC,EAAE,qBAAqB,CAAC;IAClC,mFAAmF;IACnF,OAAO,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;CAClC"}
@@ -2,7 +2,7 @@ import * as THREE from 'three';
2
2
  import { type RootState } from '@react-three/fiber';
3
3
  import type { SkySettings, TimeSettings } from '@3driseai/3drise-core';
4
4
  import { OceanSurfaceMaterial } from '../shaders/ocean/OceanSurfaceMaterial';
5
- import type { OceanConfig, OceanQualityConfig, ObjectAnimations, MouseMoveInteractions } from '@3driseai/3drise-core';
5
+ import type { OceanConfig, OceanQualityConfig, ObjectAnimations, MouseMoveInteractions, TerrainConfig } from '@3driseai/3drise-core';
6
6
  /**
7
7
  * Everything the two water hosts share: the material, the live-edit handle, and
8
8
  * the per-frame uniform push.
@@ -27,6 +27,10 @@ export interface UseOceanMaterialOptions {
27
27
  animations?: ObjectAnimations;
28
28
  /** Pointer bindings; same `ocean` domain. */
29
29
  mouseMove?: MouseMoveInteractions;
30
+ /** The environment terrain's config, when it is on: the water reads the ground
31
+ * under it for the shoreline (waves shoal, shallow colour, shore foam) and
32
+ * marches the terrain's shadow. World host only. */
33
+ terrain?: TerrainConfig | null;
30
34
  /** Runs after the uniforms are pushed, with the live overrides already
31
35
  * merged in — where a host does its own per-frame work (snapping the
32
36
  * clipmap to the camera, say) with the config it is actually rendering. */
@@ -38,6 +42,6 @@ export interface OceanMaterialHandle {
38
42
  * handle a controller reaches through `findById`. */
39
43
  rootRef: React.RefObject<THREE.Group | null>;
40
44
  }
41
- export declare function useOceanMaterial({ id, config, quality, skySettings, timeSettings, sunIntensity, animations, mouseMove, onFrame, }: UseOceanMaterialOptions): OceanMaterialHandle;
45
+ export declare function useOceanMaterial({ id, config, quality, skySettings, timeSettings, sunIntensity, animations, mouseMove, terrain, onFrame, }: UseOceanMaterialOptions): OceanMaterialHandle;
42
46
  export default useOceanMaterial;
43
47
  //# sourceMappingURL=useOceanMaterial.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useOceanMaterial.d.ts","sourceRoot":"","sources":["../../src/hooks/useOceanMaterial.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAGvE,OAAO,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAC;AAU7E,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAStH;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,uBAAuB;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,WAAW,CAAC;IACpB,OAAO,EAAE,kBAAkB,CAAC;IAC5B,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mEAAmE;IACnE,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,6CAA6C;IAC7C,SAAS,CAAC,EAAE,qBAAqB,CAAC;IAClC;;gFAE4E;IAC5E,OAAO,CAAC,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,kBAAkB,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI,CAAC;CACtF;AAED,MAAM,WAAW,mBAAmB;IAChC,QAAQ,EAAE,oBAAoB,CAAC;IAC/B;0DACsD;IACtD,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;CAChD;AAED,wBAAgB,gBAAgB,CAAC,EAC7B,EAAE,EACF,MAAM,EACN,OAAO,EACP,WAAW,EACX,YAAY,EACZ,YAAgB,EAChB,UAAU,EACV,SAAS,EACT,OAAO,GACV,EAAE,uBAAuB,GAAG,mBAAmB,CAuG/C;AAED,eAAe,gBAAgB,CAAC"}
1
+ {"version":3,"file":"useOceanMaterial.d.ts","sourceRoot":"","sources":["../../src/hooks/useOceanMaterial.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAGvE,OAAO,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAC;AAU7E,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AASrI;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,uBAAuB;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,WAAW,CAAC;IACpB,OAAO,EAAE,kBAAkB,CAAC;IAC5B,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mEAAmE;IACnE,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,6CAA6C;IAC7C,SAAS,CAAC,EAAE,qBAAqB,CAAC;IAClC;;yDAEqD;IACrD,OAAO,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IAC/B;;gFAE4E;IAC5E,OAAO,CAAC,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,kBAAkB,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI,CAAC;CACtF;AAED,MAAM,WAAW,mBAAmB;IAChC,QAAQ,EAAE,oBAAoB,CAAC;IAC/B;0DACsD;IACtD,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;CAChD;AAED,wBAAgB,gBAAgB,CAAC,EAC7B,EAAE,EACF,MAAM,EACN,OAAO,EACP,WAAW,EACX,YAAY,EACZ,YAAgB,EAChB,UAAU,EACV,SAAS,EACT,OAAO,EACP,OAAO,GACV,EAAE,uBAAuB,GAAG,mBAAmB,CA0H/C;AAED,eAAe,gBAAgB,CAAC"}
@@ -5,7 +5,7 @@ import { useCloudSun } from './useCloudSun.js';
5
5
  import { OCEAN_LIVE_KEY } from './useOceanLive.js';
6
6
  import { OceanSurfaceMaterial } from '../shaders/ocean/OceanSurfaceMaterial.js';
7
7
  import { applySunUniforms } from '../utils/cloudUniforms.js';
8
- import { applyTerrainAmbient } from '../utils/terrainUniforms.js';
8
+ import { applyTerrainAmbient, applyTerrainFieldUniforms } from '../utils/terrainUniforms.js';
9
9
  import { applyOceanUniforms } from '../utils/oceanUniforms.js';
10
10
  import { resolveMoon, moonlightFactor, MOON_LIGHT_COLOR } from '../utils/skyMoon.js';
11
11
  /** Moonlight on the water relative to the sun's (sunIntensity 1). The key light
@@ -14,7 +14,7 @@ import { resolveMoon, moonlightFactor, MOON_LIGHT_COLOR } from '../utils/skyMoon
14
14
  const MOON_WATER_STRENGTH = 0.15;
15
15
  import { getAnimatableProperties } from '@3driseai/3drise-core';
16
16
  import { applyConfigKeyframeAnimations, applyConfigMouseMove, } from '../utils/configAnimations.js';
17
- export function useOceanMaterial({ id, config, quality, skySettings, timeSettings, sunIntensity = 1, animations, mouseMove, onFrame, }) {
17
+ export function useOceanMaterial({ id, config, quality, skySettings, timeSettings, sunIntensity = 1, animations, mouseMove, terrain, onFrame, }) {
18
18
  const { state: sun, update: updateSun } = useCloudSun({ skySettings, timeSettings });
19
19
  const moon = useRef({ direction: new THREE.Vector3(), illumination: 0 });
20
20
  const material = useMemo(() => new OceanSurfaceMaterial(), []);
@@ -82,6 +82,26 @@ export function useOceanMaterial({ id, config, quality, skySettings, timeSetting
82
82
  const cfg = { ...config, ...o, ...liveConfig.current };
83
83
  const qual = { ...quality, ...liveQuality.current };
84
84
  applyOceanUniforms(shader, cfg, qual);
85
+ // The shoreline. OCEAN_TERRAIN compiles the terrain field into the water;
86
+ // it flips only when terrain is switched on or off, never per frame.
87
+ const withTerrain = !!terrain?.enabled;
88
+ const defines = (shader.defines ??= {});
89
+ if (withTerrain !== ('OCEAN_TERRAIN' in defines)) {
90
+ if (withTerrain)
91
+ defines.OCEAN_TERRAIN = '';
92
+ else
93
+ delete defines.OCEAN_TERRAIN;
94
+ shader.needsUpdate = true;
95
+ }
96
+ const u = shader.uniforms;
97
+ u.u_shoreDepth.value = Math.max(cfg.shoreDepth ?? 3, 0.05);
98
+ u.u_shoreFoam.value = cfg.shoreFoam ?? 1;
99
+ if (withTerrain && terrain) {
100
+ applyTerrainFieldUniforms(shader, terrain);
101
+ u.u_sunShadow.value = cfg.terrainShadow ?? 1;
102
+ u.u_shadowSteps.value = Math.round(qual.terrainShadowSteps ?? 10);
103
+ u.u_shadowDistance.value = Math.max(qual.terrainShadowDistance ?? 4000, 1);
104
+ }
85
105
  shader.uniforms.u_time.value = state.clock.getElapsedTime();
86
106
  // World size of one pixel at unit distance. The detail fade and the
87
107
  // specular filtering are both screen-space decisions — whether a ripple
@@ -0,0 +1,10 @@
1
+ import type { ControlsConfig, OrbitTargetObject } from '@3driseai/3drise-core';
2
+ export declare function useOrbitTargetFollow(controlsRef: React.MutableRefObject<any>, targetObject: OrbitTargetObject | null | undefined, followObject: OrbitTargetObject | null | undefined,
3
+ /** True while something else owns the camera position (a camera animation). */
4
+ isCameraBusy: () => boolean,
5
+ /** False when the orbit controls are switched off: the camera keeps its own orientation. */
6
+ orbiting: boolean,
7
+ /** The authored controls — their limits are what the widening falls back to. */
8
+ authored: ControlsConfig): void;
9
+ export default useOrbitTargetFollow;
10
+ //# sourceMappingURL=useOrbitTargetFollow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useOrbitTargetFollow.d.ts","sourceRoot":"","sources":["../../src/hooks/useOrbitTargetFollow.ts"],"names":[],"mappings":"AA+CA,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAsE/E,wBAAgB,oBAAoB,CAChC,WAAW,EAAE,KAAK,CAAC,gBAAgB,CAAC,GAAG,CAAC,EACxC,YAAY,EAAE,iBAAiB,GAAG,IAAI,GAAG,SAAS,EAClD,YAAY,EAAE,iBAAiB,GAAG,IAAI,GAAG,SAAS;AAClD,+EAA+E;AAC/E,YAAY,EAAE,MAAM,OAAO;AAC3B,4FAA4F;AAC5F,QAAQ,EAAE,OAAO;AACjB,gFAAgF;AAChF,QAAQ,EAAE,cAAc,QAqE3B;AAED,eAAe,oBAAoB,CAAC"}
@@ -0,0 +1,194 @@
1
+ // =============================================================================
2
+ // useOrbitTargetFollow — the camera tracks scene objects
3
+ // =============================================================================
4
+ //
5
+ // Two independent settings on `controls`:
6
+ //
7
+ // targetObject ORBIT TARGET — the orbit point (what the camera looks at) sits on the
8
+ // object's centre every frame. The camera position is not touched; it
9
+ // only turns to keep looking at the object.
10
+ // followObject FOLLOW — the camera travels with the object: it (and the orbit point)
11
+ // moves by exactly as much as the object moved, so the distance and
12
+ // angle stay the same. Translation only — the object spinning in place
13
+ // does not swing the camera around.
14
+ //
15
+ // Both together: the camera rides along with one object and looks at another (or the
16
+ // same one).
17
+ //
18
+ // Smoothness — why this runs in scene.onBeforeRender and not in useFrame:
19
+ // clips, actions, effects and physics move objects in useFrame callbacks, some of them
20
+ // after this component's. Reading the object in useFrame would see last frame's position
21
+ // half the time, so the camera would trail the object by a frame and stutter. Three calls
22
+ // scene.onBeforeRender inside gl.render, after it has updated every matrixWorld and before
23
+ // it builds the view — so here every object is exactly where it is drawn this frame. We
24
+ // move the camera, re-aim it and update its matrix ourselves, and this frame's image is
25
+ // already right.
26
+ //
27
+ // The object's centre is its bounding-box centre, measured once in the object's own space
28
+ // when it is found and then carried by its matrixWorld every frame — exact under animation
29
+ // and cheap (no per-frame box).
30
+ //
31
+ // Limits vs a moving target: OrbitControls clamps the camera's distance and angles to
32
+ // the target on every update. When the TARGET moves (the plane climbs above a camera
33
+ // limited to 88°), that clamp would drag the camera along — the camera must not move.
34
+ // So while looking at an object, the limits are widened each frame just enough to hold
35
+ // where the camera is now: the object can take the view outside the authored range,
36
+ // the user still can't rotate / zoom further out of it, and moving back in tightens
37
+ // them to the authored values again. The authored values come from `controls`.
38
+ //
39
+ // Height limits (controls.minHeight / maxHeight) are world Y, not relative to the target,
40
+ // and are applied after everything else in the frame.
41
+ //
42
+ // While a camera animation (path / gsap) runs, follow does not move the camera — the
43
+ // animation owns its position; the follow just keeps counting, so nothing jumps after.
44
+ //
45
+ import { useEffect, useRef } from 'react';
46
+ import { useFrame, useThree } from '@react-three/fiber';
47
+ import { Box3, Vector3 } from 'three';
48
+ import { orbitAngleLimits } from '@3driseai/3drise-core';
49
+ import { findById } from '../utils/sceneQuery.js';
50
+ const RESOLVE_EVERY = 30; // frames between lookups while the object is missing
51
+ const _box = new Box3();
52
+ const _p = new Vector3();
53
+ const _d = new Vector3();
54
+ const _off = new Vector3();
55
+ const keyOf = (t) => (t?.id ? `${t.id}|${t.node ?? ''}` : '');
56
+ /** Finds one object and gives its centre in world space, every frame, without lag. */
57
+ class Tracker {
58
+ key = '';
59
+ obj = null;
60
+ local = new Vector3(); // bounding-box centre in the object's own space
61
+ missingSince = -1e9;
62
+ locked = false; // first point taken
63
+ last = new Vector3(); // point at the previous frame
64
+ sync(t) {
65
+ const key = keyOf(t);
66
+ if (key === this.key)
67
+ return;
68
+ this.key = key;
69
+ this.obj = null;
70
+ this.missingSince = -1e9;
71
+ this.locked = false;
72
+ }
73
+ /** Writes the object's centre into `out`; false while the object is not there. */
74
+ point(t, frame, out) {
75
+ if (!this.obj || !this.obj.parent) {
76
+ if (frame - this.missingSince < RESOLVE_EVERY)
77
+ return false;
78
+ const root = findById(t.id);
79
+ const obj = root && t.node ? root.getObjectByName(t.node) ?? null : root;
80
+ if (!obj) {
81
+ this.missingSince = frame;
82
+ this.obj = null;
83
+ return false;
84
+ }
85
+ this.obj = obj;
86
+ this.locked = false;
87
+ obj.updateWorldMatrix(true, true);
88
+ _box.setFromObject(obj);
89
+ if (_box.isEmpty())
90
+ this.local.set(0, 0, 0);
91
+ else
92
+ obj.worldToLocal(_box.getCenter(this.local));
93
+ }
94
+ out.copy(this.local).applyMatrix4(this.obj.matrixWorld);
95
+ return true;
96
+ }
97
+ }
98
+ /** Authored limits, widened to include the camera's current distance and angles to the target. */
99
+ function widenLimits(controls, cameraPos, auth) {
100
+ _off.copy(cameraPos).sub(controls.target);
101
+ const r = _off.length();
102
+ if (r < 1e-6)
103
+ return;
104
+ const phi = Math.acos(Math.min(1, Math.max(-1, _off.y / r)));
105
+ const theta = Math.atan2(_off.x, _off.z);
106
+ const lim = orbitAngleLimits(auth);
107
+ const minD = auth.minDistance ?? 0, maxD = auth.maxDistance ?? Infinity;
108
+ controls.minDistance = Math.min(minD, r);
109
+ controls.maxDistance = Math.max(maxD, r);
110
+ controls.minPolarAngle = Math.min(lim.minPolarAngle, phi);
111
+ controls.maxPolarAngle = Math.max(lim.maxPolarAngle, phi);
112
+ // Azimuth: only when it is limited (the full circle needs nothing).
113
+ if (lim.maxAzimuthAngle - lim.minAzimuthAngle < Math.PI * 2 - 1e-6) {
114
+ controls.minAzimuthAngle = Math.min(lim.minAzimuthAngle, theta);
115
+ controls.maxAzimuthAngle = Math.max(lim.maxAzimuthAngle, theta);
116
+ }
117
+ }
118
+ export function useOrbitTargetFollow(controlsRef, targetObject, followObject,
119
+ /** True while something else owns the camera position (a camera animation). */
120
+ isCameraBusy,
121
+ /** False when the orbit controls are switched off: the camera keeps its own orientation. */
122
+ orbiting,
123
+ /** The authored controls — their limits are what the widening falls back to. */
124
+ authored) {
125
+ const { scene, camera } = useThree();
126
+ const live = useRef({ targetObject, followObject, isCameraBusy, orbiting, authored });
127
+ live.current = { targetObject, followObject, isCameraBusy, orbiting, authored };
128
+ const frame = useRef(0);
129
+ const done = useRef(-1);
130
+ const aim = useRef(new Tracker());
131
+ const follow = useRef(new Tracker());
132
+ // A frame counter, so the render hook runs once per frame even if the scene is
133
+ // rendered more than once (reflections, previews).
134
+ useFrame(() => { frame.current++; });
135
+ useEffect(() => {
136
+ const prev = scene.onBeforeRender;
137
+ scene.onBeforeRender = function (...args) {
138
+ prev.apply(this, args);
139
+ const cam = args[2];
140
+ if (cam !== camera || done.current === frame.current)
141
+ return;
142
+ done.current = frame.current;
143
+ const controls = controlsRef.current;
144
+ const { targetObject: tObj, followObject: fObj, isCameraBusy: busy, orbiting: orb, authored: auth } = live.current;
145
+ const f = follow.current, a = aim.current;
146
+ f.sync(fObj);
147
+ a.sync(tObj);
148
+ let moved = false;
149
+ // FOLLOW: move the camera and the orbit point with the object.
150
+ if (fObj && f.point(fObj, frame.current, _p)) {
151
+ if (!f.locked) {
152
+ f.last.copy(_p);
153
+ f.locked = true;
154
+ }
155
+ _d.copy(_p).sub(f.last);
156
+ f.last.copy(_p);
157
+ if (_d.lengthSq() > 0 && !busy()) {
158
+ camera.position.add(_d);
159
+ if (controls)
160
+ controls.target.add(_d);
161
+ moved = true;
162
+ }
163
+ }
164
+ // ORBIT TARGET: look at the object; the camera stays where it is.
165
+ if (tObj && controls && a.point(tObj, frame.current, _p)) {
166
+ if (!a.locked || !controls.target.equals(_p)) {
167
+ controls.target.copy(_p);
168
+ if (orb)
169
+ camera.lookAt(_p);
170
+ a.locked = true;
171
+ moved = true;
172
+ }
173
+ }
174
+ // HEIGHT LIMITS (world Y): last, so nothing — user, target, follow, a clip — ends
175
+ // the frame under the floor or over the ceiling. The camera keeps facing the target.
176
+ const minH = typeof auth.minHeight === 'number' ? auth.minHeight : -Infinity;
177
+ const maxH = typeof auth.maxHeight === 'number' ? auth.maxHeight : Infinity;
178
+ const y = Math.min(maxH, Math.max(minH, camera.position.y));
179
+ if (y !== camera.position.y) {
180
+ camera.position.y = y;
181
+ if (orb && controls)
182
+ camera.lookAt(controls.target);
183
+ moved = true;
184
+ }
185
+ if (moved)
186
+ camera.updateMatrixWorld();
187
+ // Limits hold the camera where the moving target left it (see the header).
188
+ if (tObj && controls && a.locked)
189
+ widenLimits(controls, camera.position, auth);
190
+ };
191
+ return () => { scene.onBeforeRender = prev; };
192
+ }, [scene, camera, controlsRef]);
193
+ }
194
+ export default useOrbitTargetFollow;
package/dist/index.d.ts CHANGED
@@ -25,6 +25,7 @@ export type { Object3DRefLike, Object3DSource } from './utils/resolveObject3D';
25
25
  export { applyTerrainUniforms, applyTerrainTextures } from './utils/terrainUniforms';
26
26
  export { useTerrainTextures } from './hooks/useTerrainTextures';
27
27
  export { terrainHeightAt } from './utils/terrainHeight';
28
+ export { isMobileDevice } from './utils/deviceTier';
28
29
  export { setTerrainTextureResolver, resolveTerrainTextureSets, useTerrainTextureResolverVersion, type TerrainTextureResolver } from './utils/terrainTextureResolver';
29
30
  export { landHeightAt, landWorldHeightAt, landSeedOffset, landToTerrainConfig, LAND_SEGMENTS } from './utils/landPatch';
30
31
  export type { TerrainTextureState } from './hooks/useTerrainTextures';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAGtD,OAAO,EAAE,iBAAiB,IAAI,iBAAiB,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAG5F,cAAc,SAAS,CAAC;AAGxB,cAAc,cAAc,CAAC;AAG7B,cAAc,SAAS,CAAC;AAExB,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,MAAM,CAAC;AACrB,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,UAAU,EAAE,WAAW,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,eAAe,EAAE,aAAa,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAE7N,YAAY,EAAE,oBAAoB,IAAI,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AAGjG,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,iBAAiB,EAAE,WAAW,GAAG,MAAM,iBAAiB,CAAC;AACrG,OAAO,EAAE,mBAAmB,IAAI,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AACvF,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAC7F,YAAY,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC/E,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AACrF,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,yBAAyB,EAAE,yBAAyB,EAAE,gCAAgC,EAAE,KAAK,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACrK,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACxH,YAAY,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACvF,YAAY,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,EACL,oBAAoB,EACpB,4BAA4B,EAC5B,qBAAqB,EACrB,eAAe,EACf,8BAA8B,GAC/B,MAAM,yBAAyB,CAAC;AACjC,YAAY,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,sBAAsB,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AACpG,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AACnF,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAC1F,YAAY,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACrF,YAAY,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,iBAAiB,EACjB,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,6BAA6B,EAC7B,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,gCAAgC,CAAC;AAExC,OAAO,EACL,6BAA6B,EAC7B,oBAAoB,EACpB,kBAAkB,EAClB,aAAa,EACb,kBAAkB,GACnB,MAAM,0BAA0B,CAAC;AAClC,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAGlF,OAAO,EACL,sBAAsB,EACtB,aAAa,EACb,sBAAsB,EACtB,oBAAoB,EACpB,qBAAqB,EACrB,wBAAwB,GACzB,MAAM,kCAAkC,CAAC;AAC1C,YAAY,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAC5F,OAAO,EACL,cAAc,EACd,wBAAwB,EACxB,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,0BAA0B,CAAC;AAClC,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EACL,aAAa,EACb,qBAAqB,EACrB,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,EAClB,sBAAsB,EACtB,oBAAoB,EACpB,UAAU,EACV,eAAe,EACf,aAAa,GACd,MAAM,yBAAyB,CAAC;AACjC,YAAY,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EACL,wBAAwB,EACxB,yBAAyB,EACzB,gBAAgB,EAChB,sBAAsB,EACtB,4BAA4B,GAC7B,MAAM,4BAA4B,CAAC;AACpC,YAAY,EACV,sBAAsB,EACtB,eAAe,EACf,uBAAuB,GACxB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,iBAAiB,EACjB,gBAAgB,EAChB,yBAAyB,EACzB,eAAe,EACf,kBAAkB,EAClB,yBAAyB,EACzB,cAAc,EACd,eAAe,EACf,oBAAoB,GACrB,MAAM,0BAA0B,CAAC;AAClC,YAAY,EACV,mBAAmB,EACnB,eAAe,EACf,gBAAgB,EAChB,iBAAiB,EACjB,yBAAyB,EACzB,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,EACpB,wBAAwB,GACzB,MAAM,+BAA+B,CAAC;AACvC,YAAY,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC"}
package/dist/index.js CHANGED
@@ -26,6 +26,7 @@ export { resolveObject3D } from './utils/resolveObject3D.js';
26
26
  export { applyTerrainUniforms, applyTerrainTextures } from './utils/terrainUniforms.js';
27
27
  export { useTerrainTextures } from './hooks/useTerrainTextures.js';
28
28
  export { terrainHeightAt } from './utils/terrainHeight.js';
29
+ export { isMobileDevice } from './utils/deviceTier.js';
29
30
  export { setTerrainTextureResolver, resolveTerrainTextureSets, useTerrainTextureResolverVersion } from './utils/terrainTextureResolver.js';
30
31
  export { landHeightAt, landWorldHeightAt, landSeedOffset, landToTerrainConfig, LAND_SEGMENTS } from './utils/landPatch.js';
31
32
  export { applyLandscapeMaps, applyLandscapeUniforms } from './utils/landscapeUniforms.js';
@@ -26,8 +26,14 @@
26
26
  * per octave, nine octaves, several evaluations per pixel puts it into the
27
27
  * billions of transcendentals per frame.
28
28
  */
29
- /** Every uniform the height field and the shading read. */
30
- export declare const terrainUniforms = "\nuniform int u_terrainType;\n\nuniform float u_elevation;\nuniform float u_seaLevel;\nuniform float u_baseScale; // 1 / featureSize\nuniform int u_octaves;\nuniform float u_lacunarity;\nuniform float u_gain;\nuniform float u_ridge;\nuniform float u_warpStrength;\nuniform float u_warpScale;\nuniform float u_erosion;\nuniform float u_terraces;\nuniform float u_terraceSharp;\nuniform vec2 u_bearingDir;\nuniform float u_duneFreq; // 2pi / duneWavelength\nuniform float u_duneAsym;\nuniform float u_plateau;\nuniform float u_valleyDepth;\nuniform float u_valleyWidth;\n\nuniform float u_cellSize;\nuniform float u_curvature;\n\n// surface\nuniform vec3 u_lowColor, u_midColor, u_rockColor, u_peakColor;\nuniform float u_slopeRockStart, u_slopeRockEnd;\nuniform float u_peakLevel, u_peakBlend, u_peakSlopeFalloff;\nuniform float u_macroVariation, u_macroScale;\nuniform float u_detailStrength, u_detailScale, u_detailFade;\nuniform float u_roughness, u_specular;\n\n// lighting. u_skyAmbient and u_groundAmbient come from useCloudSun through\n// applySunUniforms - the same objects that light the clouds. They are constant\n// across the frame, so computing them on the CPU rather than calling skyRadiance\n// per pixel removes an acos, a pow and three exps from every fragment.\nuniform vec3 u_skyAmbient;\nuniform vec3 u_groundAmbient;\nuniform float u_ambient, u_bounce, u_sunShadow, u_shadowSoftness;\nuniform float u_shadowDistance;\nuniform float u_fogDensity, u_exposure;\nuniform int u_shadowSteps;\n\n// Ground textures. The far colour per layer is always set: the flat colour for an\n// untextured layer, the texture's own mean x tint for a textured one, so the\n// near-to-far fade has no colour jump.\nuniform vec3 u_tFar[4];\n#ifdef TERRAIN_TEXTURED\nuniform sampler2DArray u_tColor; // albedo, sRGB\nuniform sampler2DArray u_tNormal; // rgb tangent normal (GL), a surface height\nuniform sampler2DArray u_tOrm; // r ambient occlusion, g roughness\nuniform vec4 u_tLayer[4]; // 1/tileSize, normalStrength, textured 0|1, -\nuniform vec3 u_tTint[4]; // layer colour / texture mean: the texture averages to the layer colour\nuniform vec4 u_tMeanC[4]; // per-array layer means, for the stochastic blend\nuniform vec4 u_tMeanN[4];\nuniform vec4 u_tMeanO[4];\nuniform float u_tDistance;\nuniform float u_tHeightBlend;\n#endif\n\n// A land object: a bounded patch of this field, placed by its object's transform.\n// Its \"terrain space\" is metres from the object's origin, unrotated, plus a seed\n// offset; height is above the object's base. See TerrainMaterial's patch vertex.\n#ifdef TERRAIN_PATCH\nuniform vec2 u_patchSize; // footprint in metres (object scale x / z)\nuniform vec2 u_patchOffset; // terrain-space centre: the seed's offset into the field\nuniform float u_edgeWidth; // metres over which the land eases down to its base\nuniform vec3 u_camTerrain; // the camera, in terrain space\nuniform float u_scaleY; // object scale y: terrain metres = local y x this\nuniform float u_patchCell; // metres per grid cell\n#define TERRAIN_CAM u_camTerrain\n#else\n#define TERRAIN_CAM cameraPosition\n#endif\n";
29
+ /**
30
+ * The uniforms the height field and the sun-shadow march read — and nothing else,
31
+ * so another material (the ocean) can include the field without redeclaring its
32
+ * own u_curvature, u_exposure, u_roughness… GLSL refuses a uniform declared twice.
33
+ */
34
+ export declare const terrainFormUniforms = "\nuniform int u_terrainType;\n\nuniform float u_elevation;\nuniform float u_seaLevel;\nuniform float u_baseScale; // 1 / featureSize\nuniform int u_octaves;\nuniform float u_lacunarity;\nuniform float u_gain;\nuniform float u_ridge;\nuniform float u_warpStrength;\nuniform float u_warpScale;\nuniform float u_erosion;\nuniform float u_terraces;\nuniform float u_terraceSharp;\nuniform vec2 u_bearingDir;\nuniform float u_duneFreq; // 2pi / duneWavelength\nuniform float u_duneAsym;\nuniform float u_plateau;\nuniform float u_valleyDepth;\nuniform float u_valleyWidth;\n\n// The analytic sun-shadow march (terrainShadow) - shared with the ocean, which\n// marches the same field to put headland shadows on the water.\nuniform float u_sunShadow, u_shadowSoftness;\nuniform float u_shadowDistance;\nuniform int u_shadowSteps;\n\n// A land object: a bounded patch of this field, placed by its object's transform.\n// Its \"terrain space\" is metres from the object's origin, unrotated, plus a seed\n// offset; height is above the object's base. See TerrainMaterial's patch vertex.\n#ifdef TERRAIN_PATCH\nuniform vec2 u_patchSize; // footprint in metres (object scale x / z)\nuniform vec2 u_patchOffset; // terrain-space centre: the seed's offset into the field\nuniform float u_edgeWidth; // metres over which the land eases down to its base\nuniform vec3 u_camTerrain; // the camera, in terrain space\nuniform float u_scaleY; // object scale y: terrain metres = local y x this\nuniform float u_patchCell; // metres per grid cell\n#define TERRAIN_CAM u_camTerrain\n#else\n#define TERRAIN_CAM cameraPosition\n#endif\n";
35
+ /** The terrain surface's own uniforms: shading, lighting, textures. */
36
+ export declare const terrainSurfaceUniforms = "\nuniform float u_cellSize;\nuniform float u_curvature;\n\n// surface\nuniform vec3 u_lowColor, u_midColor, u_rockColor, u_peakColor;\nuniform float u_slopeRockStart, u_slopeRockEnd;\nuniform float u_peakLevel, u_peakBlend, u_peakSlopeFalloff;\nuniform float u_macroVariation, u_macroScale;\nuniform float u_detailStrength, u_detailScale, u_detailFade;\nuniform float u_roughness, u_specular;\n\n// lighting. u_skyAmbient and u_groundAmbient come from useCloudSun through\n// applySunUniforms - the same objects that light the clouds. They are constant\n// across the frame, so computing them on the CPU rather than calling skyRadiance\n// per pixel removes an acos, a pow and three exps from every fragment.\nuniform vec3 u_skyAmbient;\nuniform vec3 u_groundAmbient;\nuniform float u_ambient, u_bounce;\nuniform float u_fogDensity, u_exposure;\n\n// Ground textures. The far colour per layer is always set: the flat colour for an\n// untextured layer, the texture's own mean x tint for a textured one, so the\n// near-to-far fade has no colour jump.\nuniform vec3 u_tFar[4];\n#ifdef TERRAIN_TEXTURED\nuniform sampler2DArray u_tColor; // albedo, sRGB\nuniform sampler2DArray u_tNormal; // rgb tangent normal (GL), a surface height\nuniform sampler2DArray u_tOrm; // r ambient occlusion, g roughness\nuniform vec4 u_tLayer[4]; // 1/tileSize, normalStrength, textured 0|1, -\nuniform vec3 u_tTint[4]; // layer colour / texture mean: the texture averages to the layer colour\nuniform vec4 u_tMeanC[4]; // per-array layer means, for the stochastic blend\nuniform vec4 u_tMeanN[4];\nuniform vec4 u_tMeanO[4];\nuniform float u_tDistance;\nuniform float u_tHeightBlend;\n#endif\n\n";
31
37
  /**
32
38
  * Gradient noise with an analytic derivative.
33
39
  *
@@ -70,7 +76,8 @@ export declare const terrainHeight = "\n#define TERRAIN_MOUNTAINS 0\n#define TER
70
76
  * The terminator itself stays per-pixel: `terrainLight` takes N·L from the
71
77
  * fragment's own normal, so only the CAST shadow is interpolated.
72
78
  */
73
- export declare const terrainShading = "\n/** 1 lit, 0 shadowed, soft in between. */\nfloat terrainSunShadow(vec3 p, vec3 n, float lodCell, int steps){\n if (steps < 1 || u_sunShadow <= 0.0) return 1.0;\n // Below the horizon there is nothing to shadow; the march would also walk\n // downhill forever.\n if (u_sunDir.y <= 0.015) return 1.0;\n // A surface facing away from the sun is shadowed by definition, and marching\n // from it only finds the ground it is already part of.\n if (dot(n, u_sunDir) <= 0.0) return 0.0;\n\n // Lift off the surface before starting, or every sample on a steep face - a\n // terrace riser above all - reads its own ground as an occluder and the whole\n // wall goes black in a grid of triangles. Start ON the offset surface and\n // compare raw heights; subtracting the bias from the height difference instead\n // makes the first sample fail on any slope tilted a few degrees from the sun.\n float bias = max(lodCell, u_elevation * 0.003);\n vec3 origin = p + n * bias;\n\n float shadowLod = (1.0 / max(u_baseScale, 1e-9)) * 0.008;\n\n float res = 1.0;\n float t = bias * 3.0;\n float maxT = (u_elevation * 2.5) / max(u_sunDir.y, 0.05);\n\n for (int i = 0; i < 48; i++){\n if (i >= steps || t > maxT) break;\n vec3 sp = origin + u_sunDir * t;\n // The occluder is sampled at the SHADING lod, coarsening with distance -\n // never at a fixed coarse lod. Testing a fine surface point against a coarse\n // height field puts the whole valley floor in shadow, because the coarse\n // field sits above the fine one wherever the fine one has been cut down.\n // Sampled MUCH coarser than the surface being shaded. A shadow does not need\n // nine octaves: what casts a visible shadow is the landform, and marching the\n // fine field costs the same nine octaves per step for detail that is below a\n // pixel by the time it lands. Floored against the landform's own size, so the\n // march runs about five octaves instead of nine wherever it starts.\n float h = terrainOccluderAt(sp.xz, max(max(lodCell * 2.0, t * 0.06), shadowLod));\n float diff = sp.y - h;\n res = min(res, u_shadowSoftness * diff / t);\n if (res <= 0.0) return 0.0;\n // Geometric growth: near the surface the occluder is metres away, at the end\n // of the march it is kilometres, and a linear step wastes every sample at one\n // end or the other. 1.7 rather than 1.42 so twelve steps actually reach maxT -\n // at 1.42 the march ran out around 700 m and long shadows at low sun simply\n // did not exist, which is a quality bug that happened to look like a cheap one.\n t = t * 1.7 + bias;\n }\n return saturate(res);\n}\n\n/**\n * The shadow, faded out with distance and given a step budget that falls with it.\n *\n * The march is the most expensive thing left in the renderer, and past a few\n * kilometres it is buying nothing: aerial perspective has already washed most of\n * the contrast out of the ground, and a ridge shadow out there is a couple of\n * pixels wide. Skipping it entirely beyond u_shadowDistance drops the outer\n * clipmap rings - which are two thirds of the vertices - out of the march\n * completely, and the rings in between run four steps instead of fourteen.\n *\n * Faded rather than cut, or the cut-off would draw a visible circle on the ground.\n */\nfloat terrainShadowAt(vec3 p, vec3 n, float lodCell, float dist){\n float fade = 1.0 - saturate(dist / max(u_shadowDistance, 1.0));\n if (fade <= 0.002) return 1.0;\n int steps = int(mix(4.0, float(u_shadowSteps), fade));\n return mix(1.0, terrainSunShadow(p, n, lodCell, steps), fade);\n}\n\n/**\n * Albedo.\n *\n * Rock is splatted by SLOPE, everything else by height. That ordering is the\n * whole trick: it means a cliff is bare rock whether it is in a valley or on a\n * summit, which is how real ground works and why height-only splatting always\n * reads as a painted contour map.\n */\n/**\n * Splat weights for the four layers, in layer order: low, mid, rock, peak.\n * Exactly the old colour mixes written as weights \u2014 low/mid by height, the peak\n * held back on steep ground (snow does not sit on a vertical face), rock by slope\n * and applied last, which is why a cliff reads as a cliff at any altitude.\n */\nvec4 terrainLayerWeights(float h01, float slope){\n float m = smoothstep(0.05, 0.55, h01);\n float p = saturate(smoothstep(u_peakLevel, u_peakLevel + u_peakBlend, h01 - slope * u_peakSlopeFalloff * 0.6));\n float r = smoothstep(u_slopeRockStart, u_slopeRockEnd, slope);\n return vec4((1.0 - m) * (1.0 - p) * (1.0 - r), m * (1.0 - p) * (1.0 - r), r, p * (1.0 - r));\n}\n\n/** Large-scale variation. Without it a splat layer is one flat colour across\n * kilometres, and the eye reads flat colour as untextured rather than as ground. */\nfloat terrainMacro(vec3 wp){\n float macro = tNoise(wp.xz * u_macroScale) * 0.5 + 0.5;\n return 1.0 + u_macroVariation * (macro - 0.5) * 2.0;\n}\n\n/** The far-field albedo: each layer's far colour by its weight. */\nvec3 terrainAlbedo(vec3 wp, vec4 w){\n vec3 col = u_tFar[0] * w.x + u_tFar[1] * w.y + u_tFar[2] * w.z + u_tFar[3] * w.w;\n return col * terrainMacro(wp);\n}\n\n/** High-frequency bump, well below the mesh resolution, faded out with distance\n * so it never becomes a field of crawling pixels on the horizon. One evaluation:\n * the noise carries its own derivative, so the three taps a finite difference\n * would need are not. */\nvec3 terrainDetailNormal(vec3 n, vec3 wp, float dist){\n float fade = 1.0 - saturate(dist * u_detailFade);\n float amt = u_detailStrength * fade * fade;\n if (amt <= 0.001) return n;\n vec3 d = tNoised(wp.xz * u_detailScale);\n return normalize(n + vec3(-d.y, 0.0, -d.z) * amt * 0.9);\n}\n\n/**\n * Direct sun, sky fill and ground bounce.\n *\n * Sun irradiance is in the units skyRadiance returns, calibrated against it rather\n * than guessed: a 0.3-albedo surface in full sun lands a little below the sky at\n * the horizon, which is the relationship the eye reads as daylight. Set it to the\n * clouds' SUN_RADIANCE and every ground surface clips to white.\n */\nconst float TERRAIN_SUN = 2.2;\n\nvec3 terrainLight(vec3 albedo, vec3 n, vec3 viewDir, float shadow, float sceneShadow, float roughness, float ao){\n float ndl = max(dot(n, u_sunDir), 0.0);\n // Wrap the terminator slightly: bare ground is not a Lambertian sphere, and a\n // hard terminator across a whole hillside reads as CG.\n float wrap = max((dot(n, u_sunDir) + 0.2) / 1.2, 0.0);\n float diff = mix(ndl, wrap, 0.4);\n\n vec3 sun = u_sunColor * u_sunIntensity * TERRAIN_SUN * diff * shadow * sceneShadow;\n\n // Sky fill from the same atmosphere the sky is drawn with, so it darkens and\n // warms with the sun instead of freezing at a constant blue - but supplied as a\n // uniform rather than evaluated here, since it is the same for every pixel.\n // Whitened a little: a single-scattering zenith colour is very saturated, and\n // at full saturation every shadowed slope - snow above all - comes out cyan.\n vec3 fill = mix(u_skyAmbient, vec3(dot(u_skyAmbient, vec3(0.3333))), 0.4);\n vec3 ambient = fill * (0.5 + 0.5 * n.y) * u_ambient * 1.05;\n\n // Ground bounce, tinted by whatever the ground is. Keeps overhangs and shaded\n // faces off pure black without flattening them the way raising ambient does.\n vec3 bounce = u_tFar[0] * u_groundAmbient * (0.5 - 0.5 * n.y) * u_bounce * 1.3;\n\n // Occlusion is a property of the small-scale surface, so it darkens the light\n // that arrives from everywhere \u2014 sky fill and bounce \u2014 and leaves the sun alone.\n vec3 col = albedo * (sun + (ambient + bounce) * ao);\n\n // A single specular lobe. Rock barely needs it; wet sand and snow at low sun\n // are almost entirely this.\n if (u_specular > 0.0){\n vec3 hv = normalize(u_sunDir - viewDir);\n float a = max(roughness * roughness, 0.002);\n float nh = max(dot(n, hv), 0.0);\n float dd = nh * nh * (a - 1.0) + 1.0;\n float d = a / max(3.14159265 * dd * dd, 1e-4);\n col += u_sunColor * u_sunIntensity * TERRAIN_SUN * d * u_specular * shadow * sceneShadow * ndl;\n }\n\n return col;\n}\n\n/**\n * Aerial perspective.\n *\n * The single most important line in this shader for the horizon. Ground fades\n * into the exact sky colour it is seen through, at a rate that thins with\n * altitude because the atmosphere does \u2014 so a distant ridge sits behind the haze\n * and a peak above it stands clear of it, which is what tells you the scene is\n * kilometres across rather than metres.\n *\n * The one remaining per-pixel skyRadiance call, and it has to be: the colour is\n * the sky in the view direction, which is different for every fragment.\n */\nvec3 terrainAerial(vec3 col, vec3 rd, float dist, float height){\n float scaleHeight = exp(-max(height - u_seaLevel, 0.0) / 1400.0);\n float f = 1.0 - exp(-dist * u_fogDensity * scaleHeight);\n if (f < 0.002) return col;\n return mix(col, skyRadiance(rd), saturate(f));\n}\n";
79
+ export declare const terrainShadow = "\n/** 1 lit, 0 shadowed, soft in between. */\nfloat terrainSunShadow(vec3 p, vec3 n, float lodCell, int steps){\n if (steps < 1 || u_sunShadow <= 0.0) return 1.0;\n // Below the horizon there is nothing to shadow; the march would also walk\n // downhill forever.\n if (u_sunDir.y <= 0.015) return 1.0;\n // A surface facing away from the sun is shadowed by definition, and marching\n // from it only finds the ground it is already part of.\n if (dot(n, u_sunDir) <= 0.0) return 0.0;\n\n // Lift off the surface before starting, or every sample on a steep face - a\n // terrace riser above all - reads its own ground as an occluder and the whole\n // wall goes black in a grid of triangles. Start ON the offset surface and\n // compare raw heights; subtracting the bias from the height difference instead\n // makes the first sample fail on any slope tilted a few degrees from the sun.\n float bias = max(lodCell, u_elevation * 0.003);\n vec3 origin = p + n * bias;\n\n float shadowLod = (1.0 / max(u_baseScale, 1e-9)) * 0.008;\n\n float res = 1.0;\n float t = bias * 3.0;\n float maxT = (u_elevation * 2.5) / max(u_sunDir.y, 0.05);\n\n for (int i = 0; i < 48; i++){\n if (i >= steps || t > maxT) break;\n vec3 sp = origin + u_sunDir * t;\n // The occluder is sampled at the SHADING lod, coarsening with distance -\n // never at a fixed coarse lod. Testing a fine surface point against a coarse\n // height field puts the whole valley floor in shadow, because the coarse\n // field sits above the fine one wherever the fine one has been cut down.\n // Sampled MUCH coarser than the surface being shaded. A shadow does not need\n // nine octaves: what casts a visible shadow is the landform, and marching the\n // fine field costs the same nine octaves per step for detail that is below a\n // pixel by the time it lands. Floored against the landform's own size, so the\n // march runs about five octaves instead of nine wherever it starts.\n float h = terrainOccluderAt(sp.xz, max(max(lodCell * 2.0, t * 0.06), shadowLod));\n float diff = sp.y - h;\n res = min(res, u_shadowSoftness * diff / t);\n if (res <= 0.0) return 0.0;\n // Geometric growth: near the surface the occluder is metres away, at the end\n // of the march it is kilometres, and a linear step wastes every sample at one\n // end or the other. 1.7 rather than 1.42 so twelve steps actually reach maxT -\n // at 1.42 the march ran out around 700 m and long shadows at low sun simply\n // did not exist, which is a quality bug that happened to look like a cheap one.\n t = t * 1.7 + bias;\n }\n return saturate(res);\n}\n\n/**\n * The shadow, faded out with distance and given a step budget that falls with it.\n *\n * The march is the most expensive thing left in the renderer, and past a few\n * kilometres it is buying nothing: aerial perspective has already washed most of\n * the contrast out of the ground, and a ridge shadow out there is a couple of\n * pixels wide. Skipping it entirely beyond u_shadowDistance drops the outer\n * clipmap rings - which are two thirds of the vertices - out of the march\n * completely, and the rings in between run four steps instead of fourteen.\n *\n * Faded rather than cut, or the cut-off would draw a visible circle on the ground.\n */\nfloat terrainShadowAt(vec3 p, vec3 n, float lodCell, float dist){\n float fade = 1.0 - saturate(dist / max(u_shadowDistance, 1.0));\n if (fade <= 0.002) return 1.0;\n int steps = int(mix(4.0, float(u_shadowSteps), fade));\n return mix(1.0, terrainSunShadow(p, n, lodCell, steps), fade);\n}\n\n";
80
+ export declare const terrainShading = "\n/**\n * Albedo.\n *\n * Rock is splatted by SLOPE, everything else by height. That ordering is the\n * whole trick: it means a cliff is bare rock whether it is in a valley or on a\n * summit, which is how real ground works and why height-only splatting always\n * reads as a painted contour map.\n */\n/**\n * Splat weights for the four layers, in layer order: low, mid, rock, peak.\n * Exactly the old colour mixes written as weights \u2014 low/mid by height, the peak\n * held back on steep ground (snow does not sit on a vertical face), rock by slope\n * and applied last, which is why a cliff reads as a cliff at any altitude.\n */\nvec4 terrainLayerWeights(float h01, float slope){\n float m = smoothstep(0.05, 0.55, h01);\n float p = saturate(smoothstep(u_peakLevel, u_peakLevel + u_peakBlend, h01 - slope * u_peakSlopeFalloff * 0.6));\n float r = smoothstep(u_slopeRockStart, u_slopeRockEnd, slope);\n return vec4((1.0 - m) * (1.0 - p) * (1.0 - r), m * (1.0 - p) * (1.0 - r), r, p * (1.0 - r));\n}\n\n/** Large-scale variation. Without it a splat layer is one flat colour across\n * kilometres, and the eye reads flat colour as untextured rather than as ground. */\nfloat terrainMacro(vec3 wp){\n float macro = tNoise(wp.xz * u_macroScale) * 0.5 + 0.5;\n return 1.0 + u_macroVariation * (macro - 0.5) * 2.0;\n}\n\n/** The far-field albedo: each layer's far colour by its weight. */\nvec3 terrainAlbedo(vec3 wp, vec4 w){\n vec3 col = u_tFar[0] * w.x + u_tFar[1] * w.y + u_tFar[2] * w.z + u_tFar[3] * w.w;\n return col * terrainMacro(wp);\n}\n\n/** High-frequency bump, well below the mesh resolution, faded out with distance\n * so it never becomes a field of crawling pixels on the horizon. One evaluation:\n * the noise carries its own derivative, so the three taps a finite difference\n * would need are not. */\nvec3 terrainDetailNormal(vec3 n, vec3 wp, float dist){\n float fade = 1.0 - saturate(dist * u_detailFade);\n float amt = u_detailStrength * fade * fade;\n if (amt <= 0.001) return n;\n vec3 d = tNoised(wp.xz * u_detailScale);\n return normalize(n + vec3(-d.y, 0.0, -d.z) * amt * 0.9);\n}\n\n/**\n * Direct sun, sky fill and ground bounce.\n *\n * Sun irradiance is in the units skyRadiance returns, calibrated against it rather\n * than guessed: a 0.3-albedo surface in full sun lands a little below the sky at\n * the horizon, which is the relationship the eye reads as daylight. Set it to the\n * clouds' SUN_RADIANCE and every ground surface clips to white.\n */\nconst float TERRAIN_SUN = 2.2;\n\nvec3 terrainLight(vec3 albedo, vec3 n, vec3 viewDir, float shadow, float sceneShadow, float roughness, float ao){\n float ndl = max(dot(n, u_sunDir), 0.0);\n // Wrap the terminator slightly: bare ground is not a Lambertian sphere, and a\n // hard terminator across a whole hillside reads as CG.\n float wrap = max((dot(n, u_sunDir) + 0.2) / 1.2, 0.0);\n float diff = mix(ndl, wrap, 0.4);\n\n vec3 sun = u_sunColor * u_sunIntensity * TERRAIN_SUN * diff * shadow * sceneShadow;\n\n // Sky fill from the same atmosphere the sky is drawn with, so it darkens and\n // warms with the sun instead of freezing at a constant blue - but supplied as a\n // uniform rather than evaluated here, since it is the same for every pixel.\n // Whitened a little: a single-scattering zenith colour is very saturated, and\n // at full saturation every shadowed slope - snow above all - comes out cyan.\n vec3 fill = mix(u_skyAmbient, vec3(dot(u_skyAmbient, vec3(0.3333))), 0.4);\n vec3 ambient = fill * (0.5 + 0.5 * n.y) * u_ambient * 1.05;\n\n // Ground bounce, tinted by whatever the ground is. Keeps overhangs and shaded\n // faces off pure black without flattening them the way raising ambient does.\n vec3 bounce = u_tFar[0] * u_groundAmbient * (0.5 - 0.5 * n.y) * u_bounce * 1.3;\n\n // Occlusion is a property of the small-scale surface, so it darkens the light\n // that arrives from everywhere \u2014 sky fill and bounce \u2014 and leaves the sun alone.\n vec3 col = albedo * (sun + (ambient + bounce) * ao);\n\n // A single specular lobe. Rock barely needs it; wet sand and snow at low sun\n // are almost entirely this.\n if (u_specular > 0.0){\n vec3 hv = normalize(u_sunDir - viewDir);\n float a = max(roughness * roughness, 0.002);\n float nh = max(dot(n, hv), 0.0);\n float dd = nh * nh * (a - 1.0) + 1.0;\n float d = a / max(3.14159265 * dd * dd, 1e-4);\n col += u_sunColor * u_sunIntensity * TERRAIN_SUN * d * u_specular * shadow * sceneShadow * ndl;\n }\n\n return col;\n}\n\n/**\n * Aerial perspective.\n *\n * The single most important line in this shader for the horizon. Ground fades\n * into the exact sky colour it is seen through, at a rate that thins with\n * altitude because the atmosphere does \u2014 so a distant ridge sits behind the haze\n * and a peak above it stands clear of it, which is what tells you the scene is\n * kilometres across rather than metres.\n *\n * The one remaining per-pixel skyRadiance call, and it has to be: the colour is\n * the sky in the view direction, which is different for every fragment.\n */\nvec3 terrainAerial(vec3 col, vec3 rd, float dist, float height){\n float scaleHeight = exp(-max(height - u_seaLevel, 0.0) / 1400.0);\n float f = 1.0 - exp(-dist * u_fogDensity * scaleHeight);\n if (f < 0.002) return col;\n return mix(col, skyRadiance(rd), saturate(f));\n}\n";
74
81
  /**
75
82
  * The textured near field. Needs `surfaceSampling` (surfaceCore.glsl) before it.
76
83
  *
@@ -86,6 +93,14 @@ export declare const terrainShading = "\n/** 1 lit, 0 shadowed, soft in between.
86
93
  export declare const terrainTextured = "\n#ifdef TERRAIN_TEXTURED\nstruct TerrainGrads { vec2 topX; vec2 topY; vec2 sideXX; vec2 sideXY; vec2 sideZX; vec2 sideZY; };\n\n// The large-scale sample is the same texture, T_MACRO times bigger.\n#define T_MACRO 8.0\n\n/**\n * One layer's three arrays at one projection, at two scales.\n *\n * A ground scan is millimetres per texel: a gravel tile shows its pebbles for a\n * few metres and is a flat average colour from fifty. So past the point where a\n * pixel covers a fifth of a tile, the same texture is sampled again T_MACRO times\n * larger, and its pebbles become patches, its cracks become streaks \u2014 the set's\n * character survives out to hundreds of metres instead of dissolving into a flat\n * colour, and picking a different set is visible from where people actually look\n * at terrain. The switch is by pixel footprint, so it is the same at any tile\n * size and any camera height. Each scale is skipped when its weight is zero.\n */\nvoid terrainTap(int L, vec2 uv, vec2 dx, vec2 dy, out vec4 c, out vec4 nh, out vec4 o){\n float layer = float(L);\n float fp = max(length(dx), length(dy));\n float m = smoothstep(0.04, 0.25, fp);\n vec4 cd = vec4(0.0);\n vec4 nd = vec4(0.0);\n vec4 od = vec4(0.0);\n vec4 cm = vec4(0.0);\n vec4 nm = vec4(0.0);\n vec4 om = vec4(0.0);\n if (m < 0.999){\n cd = sfSample(u_tColor, uv, dx, dy, layer, u_tMeanC[L]);\n nd = sfSample(u_tNormal, uv, dx, dy, layer, u_tMeanN[L]);\n od = sfSample(u_tOrm, uv, dx, dy, layer, u_tMeanO[L]);\n }\n if (m > 0.001){\n vec2 mu = uv / T_MACRO;\n vec2 mdx = dx / T_MACRO;\n vec2 mdy = dy / T_MACRO;\n cm = sfSample(u_tColor, mu, mdx, mdy, layer, u_tMeanC[L]);\n nm = sfSample(u_tNormal, mu, mdx, mdy, layer, u_tMeanN[L]);\n om = sfSample(u_tOrm, mu, mdx, mdy, layer, u_tMeanO[L]);\n // No height blending at the large scale. A height map blown up 8x has\n // tens-of-metres blobs, and blending layers along those contours paints\n // the ground in camouflage. Mid height = an ordinary cross-fade.\n nm.a = 0.5;\n }\n c = mix(cd, cm, m);\n nh = mix(nd, nm, m);\n o = mix(od, om, m);\n}\n\n/** One layer: albedo (tinted), an unnormalised world normal, surface height,\n * roughness and occlusion. An untextured layer returns its flat colour. */\nvoid terrainLayerSample(int L, vec3 wp, vec3 n, TerrainGrads g,\n out vec3 col, out vec3 wn, out float h, out float rough, out float ao){\n col = u_tFar[L];\n wn = n;\n h = 0.5;\n rough = u_roughness;\n ao = 1.0;\n if (u_tLayer[L].z > 0.5){\n float inv = u_tLayer[L].x;\n float ns = u_tLayer[L].y;\n vec4 c = vec4(0.0);\n vec4 nh = vec4(0.0);\n vec4 o = vec4(0.0);\n vec3 acc = vec3(0.0);\n if (L == 2){\n // Biplanar: the two strongest axes. The third adds a fraction of a percent\n // anywhere that is not an exact corner and costs a third of the fetches.\n vec3 an = abs(n);\n an = an * an * an;\n an /= max(an.x + an.y + an.z, 1e-4);\n float lo = min(an.x, min(an.y, an.z));\n vec3 bw = max(an - lo, 0.0);\n bw /= max(bw.x + bw.y + bw.z, 1e-4);\n vec4 c1, n1, o1;\n if (bw.y > 0.002){\n terrainTap(L, wp.xz * inv, g.topX * inv, g.topY * inv, c1, n1, o1);\n c += c1 * bw.y; nh += n1 * bw.y; o += o1 * bw.y;\n acc += sfWhiteoutTop(n, sfUnpackNormal(n1.rgb, ns)) * bw.y;\n }\n if (bw.x > 0.002){\n terrainTap(L, wp.zy * inv, g.sideXX * inv, g.sideXY * inv, c1, n1, o1);\n c += c1 * bw.x; nh += n1 * bw.x; o += o1 * bw.x;\n acc += sfWhiteoutX(n, sfUnpackNormal(n1.rgb, ns)) * bw.x;\n }\n if (bw.z > 0.002){\n terrainTap(L, wp.xy * inv, g.sideZX * inv, g.sideZY * inv, c1, n1, o1);\n c += c1 * bw.z; nh += n1 * bw.z; o += o1 * bw.z;\n acc += sfWhiteoutZ(n, sfUnpackNormal(n1.rgb, ns)) * bw.z;\n }\n } else {\n terrainTap(L, wp.xz * inv, g.topX * inv, g.topY * inv, c, nh, o);\n acc = sfWhiteoutTop(n, sfUnpackNormal(nh.rgb, ns));\n }\n col = c.rgb * u_tTint[L];\n wn = acc;\n h = nh.a;\n rough = o.g;\n ao = o.r;\n }\n}\n\n/** Near-field surface for weights w. Outputs albedo (no macro), normal,\n * roughness and occlusion. */\nvoid terrainSurfaceNear(vec3 wp, vec3 n, vec4 w, TerrainGrads g,\n out vec3 albedo, out vec3 nrm, out float rough, out float ao){\n float ws[4];\n ws[0] = w.x; ws[1] = w.y; ws[2] = w.z; ws[3] = w.w;\n int i0 = 0;\n float b0 = -1.0;\n for (int i = 0; i < 4; i++){ if (ws[i] > b0){ b0 = ws[i]; i0 = i; } }\n ws[i0] = -1.0;\n int i1 = 0;\n float b1 = -1.0;\n for (int i = 0; i < 4; i++){ if (ws[i] > b1){ b1 = ws[i]; i1 = i; } }\n b0 = max(b0, 0.0);\n b1 = max(b1, 0.0);\n float bs = max(b0 + b1, 1e-4);\n b0 /= bs;\n b1 /= bs;\n\n vec3 c0, n0, c1, n1;\n float h0, r0, a0, h1, r1, a1;\n terrainLayerSample(i0, wp, n, g, c0, n0, h0, r0, a0);\n if (b1 > 0.001){\n terrainLayerSample(i1, wp, n, g, c1, n1, h1, r1, a1);\n } else {\n c1 = c0; n1 = n0; h1 = h0; r1 = r0; a1 = a0;\n }\n\n // Height blend: the layer whose own surface is higher at this texel wins, so\n // gravel comes through grass along the gravel's grain and rock emerges from\n // soil along its cracks, instead of the two cross-fading into mud.\n float hb = u_tHeightBlend;\n float range = mix(1.0, 0.04, hb);\n float v0 = b0 + h0 * hb;\n float v1 = b1 + h1 * hb;\n float peak = max(v0, v1);\n float w0 = max(v0 - (peak - range), 0.0);\n float w1 = max(v1 - (peak - range), 0.0);\n if (b1 <= 0.001) w1 = 0.0;\n float ws2 = max(w0 + w1, 1e-4);\n w0 /= ws2;\n w1 /= ws2;\n\n albedo = c0 * w0 + c1 * w1;\n nrm = normalize(n0 * w0 + n1 * w1);\n rough = r0 * w0 + r1 * w1;\n ao = a0 * w0 + a1 * w1;\n}\n#endif\n";
87
94
  /** Everything a surface material needs, in include order. Requires three's
88
95
  * `<common>` and `skyLib` from cloudCore before it. */
96
+ /** Every uniform the terrain material reads. */
97
+ export declare const terrainUniforms: string;
89
98
  export declare const terrainField: string;
99
+ /**
100
+ * The height field and its sun-shadow march, with only the uniforms they read.
101
+ * What another material includes to stand water (or anything else) on the
102
+ * terrain: the ocean uses it for shoreline depth and headland shadows.
103
+ */
104
+ export declare const terrainFieldOnly: string;
90
105
  export declare const terrainFull: string;
91
106
  //# sourceMappingURL=terrainCore.glsl.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"terrainCore.glsl.d.ts","sourceRoot":"","sources":["../../../src/shaders/glsl/terrainCore.glsl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,2DAA2D;AAC3D,eAAO,MAAM,eAAe,wpGA2E3B,CAAA;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,YAAY,uiCAiCxB,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,aAAa,mqbAiVzB,CAAA;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,cAAc,s0RAuL1B,CAAA;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,eAAe,4pLAyJ3B,CAAA;AAED;wDACwD;AACxD,eAAO,MAAM,YAAY,QAA4D,CAAA;AAErF,eAAO,MAAM,WAAW,QAA4C,CAAA"}
1
+ {"version":3,"file":"terrainCore.glsl.d.ts","sourceRoot":"","sources":["../../../src/shaders/glsl/terrainCore.glsl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,ooDA0C/B,CAAA;AAED,uEAAuE;AACvE,eAAO,MAAM,sBAAsB,8rDAsClC,CAAA;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,YAAY,uiCAiCxB,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,aAAa,mqbAiVzB,CAAA;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,aAAa,gjHAsEzB,CAAA;AAED,eAAO,MAAM,cAAc,6xKAkH1B,CAAA;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,eAAe,4pLAyJ3B,CAAA;AAED;wDACwD;AACxD,gDAAgD;AAChD,eAAO,MAAM,eAAe,QAA2D,CAAA;AAEvF,eAAO,MAAM,YAAY,QAA4D,CAAA;AAErF;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,QAA+E,CAAA;AAE5G,eAAO,MAAM,WAAW,QAA2D,CAAA"}