@jgengine/shell 0.18.1 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +504 -7
- package/dist/GameHost.d.ts +12 -1
- package/dist/GameHost.js +14 -1
- package/dist/GamePlayerShell.js +132 -17
- package/dist/Shell3dPresentation.d.ts +5 -11
- package/dist/Shell3dPresentation.js +73 -11
- package/dist/ShellChrome.d.ts +2 -1
- package/dist/ShellChrome.js +22 -6
- package/dist/ShellHudPresentation.d.ts +20 -2
- package/dist/ShellHudPresentation.js +41 -6
- package/dist/audio/AudioComponents.js +6 -2
- package/dist/audio/audioEngine.d.ts +24 -0
- package/dist/audio/audioEngine.js +231 -83
- package/dist/audio/audioWire.d.ts +3 -0
- package/dist/audio/audioWire.js +9 -3
- package/dist/audio/loopParams.d.ts +39 -0
- package/dist/audio/loopParams.js +43 -0
- package/dist/audio/synthEngine.d.ts +7 -1
- package/dist/audio/synthEngine.js +103 -38
- package/dist/camera/GameCameraRig.js +3 -2
- package/dist/camera/GameFirstPersonCamera.d.ts +6 -4
- package/dist/camera/GameFirstPersonCamera.js +68 -27
- package/dist/camera/Viewports.d.ts +34 -0
- package/dist/camera/Viewports.js +123 -0
- package/dist/camera/cameraRigs.js +76 -62
- package/dist/camera/chaseHeading.d.ts +9 -0
- package/dist/camera/chaseHeading.js +10 -0
- package/dist/camera/customMuzzle.d.ts +14 -0
- package/dist/camera/customMuzzle.js +68 -0
- package/dist/camera/index.d.ts +1 -0
- package/dist/camera/index.js +1 -0
- package/dist/camera/orbitCameraMath.js +2 -2
- package/dist/camera/rigMath.d.ts +82 -4
- package/dist/camera/rigMath.js +133 -15
- package/dist/camera/rigResolve.d.ts +3 -0
- package/dist/camera/rigResolve.js +11 -0
- package/dist/combat/ProjectileModels.d.ts +31 -0
- package/dist/combat/ProjectileModels.js +70 -0
- package/dist/commandSink.d.ts +1 -2
- package/dist/commandSink.js +3 -2
- package/dist/defineGame.d.ts +19 -3
- package/dist/defineGame.js +39 -7
- package/dist/devtools/DevtoolsOverlay.d.ts +2 -69
- package/dist/devtools/DevtoolsOverlay.js +15 -167
- package/dist/devtools/DevtoolsRuntime.d.ts +7 -0
- package/dist/devtools/DevtoolsRuntime.js +61 -0
- package/dist/devtools/agentBridge.d.ts +6 -0
- package/dist/devtools/agentBridge.js +16 -2
- package/dist/devtools/devtoolsReports.d.ts +68 -0
- package/dist/devtools/devtoolsReports.js +100 -0
- package/dist/devtools/latencyInstrumentation.d.ts +3 -0
- package/dist/devtools/latencyInstrumentation.js +11 -0
- package/dist/devtools/playtestProbe.d.ts +4 -0
- package/dist/devtools/playtestProbe.js +22 -0
- package/dist/diagnostics/FpsCounter.d.ts +17 -0
- package/dist/diagnostics/FpsCounter.js +32 -0
- package/dist/drivers/FrameDriver.js +35 -20
- package/dist/drivers/FrameRateLimiter.d.ts +9 -0
- package/dist/drivers/FrameRateLimiter.js +39 -0
- package/dist/drivers/HudOnlyDriver.d.ts +6 -1
- package/dist/drivers/HudOnlyDriver.js +32 -14
- package/dist/environment/DayNightSky.js +5 -5
- package/dist/environment/Daylight.d.ts +35 -1
- package/dist/environment/Daylight.js +98 -28
- package/dist/environment/EnvironmentScene.js +11 -10
- package/dist/environment/VolumetricClouds.js +20 -2
- package/dist/environment/daylightCycle.d.ts +20 -0
- package/dist/environment/daylightCycle.js +59 -7
- package/dist/environment/sunShadowMath.d.ts +45 -0
- package/dist/environment/sunShadowMath.js +50 -0
- package/dist/input/gamepadPoll.d.ts +47 -0
- package/dist/input/gamepadPoll.js +142 -0
- package/dist/input/gamepadSource.d.ts +11 -1
- package/dist/input/gamepadSource.js +117 -71
- package/dist/input/mouseLook.js +2 -1
- package/dist/input/padHaptics.d.ts +17 -0
- package/dist/input/padHaptics.js +32 -0
- package/dist/input/pointerLock.d.ts +11 -0
- package/dist/input/pointerLock.js +19 -0
- package/dist/inputSink.d.ts +6 -6
- package/dist/inputSink.js +60 -21
- package/dist/lazyShell3dPresentation.d.ts +9 -0
- package/dist/lazyShell3dPresentation.js +24 -0
- package/dist/materialOverride.d.ts +42 -0
- package/dist/materialOverride.js +95 -36
- package/dist/multiplayer.d.ts +33 -3
- package/dist/multiplayer.js +105 -22
- package/dist/pointer/pointerService.d.ts +2 -0
- package/dist/pointer/pointerService.js +12 -2
- package/dist/postfx/PostProcessing.d.ts +11 -1
- package/dist/postfx/PostProcessing.js +14 -42
- package/dist/postfx/gradeShader.d.ts +1 -1
- package/dist/postfx/gradeShader.js +30 -2
- package/dist/postfx/shadowPrepass.d.ts +3 -0
- package/dist/postfx/shadowPrepass.js +18 -0
- package/dist/postfx/stylizePass.d.ts +23 -0
- package/dist/postfx/stylizePass.js +216 -0
- package/dist/presentationRecovery.d.ts +75 -0
- package/dist/presentationRecovery.js +204 -0
- package/dist/presentationRecovery3d.d.ts +4 -0
- package/dist/presentationRecovery3d.js +32 -0
- package/dist/presentationResolve.d.ts +8 -1
- package/dist/presentationResolve.js +5 -2
- package/dist/render/CascadedShadows.d.ts +7 -2
- package/dist/render/CascadedShadows.js +98 -20
- package/dist/render/EnvironmentLighting.js +74 -35
- package/dist/render/HairCards.d.ts +7 -0
- package/dist/render/HairCards.js +23 -0
- package/dist/render/SceneModels.js +52 -78
- package/dist/render/SkinnedInstances.d.ts +91 -0
- package/dist/render/SkinnedInstances.js +249 -0
- package/dist/render/TextureFiltering.d.ts +16 -0
- package/dist/render/TextureFiltering.js +64 -0
- package/dist/render/authoredSurfaceMaterial.d.ts +43 -0
- package/dist/render/authoredSurfaceMaterial.js +108 -0
- package/dist/render/displayColor.d.ts +33 -0
- package/dist/render/displayColor.js +138 -0
- package/dist/render/materialAppearanceSignals.d.ts +23 -0
- package/dist/render/materialAppearanceSignals.js +58 -0
- package/dist/render/materialAsset.d.ts +98 -0
- package/dist/render/materialAsset.js +454 -0
- package/dist/render/measureBounds.js +16 -3
- package/dist/render/measureCollisionMesh.js +8 -1
- package/dist/render/modelAssets.d.ts +8 -0
- package/dist/render/modelAssets.js +43 -0
- package/dist/render/modelBindPose.d.ts +5 -0
- package/dist/render/modelBindPose.js +35 -0
- package/dist/render/modelLoad.d.ts +32 -0
- package/dist/render/modelLoad.js +70 -1
- package/dist/render/modelNodeVisibility.d.ts +6 -0
- package/dist/render/modelNodeVisibility.js +18 -0
- package/dist/render/modelRender.d.ts +18 -1
- package/dist/render/modelRender.js +81 -3
- package/dist/render/projectedOverdraw.d.ts +35 -0
- package/dist/render/projectedOverdraw.js +204 -0
- package/dist/render/resolveModel.d.ts +12 -2
- package/dist/render/resolveModel.js +25 -4
- package/dist/render/rigNode.d.ts +26 -0
- package/dist/render/rigNode.js +32 -0
- package/dist/render/spriteRender.d.ts +4 -0
- package/dist/render/spriteRender.js +5 -0
- package/dist/render/staticShapeInstances.d.ts +34 -0
- package/dist/render/staticShapeInstances.js +78 -0
- package/dist/render/useFootIk.d.ts +53 -4
- package/dist/render/useFootIk.js +262 -70
- package/dist/render/useModelAnimation.d.ts +50 -0
- package/dist/render/useModelAnimation.js +245 -99
- package/dist/render/useModelInstance.d.ts +25 -0
- package/dist/render/useModelInstance.js +43 -0
- package/dist/scatter/scatterModels.d.ts +3 -2
- package/dist/scatter/scatterModels.js +15 -7
- package/dist/scene/AuthoredScene.d.ts +23 -6
- package/dist/scene/AuthoredScene.js +46 -9
- package/dist/scene/CityRenderer.js +6 -6
- package/dist/scene/GeneratedAssetRenderer.js +11 -42
- package/dist/settings/QuickControls.js +1 -1
- package/dist/settings/SettingsMenu.js +11 -18
- package/dist/settings/SettingsRuntime.d.ts +5 -0
- package/dist/settings/SettingsRuntime.js +29 -2
- package/dist/settings/appliedSettings.d.ts +9 -3
- package/dist/settings/appliedSettings.js +6 -5
- package/dist/settings/settingsController.d.ts +3 -0
- package/dist/settings/settingsController.js +47 -3
- package/dist/shellControlSuspension.d.ts +18 -0
- package/dist/shellControlSuspension.js +28 -0
- package/dist/shellInputPublication.d.ts +28 -0
- package/dist/shellInputPublication.js +49 -0
- package/dist/shellPointerInput.d.ts +18 -0
- package/dist/shellPointerInput.js +49 -0
- package/dist/structures/BuildingKitBatch.d.ts +3 -1
- package/dist/structures/BuildingKitBatch.js +35 -56
- package/dist/structures/BuildingRenderBudget.d.ts +23 -0
- package/dist/structures/BuildingRenderBudget.js +54 -0
- package/dist/structures/GeneratedBuilding.d.ts +3 -15
- package/dist/structures/GeneratedBuilding.js +33 -22
- package/dist/structures/buildingKitMaterials.d.ts +20 -0
- package/dist/structures/buildingKitMaterials.js +70 -0
- package/dist/structures/buildingSpatialBatch.d.ts +11 -0
- package/dist/structures/buildingSpatialBatch.js +45 -0
- package/dist/structures/buildingSurface.d.ts +21 -0
- package/dist/structures/buildingSurface.js +134 -0
- package/dist/terrain/GrassField.d.ts +2 -1
- package/dist/terrain/GrassField.js +59 -5
- package/dist/terrain/grassGeometry.d.ts +10 -0
- package/dist/terrain/grassGeometry.js +55 -0
- package/dist/terrain/grassMaterial.js +2 -2
- package/dist/terrain/terrainDetailMaterial.d.ts +3 -3
- package/dist/terrain/terrainDetailMaterial.js +7 -7
- package/dist/touch/TouchControlsOverlay.js +4 -4
- package/dist/useShellMultiplayerSync.d.ts +1 -1
- package/dist/useShellMultiplayerSync.js +9 -3
- package/dist/vfx/ParticleField.d.ts +23 -20
- package/dist/vfx/ParticleField.js +168 -50
- package/dist/visibility/CullingProvider.js +2 -62
- package/dist/visibility/cullingDriver.d.ts +9 -0
- package/dist/visibility/cullingDriver.js +68 -0
- package/dist/weather/AuthoredWeatherLayer.d.ts +16 -0
- package/dist/weather/AuthoredWeatherLayer.js +31 -0
- package/dist/weather/DustField.d.ts +2 -1
- package/dist/weather/DustField.js +17 -3
- package/dist/weather/FireSpreadLayer.d.ts +4 -2
- package/dist/weather/FireSpreadLayer.js +5 -11
- package/dist/weather/LightningStrike.d.ts +4 -1
- package/dist/weather/LightningStrike.js +13 -17
- package/dist/weather/RainField.d.ts +2 -1
- package/dist/weather/RainField.js +17 -3
- package/dist/weather/RainImpactField.d.ts +16 -0
- package/dist/weather/RainImpactField.js +89 -0
- package/dist/weather/SnowField.d.ts +2 -1
- package/dist/weather/SnowField.js +17 -3
- package/dist/weather/WeatherLayer.d.ts +11 -2
- package/dist/weather/WeatherLayer.js +30 -6
- package/dist/weather/index.d.ts +2 -0
- package/dist/weather/index.js +2 -0
- package/dist/weather/weatherMath.d.ts +2 -0
- package/dist/weather/weatherMath.js +8 -0
- package/dist/weather/weatherShelter.d.ts +18 -0
- package/dist/weather/weatherShelter.js +75 -0
- package/dist/weather/weatherUniforms.d.ts +40 -1
- package/dist/weather/weatherUniforms.js +29 -9
- package/dist/world/AuthoredFlocks.d.ts +22 -0
- package/dist/world/AuthoredFlocks.js +87 -0
- package/dist/world/StaticObjectModels.d.ts +8 -0
- package/dist/world/StaticObjectModels.js +75 -0
- package/dist/world/WorldHud.d.ts +14 -6
- package/dist/world/WorldHud.js +25 -12
- package/dist/world/WorldParticles.d.ts +10 -23
- package/dist/world/WorldParticles.js +210 -62
- package/dist/world/WorldScene.d.ts +3 -1
- package/dist/world/WorldScene.js +41 -26
- package/dist/world/staticObjectBatches.d.ts +35 -0
- package/dist/world/staticObjectBatches.js +163 -0
- package/dist/world/worldBarSamples.d.ts +37 -33
- package/dist/world/worldBarSamples.js +117 -75
- package/package.json +4 -4
- package/skills/ce-handoff/SKILL.md +2 -2
- package/skills/game-design/SKILL.md +2 -2
- package/skills/jgengine/SKILL.md +28 -6
- package/skills/jgengine/api.md +139 -99
- package/skills/jgengine/capabilities.md +28 -0
- package/skills/jgengine/recipes/minimal-game.md +10 -2
- package/skills/jgengine-ui/SKILL.md +16 -17
- package/skills/jgengine-ui/api.md +603 -365
- package/skills/jgengine-ui/capabilities.md +131 -1
- package/skills/jgengine-ui/reference-primitives.md +179 -0
- package/skills/jgengine-ui/reference.md +4 -81
- package/skills/jgengine-world/SKILL.md +25 -4
- package/skills/jgengine-world/api.md +1045 -655
- package/skills/jgengine-world/capabilities.md +246 -9
- package/skills/jgengine-world/recipes/character-animation.md +62 -0
- package/skills/jgengine-world/recipes/controls.md +48 -0
- package/skills/jgengine-world/recipes/flight-feel.md +76 -0
- package/skills/jgengine-world/recipes/npc-senses.md +55 -0
- package/skills/jgengine-world/recipes/vehicle-feel.md +87 -0
- package/skills/jgengine-world/reference.md +180 -4
- package/skills/jgengine-world/visibility.md +139 -0
- package/skills/level-design/SKILL.md +1 -1
- package/dist/render/testFixtures/proceduralSpriteAtlas.d.ts +0 -15
- package/dist/render/testFixtures/proceduralSpriteAtlas.js +0 -30
- package/dist/render/testFixtures/proceduralTileLayer.d.ts +0 -13
- package/dist/render/testFixtures/proceduralTileLayer.js +0 -30
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type MutableRefObject } from "react";
|
|
1
|
+
import { type MutableRefObject, type RefObject } from "react";
|
|
2
2
|
import * as THREE from "three";
|
|
3
3
|
import type { BiomeBand, SkyEnvironmentDescriptor } from "@jgengine/core/world/features";
|
|
4
4
|
import { type VolumetricCloudsConfig } from "@jgengine/core/world/volumetricClouds";
|
|
@@ -23,6 +23,40 @@ export interface SkyDomeProps {
|
|
|
23
23
|
/** Exposes the created shader material so a time-of-day driver can mutate its uniforms per frame without recreating it. */
|
|
24
24
|
materialRef?: MutableRefObject<THREE.ShaderMaterial | null>;
|
|
25
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Shadow-casting sun whose shadow box leads the camera each frame ({@link sunShadowFocus}), so
|
|
28
|
+
* grounded shadows stay crisp and soft-edged under the player anywhere in a large world. Time-of-day
|
|
29
|
+
* drivers aim it through `directionRef` and retint it through `lightRef`.
|
|
30
|
+
* @internal
|
|
31
|
+
*/
|
|
32
|
+
export declare function SunLight({ direction, directionRef, lightRef, intensity, color, }: {
|
|
33
|
+
direction: readonly [number, number, number];
|
|
34
|
+
directionRef?: MutableRefObject<readonly [number, number, number]>;
|
|
35
|
+
lightRef?: RefObject<THREE.DirectionalLight | null>;
|
|
36
|
+
intensity: number;
|
|
37
|
+
color: string;
|
|
38
|
+
}): import("react").JSX.Element;
|
|
39
|
+
/**
|
|
40
|
+
* Sets the zenith and horizon colors a sky dome should show on screen. The dome converts them each
|
|
41
|
+
* frame through the renderer's tone mapping ({@link DisplayColorWriter}), so a picked swatch renders
|
|
42
|
+
* as picked instead of greyed by the output curve.
|
|
43
|
+
* @internal
|
|
44
|
+
*/
|
|
45
|
+
export declare function setSkyDomeColors(material: THREE.ShaderMaterial, top: THREE.ColorRepresentation, bottom: THREE.ColorRepresentation): void;
|
|
46
|
+
/**
|
|
47
|
+
* Scene fog whose color is a display color, converted through the renderer's tone mapping like the
|
|
48
|
+
* dome's horizon so fogged ground meets the sky without a seam. Drivers retint it with
|
|
49
|
+
* {@link setSkyFogColor}.
|
|
50
|
+
* @internal
|
|
51
|
+
*/
|
|
52
|
+
export declare function SkyFog({ color, near, far, fogRef }: {
|
|
53
|
+
color: string;
|
|
54
|
+
near: number;
|
|
55
|
+
far: number;
|
|
56
|
+
fogRef?: RefObject<THREE.Fog | null>;
|
|
57
|
+
}): import("react").JSX.Element;
|
|
58
|
+
/** Sets the display color a {@link SkyFog} converts each frame. @internal */
|
|
59
|
+
export declare function setSkyFogColor(fog: THREE.Fog, color: THREE.ColorRepresentation): void;
|
|
26
60
|
/** @internal */
|
|
27
61
|
export declare function SkyDome({ topColor, horizonColor, radius, offset, exponent, sunDirection, sunColor, sunIntensity, hazeStrength, sunGlowStrength, cloudiness, materialRef, }?: SkyDomeProps): import("react").JSX.Element;
|
|
28
62
|
export interface DaylightProps {
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useFrame } from "@react-three/fiber";
|
|
3
|
-
import { useEffect, useMemo, useRef } from "react";
|
|
2
|
+
import { useFrame, useThree } from "@react-three/fiber";
|
|
3
|
+
import { useEffect, useLayoutEffect, useMemo, useRef } from "react";
|
|
4
4
|
import * as THREE from "three";
|
|
5
5
|
import { createBiomeFogSampler, createBiomeSkySampler } from "@jgengine/core/world/terrain";
|
|
6
6
|
import { resolveVolumetricClouds } from "@jgengine/core/world/volumetricClouds";
|
|
7
|
+
import { warnOnce } from "@jgengine/core/devtools/warnOnce";
|
|
8
|
+
import { DisplayColorWriter } from "../render/displayColor.js";
|
|
7
9
|
import { daylightStateAt, SKY_PRESET_DAY_FRACTION } from "./daylightCycle.js";
|
|
10
|
+
import { SUN_SHADOW, sunLightPosition, sunShadowFocus } from "./sunShadowMath.js";
|
|
8
11
|
import { VolumetricClouds } from "./VolumetricClouds.js";
|
|
9
12
|
function normalizeVec3(v) {
|
|
10
13
|
const out = new THREE.Vector3(v[0], v[1], v[2]);
|
|
@@ -17,29 +20,77 @@ const SUN_COLOR = "#fff1c9";
|
|
|
17
20
|
const HEMI_SKY = "#bfe3ff";
|
|
18
21
|
const HEMI_GROUND = "#4c6b34";
|
|
19
22
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
+
* Shadow-casting sun whose shadow box leads the camera each frame ({@link sunShadowFocus}), so
|
|
24
|
+
* grounded shadows stay crisp and soft-edged under the player anywhere in a large world. Time-of-day
|
|
25
|
+
* drivers aim it through `directionRef` and retint it through `lightRef`.
|
|
26
|
+
* @internal
|
|
23
27
|
*/
|
|
24
|
-
function
|
|
25
|
-
const
|
|
28
|
+
export function SunLight({ direction, directionRef, lightRef, intensity, color, }) {
|
|
29
|
+
const ownRef = useRef(null);
|
|
30
|
+
const ref = lightRef ?? ownRef;
|
|
31
|
+
const scratch = useMemo(() => ({ forward: new THREE.Vector3(), focus: { x: 0, z: 0 }, position: [0, 0, 0] }), []);
|
|
26
32
|
useFrame((state) => {
|
|
27
33
|
const light = ref.current;
|
|
28
34
|
if (light === null)
|
|
29
35
|
return;
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
light.
|
|
36
|
+
state.camera.getWorldDirection(scratch.forward);
|
|
37
|
+
const focus = sunShadowFocus(state.camera.position, scratch.forward, scratch.focus);
|
|
38
|
+
const [x, y, z] = sunLightPosition(focus, directionRef?.current ?? direction, scratch.position);
|
|
39
|
+
light.position.set(x, y, z);
|
|
40
|
+
light.target.position.set(focus.x, 0, focus.z);
|
|
34
41
|
light.target.updateMatrixWorld();
|
|
35
42
|
});
|
|
36
|
-
|
|
43
|
+
const initial = sunLightPosition({ x: 0, z: 0 }, direction);
|
|
44
|
+
return (_jsx("directionalLight", { ref: ref, position: initial, intensity: intensity, color: color, castShadow: true, "shadow-mapSize-width": SUN_SHADOW.mapSize, "shadow-mapSize-height": SUN_SHADOW.mapSize, "shadow-camera-left": -SUN_SHADOW.halfExtent, "shadow-camera-right": SUN_SHADOW.halfExtent, "shadow-camera-top": SUN_SHADOW.halfExtent, "shadow-camera-bottom": -SUN_SHADOW.halfExtent, "shadow-camera-near": SUN_SHADOW.near, "shadow-camera-far": SUN_SHADOW.far, "shadow-bias": SUN_SHADOW.bias, "shadow-normalBias": SUN_SHADOW.normalBias, "shadow-radius": SUN_SHADOW.radius }));
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Sets the zenith and horizon colors a sky dome should show on screen. The dome converts them each
|
|
48
|
+
* frame through the renderer's tone mapping ({@link DisplayColorWriter}), so a picked swatch renders
|
|
49
|
+
* as picked instead of greyed by the output curve.
|
|
50
|
+
* @internal
|
|
51
|
+
*/
|
|
52
|
+
export function setSkyDomeColors(material, top, bottom) {
|
|
53
|
+
const display = material.userData;
|
|
54
|
+
(display.top ??= new THREE.Color()).set(top);
|
|
55
|
+
(display.bottom ??= new THREE.Color()).set(bottom);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Scene fog whose color is a display color, converted through the renderer's tone mapping like the
|
|
59
|
+
* dome's horizon so fogged ground meets the sky without a seam. Drivers retint it with
|
|
60
|
+
* {@link setSkyFogColor}.
|
|
61
|
+
* @internal
|
|
62
|
+
*/
|
|
63
|
+
export function SkyFog({ color, near, far, fogRef }) {
|
|
64
|
+
const ownRef = useRef(null);
|
|
65
|
+
const ref = fogRef ?? ownRef;
|
|
66
|
+
const writer = useMemo(() => new DisplayColorWriter(), []);
|
|
67
|
+
useLayoutEffect(() => {
|
|
68
|
+
if (ref.current !== null)
|
|
69
|
+
setSkyFogColor(ref.current, color);
|
|
70
|
+
}, [ref, color]);
|
|
71
|
+
useFrame((state) => {
|
|
72
|
+
const fog = ref.current;
|
|
73
|
+
const display = fog === null ? undefined : fogDisplayColors.get(fog);
|
|
74
|
+
if (fog === null || display === undefined)
|
|
75
|
+
return;
|
|
76
|
+
writer.write(fog.color, display, state.gl);
|
|
77
|
+
});
|
|
78
|
+
return _jsx("fog", { attach: "fog", ref: ref, args: [color, near, far] });
|
|
37
79
|
}
|
|
80
|
+
/** Sets the display color a {@link SkyFog} converts each frame. @internal */
|
|
81
|
+
export function setSkyFogColor(fog, color) {
|
|
82
|
+
const display = fogDisplayColors.get(fog);
|
|
83
|
+
if (display === undefined)
|
|
84
|
+
fogDisplayColors.set(fog, new THREE.Color(color));
|
|
85
|
+
else
|
|
86
|
+
display.set(color);
|
|
87
|
+
}
|
|
88
|
+
const fogDisplayColors = new WeakMap();
|
|
38
89
|
/** @internal */
|
|
39
90
|
export function SkyDome({ topColor = SKY_TOP, horizonColor = SKY_HORIZON, radius = 260, offset = 24, exponent = 0.65, sunDirection, sunColor = "#fff4d6", sunIntensity = 1, hazeStrength = 0.5, sunGlowStrength = 1, cloudiness = 0.35, materialRef, } = {}) {
|
|
40
91
|
const meshRef = useRef(null);
|
|
41
92
|
const material = useMemo(() => {
|
|
42
|
-
|
|
93
|
+
const dome = new THREE.ShaderMaterial({
|
|
43
94
|
uniforms: {
|
|
44
95
|
topColor: { value: new THREE.Color(topColor) },
|
|
45
96
|
bottomColor: { value: new THREE.Color(horizonColor) },
|
|
@@ -93,12 +144,16 @@ export function SkyDome({ topColor = SKY_TOP, horizonColor = SKY_HORIZON, radius
|
|
|
93
144
|
float glow = pow(sd, 8.0) * 0.35 + pow(sd, 128.0) * 2.6;
|
|
94
145
|
col += uSunColor * glow * uSunIntensity * uSunGlow;
|
|
95
146
|
gl_FragColor = vec4(col, 1.0);
|
|
147
|
+
#include <tonemapping_fragment>
|
|
148
|
+
#include <colorspace_fragment>
|
|
96
149
|
}
|
|
97
150
|
`,
|
|
98
151
|
side: THREE.BackSide,
|
|
99
152
|
depthWrite: false,
|
|
100
153
|
fog: false,
|
|
101
154
|
});
|
|
155
|
+
setSkyDomeColors(dome, topColor, horizonColor);
|
|
156
|
+
return dome;
|
|
102
157
|
}, [topColor, horizonColor, offset, exponent, sunColor, sunDirection, sunIntensity, hazeStrength, sunGlowStrength, cloudiness]);
|
|
103
158
|
useEffect(() => {
|
|
104
159
|
if (materialRef !== undefined)
|
|
@@ -109,7 +164,18 @@ export function SkyDome({ topColor = SKY_TOP, horizonColor = SKY_HORIZON, radius
|
|
|
109
164
|
materialRef.current = null;
|
|
110
165
|
};
|
|
111
166
|
}, [material, materialRef]);
|
|
167
|
+
const camera = useThree((state) => state.camera);
|
|
168
|
+
const writers = useMemo(() => ({ top: new DisplayColorWriter(), bottom: new DisplayColorWriter() }), []);
|
|
169
|
+
useEffect(() => {
|
|
170
|
+
const far = camera.far;
|
|
171
|
+
if (typeof far !== "number" || radius < far)
|
|
172
|
+
return;
|
|
173
|
+
warnOnce("sky-dome-radius", `[jgengine] sky radius ${radius} is at or beyond the camera far plane ${far}; the dome depth-clips to black. Lower sky.radius or raise camera.frustum.far.`);
|
|
174
|
+
}, [camera, radius]);
|
|
112
175
|
useFrame((state) => {
|
|
176
|
+
const display = material.userData;
|
|
177
|
+
writers.top.write(material.uniforms.topColor.value, display.top, state.gl);
|
|
178
|
+
writers.bottom.write(material.uniforms.bottomColor.value, display.bottom, state.gl);
|
|
113
179
|
const mesh = meshRef.current;
|
|
114
180
|
if (mesh === null)
|
|
115
181
|
return;
|
|
@@ -118,10 +184,14 @@ export function SkyDome({ topColor = SKY_TOP, horizonColor = SKY_HORIZON, radius
|
|
|
118
184
|
});
|
|
119
185
|
return (_jsx("mesh", { ref: meshRef, material: material, renderOrder: -1, frustumCulled: false, children: _jsx("sphereGeometry", { args: [radius, 32, 16] }) }));
|
|
120
186
|
}
|
|
187
|
+
/** Points the dome's sun glow at `position` and keeps the disc lit; called per frame by the time-of-day drivers. */
|
|
188
|
+
function aimSkySun(material, position) {
|
|
189
|
+
material.uniforms.uSunDirection.value.set(position[0], position[1], position[2]).normalize();
|
|
190
|
+
}
|
|
121
191
|
/** @internal */
|
|
122
192
|
export function Daylight({ sky, fog, sun, ambient, lights = true, clouds } = {}) {
|
|
123
193
|
const sunPosition = sun?.position ?? [120, 160, 70];
|
|
124
|
-
return (_jsxs(_Fragment, { children: [sky === false ? null : (_jsx(SkyDome, { ...(sky ?? {}), sunDirection: sunPosition, sunColor: sun?.color ?? SUN_COLOR, sunIntensity: sun?.intensity ?? 1 })), clouds === undefined ? null : (_jsx(VolumetricClouds, { rules: resolveVolumetricClouds(clouds), sunDirection: sunPosition })), fog === false ? null : (_jsx(
|
|
194
|
+
return (_jsxs(_Fragment, { children: [sky === false ? null : (_jsx(SkyDome, { ...(sky ?? {}), sunDirection: sunPosition, sunColor: sun?.color ?? SUN_COLOR, sunIntensity: sun?.intensity ?? 1 })), clouds === undefined ? null : (_jsx(VolumetricClouds, { rules: resolveVolumetricClouds(clouds), sunDirection: sunPosition })), fog === false ? null : (_jsx(SkyFog, { color: fog?.color ?? FOG_COLOR, near: fog?.near ?? 70, far: fog?.far ?? 260 })), lights ? (_jsxs(_Fragment, { children: [_jsx("hemisphereLight", { args: [ambient?.skyColor ?? HEMI_SKY, ambient?.groundColor ?? HEMI_GROUND, ambient?.intensity ?? 0.55] }), _jsx(SunLight, { direction: sunPosition, intensity: sun?.intensity ?? 0.85, color: sun?.color ?? SUN_COLOR })] })) : null] }));
|
|
125
195
|
}
|
|
126
196
|
/** True when at least one band overrides fog or sky — the trigger for the per-frame biome driver. */
|
|
127
197
|
function bandsDriveSkyOrFog(bands) {
|
|
@@ -172,6 +242,7 @@ function BiomeDaylight({ sky, bands, clock, lights, }) {
|
|
|
172
242
|
const baseFraction = timeOfDay ? clock.calendar().dayFraction : SKY_PRESET_DAY_FRACTION[sky.preset];
|
|
173
243
|
const initial = useMemo(() => daylightStateAt(baseFraction, sky), [sky, baseFraction]);
|
|
174
244
|
const sunRef = useRef(null);
|
|
245
|
+
const sunDirectionRef = useRef(initial.sunPosition);
|
|
175
246
|
const hemiRef = useRef(null);
|
|
176
247
|
const fogRef = useRef(null);
|
|
177
248
|
const skyMaterialRef = useRef(null);
|
|
@@ -196,50 +267,49 @@ function BiomeDaylight({ sky, bands, clock, lights, }) {
|
|
|
196
267
|
const skyValue = skySampler(z);
|
|
197
268
|
const fogNode = fogRef.current;
|
|
198
269
|
if (fogNode !== null) {
|
|
199
|
-
fogNode
|
|
270
|
+
setSkyFogColor(fogNode, fog.color);
|
|
200
271
|
fogNode.near = fog.near;
|
|
201
272
|
fogNode.far = fog.far;
|
|
202
273
|
}
|
|
203
274
|
const skyMaterial = skyMaterialRef.current;
|
|
204
275
|
if (skyMaterial !== null) {
|
|
205
|
-
skyMaterial.
|
|
206
|
-
skyMaterial.
|
|
276
|
+
setSkyDomeColors(skyMaterial, skyValue.zenithColor, skyValue.horizonColor);
|
|
277
|
+
aimSkySun(skyMaterial, base.sunPosition);
|
|
207
278
|
}
|
|
279
|
+
sunDirectionRef.current = base.sunPosition;
|
|
208
280
|
const sun = sunRef.current;
|
|
209
|
-
if (sun !== null)
|
|
210
|
-
sun.position.set(base.sunPosition[0], base.sunPosition[1], base.sunPosition[2]);
|
|
281
|
+
if (sun !== null)
|
|
211
282
|
sun.intensity = skyValue.sunIntensity;
|
|
212
|
-
}
|
|
213
283
|
const hemi = hemiRef.current;
|
|
214
284
|
if (hemi !== null)
|
|
215
285
|
hemi.intensity = skyValue.ambientIntensity;
|
|
216
286
|
});
|
|
217
|
-
return (_jsxs(_Fragment, { children: [_jsx(SkyDome, { topColor: initial.skyTop, horizonColor: initial.skyBottom, materialRef: skyMaterialRef, ...skyDomeShape(sky) }), sky.volumetricClouds === undefined ? null : (_jsx(VolumetricClouds, { rules: resolveVolumetricClouds(sky.volumetricClouds), sunDirection: initial.sunPosition })), _jsx(
|
|
287
|
+
return (_jsxs(_Fragment, { children: [_jsx(SkyDome, { topColor: initial.skyTop, horizonColor: initial.skyBottom, sunDirection: initial.sunPosition, sunColor: SUN_COLOR, materialRef: skyMaterialRef, ...skyDomeShape(sky) }), sky.volumetricClouds === undefined ? null : (_jsx(VolumetricClouds, { rules: resolveVolumetricClouds(sky.volumetricClouds), sunDirection: initial.sunPosition })), _jsx(SkyFog, { fogRef: fogRef, color: fogFallback.color, near: fogFallback.near, far: fogFallback.far }), lights ? (_jsxs(_Fragment, { children: [_jsx("hemisphereLight", { ref: hemiRef, args: [HEMI_SKY, HEMI_GROUND, initial.ambientIntensity] }), _jsx(SunLight, { direction: initial.sunPosition, directionRef: sunDirectionRef, lightRef: sunRef, intensity: initial.sunIntensity, color: SUN_COLOR })] })) : null] }));
|
|
218
288
|
}
|
|
219
289
|
function DrivenDaylight({ sky, clock, lights, }) {
|
|
220
290
|
const initial = useMemo(() => daylightStateAt(clock.calendar().dayFraction, sky), [clock, sky]);
|
|
221
291
|
const sunRef = useRef(null);
|
|
292
|
+
const sunDirectionRef = useRef(initial.sunPosition);
|
|
222
293
|
const hemiRef = useRef(null);
|
|
223
294
|
const fogRef = useRef(null);
|
|
224
295
|
const skyMaterialRef = useRef(null);
|
|
225
296
|
useFrame(() => {
|
|
226
297
|
const state = daylightStateAt(clock.calendar().dayFraction, sky);
|
|
298
|
+
sunDirectionRef.current = state.sunPosition;
|
|
227
299
|
const sun = sunRef.current;
|
|
228
|
-
if (sun !== null)
|
|
229
|
-
sun.position.set(state.sunPosition[0], state.sunPosition[1], state.sunPosition[2]);
|
|
300
|
+
if (sun !== null)
|
|
230
301
|
sun.intensity = state.sunIntensity;
|
|
231
|
-
}
|
|
232
302
|
const hemi = hemiRef.current;
|
|
233
303
|
if (hemi !== null)
|
|
234
304
|
hemi.intensity = state.ambientIntensity;
|
|
235
305
|
const fog = fogRef.current;
|
|
236
306
|
if (fog !== null)
|
|
237
|
-
fog
|
|
307
|
+
setSkyFogColor(fog, sky.fog?.color ?? state.background);
|
|
238
308
|
const skyMaterial = skyMaterialRef.current;
|
|
239
309
|
if (skyMaterial !== null) {
|
|
240
|
-
skyMaterial.
|
|
241
|
-
skyMaterial
|
|
310
|
+
setSkyDomeColors(skyMaterial, state.skyTop, state.skyBottom);
|
|
311
|
+
aimSkySun(skyMaterial, state.sunPosition);
|
|
242
312
|
}
|
|
243
313
|
});
|
|
244
|
-
return (_jsxs(_Fragment, { children: [_jsx(SkyDome, { topColor: initial.skyTop, horizonColor: initial.skyBottom, materialRef: skyMaterialRef, ...skyDomeShape(sky) }), sky.volumetricClouds === undefined ? null : (_jsx(VolumetricClouds, { rules: resolveVolumetricClouds(sky.volumetricClouds), sunDirection: initial.sunPosition })), _jsx(
|
|
314
|
+
return (_jsxs(_Fragment, { children: [_jsx(SkyDome, { topColor: initial.skyTop, horizonColor: initial.skyBottom, sunDirection: initial.sunPosition, sunColor: SUN_COLOR, materialRef: skyMaterialRef, ...skyDomeShape(sky) }), sky.volumetricClouds === undefined ? null : (_jsx(VolumetricClouds, { rules: resolveVolumetricClouds(sky.volumetricClouds), sunDirection: initial.sunPosition })), _jsx(SkyFog, { fogRef: fogRef, color: sky.fog?.color ?? initial.background, near: sky.fog?.near ?? 70, far: sky.fog?.far ?? 260 }), lights ? (_jsxs(_Fragment, { children: [_jsx("hemisphereLight", { ref: hemiRef, args: [HEMI_SKY, HEMI_GROUND, initial.ambientIntensity] }), _jsx(SunLight, { direction: initial.sunPosition, directionRef: sunDirectionRef, lightRef: sunRef, intensity: initial.sunIntensity, color: SUN_COLOR })] })) : null] }));
|
|
245
315
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useMemo, useRef } from "react";
|
|
3
3
|
import { useFrame } from "@react-three/fiber";
|
|
4
|
-
import {
|
|
4
|
+
import { PresentationAssetBoundary } from "../presentationRecovery.js";
|
|
5
|
+
import { PresentationRecoveredDraw, useRecoverableTexture } from "../presentationRecovery3d.js";
|
|
5
6
|
import * as THREE from "three";
|
|
6
7
|
import { useGameContext } from "@jgengine/react/provider";
|
|
7
8
|
import { resolveBuildingPalette } from "@jgengine/core/world/buildings";
|
|
@@ -20,14 +21,13 @@ import { createTerrainDetailMaterial } from "../terrain/terrainDetailMaterial.js
|
|
|
20
21
|
import { Ocean } from "../water/Ocean.js";
|
|
21
22
|
import { RainField } from "../weather/RainField.js";
|
|
22
23
|
import { SnowField } from "../weather/SnowField.js";
|
|
23
|
-
import { WeatherUniformProvider } from "../weather/weatherUniforms.js";
|
|
24
24
|
function TexturedTerrainGround({ detail, layout, }) {
|
|
25
25
|
const maps = detail.material.maps;
|
|
26
|
-
const textures =
|
|
26
|
+
const textures = useRecoverableTexture({
|
|
27
27
|
color: maps.color,
|
|
28
28
|
normal: maps.normal,
|
|
29
|
-
roughness: maps.roughness,
|
|
30
|
-
ao: maps.ao,
|
|
29
|
+
...(maps.roughness !== undefined ? { roughness: maps.roughness } : {}),
|
|
30
|
+
...(maps.ao !== undefined ? { ao: maps.ao } : {}),
|
|
31
31
|
});
|
|
32
32
|
useEffect(() => {
|
|
33
33
|
textures.color.colorSpace = THREE.SRGBColorSpace;
|
|
@@ -38,7 +38,7 @@ function TexturedTerrainGround({ detail, layout, }) {
|
|
|
38
38
|
}
|
|
39
39
|
}, [textures]);
|
|
40
40
|
const material = useDisposable(() => createTerrainDetailMaterial(detail, textures).material, [detail, textures]);
|
|
41
|
-
return (_jsx(CarvedTerrain, { field: layout.field, size: layout.size, segments: layout.segments, colors: layout.colors, heightRange: layout.heightRange, paletteAt: layout.paletteAt, center: layout.center, roughness: 0.94, surfaceMaterial: material }));
|
|
41
|
+
return (_jsxs(_Fragment, { children: [_jsx(PresentationRecoveredDraw, {}), _jsx(CarvedTerrain, { field: layout.field, size: layout.size, segments: layout.segments, colors: layout.colors, heightRange: layout.heightRange, paletteAt: layout.paletteAt, center: layout.center, roughness: 0.94, surfaceMaterial: material })] }));
|
|
42
42
|
}
|
|
43
43
|
function TerrainGround({ terrain, field, center, }) {
|
|
44
44
|
const palette = useMemo(() => resolveTerrainPalette(terrain), [terrain]);
|
|
@@ -69,7 +69,7 @@ function TerrainGround({ terrain, field, center, }) {
|
|
|
69
69
|
: createTerrainDetailMaterial(resolvedDetail).material, [resolvedDetail]);
|
|
70
70
|
useEffect(() => () => proceduralMaterial?.dispose(), [proceduralMaterial]);
|
|
71
71
|
if (resolvedDetail?.material !== undefined) {
|
|
72
|
-
return (_jsx(TexturedTerrainGround, { detail: resolvedDetail, layout: { field, size, segments: terrain.segments, colors, heightRange, paletteAt, center } }));
|
|
72
|
+
return (_jsx(PresentationAssetBoundary, { children: _jsx(TexturedTerrainGround, { detail: resolvedDetail, layout: { field, size, segments: terrain.segments, colors, heightRange, paletteAt, center } }) }));
|
|
73
73
|
}
|
|
74
74
|
return (_jsx(CarvedTerrain, { field: field, size: size, segments: terrain.segments, colors: colors, heightRange: heightRange, paletteAt: paletteAt, center: center, roughness: 0.94, surfaceMaterial: proceduralMaterial }));
|
|
75
75
|
}
|
|
@@ -99,14 +99,15 @@ function weatherVolume(area) {
|
|
|
99
99
|
return [area.w, area.h ?? 60, area.d];
|
|
100
100
|
}
|
|
101
101
|
function Precipitation({ weather }) {
|
|
102
|
+
const ctx = useGameContext();
|
|
102
103
|
const [cx, cz] = areaCenter(weather.area);
|
|
103
104
|
if (weather.kind === "rain") {
|
|
104
|
-
return (_jsx("group", { position: [cx, 0, cz], children: _jsx(RainField, { density: weather.density, speed: weather.speed, length: weather.dropLength, width: weather.width, opacity: weather.opacity, color: weather.color, wind: [weather.wind[0], 0, weather.wind[1]], volume: weatherVolume(weather.area) }) }));
|
|
105
|
+
return (_jsx("group", { position: [cx, 0, cz], children: _jsx(RainField, { timeSeconds: ctx.time.now, density: weather.density, speed: weather.speed, length: weather.dropLength, width: weather.width, opacity: weather.opacity, color: weather.color, wind: [weather.wind[0], 0, weather.wind[1]], volume: weatherVolume(weather.area) }) }));
|
|
105
106
|
}
|
|
106
|
-
return (_jsx("group", { position: [cx, 0, cz], children: _jsx(SnowField, { density: weather.density, speed: weather.speed, size: weather.flakeSize, sway: weather.drift, opacity: weather.opacity, color: weather.color, wind: [weather.wind[0], 0, weather.wind[1]], volume: weatherVolume(weather.area) }) }));
|
|
107
|
+
return (_jsx("group", { position: [cx, 0, cz], children: _jsx(SnowField, { timeSeconds: ctx.time.now, density: weather.density, speed: weather.speed, size: weather.flakeSize, sway: weather.drift, opacity: weather.opacity, color: weather.color, wind: [weather.wind[0], 0, weather.wind[1]], volume: weatherVolume(weather.area) }) }));
|
|
107
108
|
}
|
|
108
109
|
function Weather({ weather }) {
|
|
109
|
-
return (_jsx(
|
|
110
|
+
return (_jsx(_Fragment, { children: weather.map((entry, index) => (_jsx(Precipitation, { weather: entry, index: index }, `${entry.kind}-${index}`))) }));
|
|
110
111
|
}
|
|
111
112
|
function oceanConfig(ocean) {
|
|
112
113
|
return {
|
|
@@ -7,7 +7,8 @@ import { useDisposable } from "../render/useDisposable.js";
|
|
|
7
7
|
* Camera-centered proxy dome radius. Must sit inside the default camera far plane
|
|
8
8
|
* (`CAMERA_FRUSTUM_DEFAULTS.far` = 300) — a bigger sphere is clipped whole and renders nothing. The
|
|
9
9
|
* raymarch works in world space against the absolute cloud slab, so this size only has to cover the
|
|
10
|
-
* screen (camera is always at the dome center), never bound the clouds
|
|
10
|
+
* screen (camera is always at the dome center), never bound the clouds; each fragment writes its own
|
|
11
|
+
* depth at the first cloud hit, so the dome's radius never decides what occludes the clouds.
|
|
11
12
|
*/
|
|
12
13
|
const INSIDE_FRUSTUM_DOME_RADIUS = 240;
|
|
13
14
|
function hashSeed(text) {
|
|
@@ -43,8 +44,18 @@ uniform float uScale;
|
|
|
43
44
|
uniform float uSpeed;
|
|
44
45
|
uniform float uTime;
|
|
45
46
|
uniform float uSeedOffset;
|
|
47
|
+
uniform mat4 uProjection;
|
|
48
|
+
uniform mat4 uView;
|
|
46
49
|
varying vec3 vJgCloudWorldPos;
|
|
47
50
|
|
|
51
|
+
// Depth-buffer value of a world point, so the proxy dome tests against the scene at the cloud's
|
|
52
|
+
// own distance instead of the dome's: a tower in front of the slab entry then occludes the cloud.
|
|
53
|
+
float jgCloudDepthAt(vec3 worldPos){
|
|
54
|
+
vec4 clip = uProjection * uView * vec4(worldPos, 1.0);
|
|
55
|
+
float ndc = clip.z / max(clip.w, 1e-5);
|
|
56
|
+
return clamp(ndc * 0.5 + 0.5, 0.0, 0.999999);
|
|
57
|
+
}
|
|
58
|
+
|
|
48
59
|
float jgCloudHash13(vec3 p){
|
|
49
60
|
p = fract(p * 0.1031 + uSeedOffset * 0.0007);
|
|
50
61
|
p += dot(p, p.yzx + 33.33);
|
|
@@ -128,11 +139,13 @@ void main() {
|
|
|
128
139
|
vec3 color = vec3(0.0);
|
|
129
140
|
float sunDot = max(dot(rayDir, normalize(uSunDirection)), 0.0);
|
|
130
141
|
float silver = pow(sunDot, 5.0) * uSunScatter;
|
|
142
|
+
float tHit = -1.0;
|
|
131
143
|
|
|
132
144
|
for (int i = 0; i < STEPS; i++) {
|
|
133
145
|
vec3 pos = rayOrigin + rayDir * t;
|
|
134
146
|
float dens = jgCloudDensity(pos);
|
|
135
147
|
if (dens > 0.002) {
|
|
148
|
+
if (tHit < 0.0) tHit = t;
|
|
136
149
|
float light = jgCloudLight(pos);
|
|
137
150
|
float powder = 1.0 - exp(-dens * 3.0);
|
|
138
151
|
vec3 lit = mix(uColor, uSunColor, light) * (0.55 + 0.45 * powder) + uSunColor * silver * 0.5;
|
|
@@ -146,7 +159,8 @@ void main() {
|
|
|
146
159
|
}
|
|
147
160
|
|
|
148
161
|
float alpha = (1.0 - transmittance) * smoothstep(0.015, 0.22, rayDir.y);
|
|
149
|
-
if (alpha < 0.01) discard;
|
|
162
|
+
if (alpha < 0.01 || tHit < 0.0) discard;
|
|
163
|
+
gl_FragDepthEXT = jgCloudDepthAt(rayOrigin + rayDir * tHit);
|
|
150
164
|
gl_FragColor = vec4(color, alpha);
|
|
151
165
|
}
|
|
152
166
|
`;
|
|
@@ -178,6 +192,8 @@ export function VolumetricClouds({ rules, sunDirection }) {
|
|
|
178
192
|
uSpeed: { value: rules.speed },
|
|
179
193
|
uTime: { value: 0 },
|
|
180
194
|
uSeedOffset: { value: hashSeed(rules.seed) },
|
|
195
|
+
uProjection: { value: new THREE.Matrix4() },
|
|
196
|
+
uView: { value: new THREE.Matrix4() },
|
|
181
197
|
},
|
|
182
198
|
vertexShader: CLOUD_VERTEX_SHADER,
|
|
183
199
|
fragmentShader: CLOUD_FRAGMENT_SHADER,
|
|
@@ -196,6 +212,8 @@ export function VolumetricClouds({ rules, sunDirection }) {
|
|
|
196
212
|
}
|
|
197
213
|
const uniforms = material.uniforms;
|
|
198
214
|
uniforms.uCameraPos.value.copy(state.camera.position);
|
|
215
|
+
uniforms.uProjection.value.copy(state.camera.projectionMatrix);
|
|
216
|
+
uniforms.uView.value.copy(state.camera.matrixWorldInverse);
|
|
199
217
|
uniforms.uTime.value = timeRef.current;
|
|
200
218
|
});
|
|
201
219
|
return (_jsx("mesh", { ref: meshRef, material: material, renderOrder: -0.5, frustumCulled: false, children: _jsx("sphereGeometry", { args: [INSIDE_FRUSTUM_DOME_RADIUS, 32, 16] }) }));
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import type { SkySunConfig } from "@jgengine/core/world/features";
|
|
1
2
|
export interface DaylightCycleConfig {
|
|
2
3
|
horizonColor?: string;
|
|
3
4
|
zenithColor?: string;
|
|
4
5
|
sunIntensity?: number;
|
|
5
6
|
ambientIntensity?: number;
|
|
7
|
+
/** Fixed sun placement, or the noon placement the day arc swings around. Unset keeps the engine arc. */
|
|
8
|
+
sun?: SkySunConfig;
|
|
6
9
|
}
|
|
7
10
|
export interface DaylightState {
|
|
8
11
|
sunPosition: [number, number, number];
|
|
@@ -16,6 +19,23 @@ export declare const DEFAULT_DAY_SUN_INTENSITY = 1;
|
|
|
16
19
|
export declare const DEFAULT_DAY_AMBIENT_INTENSITY = 0.6;
|
|
17
20
|
export declare const DEFAULT_DAY_SKY_TOP = "#3fa4f2";
|
|
18
21
|
export declare const DEFAULT_DAY_SKY_BOTTOM = "#e3f4ff";
|
|
22
|
+
/** The engine arc's noon height: `atan(1 / SUN_DEPTH_RATIO)`, ~68 degrees. */
|
|
23
|
+
export declare const DEFAULT_SUN_ELEVATION_DEG: number;
|
|
24
|
+
/**
|
|
25
|
+
* Unit direction toward a sun at compass `azimuth` (0 = -Z, 90 = +X) and `elevation` above the
|
|
26
|
+
* horizon, both in degrees. Shared by the dome glow, the sky-owned sun light, and the editor.
|
|
27
|
+
* @internal
|
|
28
|
+
*/
|
|
29
|
+
export declare function sunDirectionFromBearing(azimuth: number, elevation: number): [number, number, number];
|
|
30
|
+
/**
|
|
31
|
+
* Inverse of {@link sunDirectionFromBearing}: the bearing/elevation (degrees) of a world-space
|
|
32
|
+
* direction such as an authored directional light's position. A zero vector reads as overhead.
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
35
|
+
export declare function bearingFromDirection(direction: readonly [number, number, number]): {
|
|
36
|
+
azimuth: number;
|
|
37
|
+
elevation: number;
|
|
38
|
+
};
|
|
19
39
|
export declare const SKY_PRESET_DAY_FRACTION: Record<"day" | "dusk" | "night", number>;
|
|
20
40
|
/** @internal */
|
|
21
41
|
export declare function lerpHexColor(a: string, b: string, t: number): string;
|
|
@@ -16,6 +16,34 @@ export const DEFAULT_DAY_SKY_TOP = "#3fa4f2";
|
|
|
16
16
|
export const DEFAULT_DAY_SKY_BOTTOM = "#e3f4ff";
|
|
17
17
|
const SUN_DISTANCE = 200;
|
|
18
18
|
const SUN_DEPTH_RATIO = 0.4;
|
|
19
|
+
const DEG = Math.PI / 180;
|
|
20
|
+
/** The engine arc's noon height: `atan(1 / SUN_DEPTH_RATIO)`, ~68 degrees. */
|
|
21
|
+
export const DEFAULT_SUN_ELEVATION_DEG = Math.atan2(1, SUN_DEPTH_RATIO) / DEG;
|
|
22
|
+
/**
|
|
23
|
+
* Unit direction toward a sun at compass `azimuth` (0 = -Z, 90 = +X) and `elevation` above the
|
|
24
|
+
* horizon, both in degrees. Shared by the dome glow, the sky-owned sun light, and the editor.
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
export function sunDirectionFromBearing(azimuth, elevation) {
|
|
28
|
+
const az = azimuth * DEG;
|
|
29
|
+
const el = elevation * DEG;
|
|
30
|
+
const flat = Math.cos(el);
|
|
31
|
+
return [Math.sin(az) * flat, Math.sin(el), -Math.cos(az) * flat];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Inverse of {@link sunDirectionFromBearing}: the bearing/elevation (degrees) of a world-space
|
|
35
|
+
* direction such as an authored directional light's position. A zero vector reads as overhead.
|
|
36
|
+
* @internal
|
|
37
|
+
*/
|
|
38
|
+
export function bearingFromDirection(direction) {
|
|
39
|
+
const [x, y, z] = direction;
|
|
40
|
+
const length = Math.hypot(x, y, z);
|
|
41
|
+
if (length === 0)
|
|
42
|
+
return { azimuth: 0, elevation: 90 };
|
|
43
|
+
const azimuth = ((Math.atan2(x, -z) / DEG) + 360) % 360;
|
|
44
|
+
const elevation = Math.asin(Math.max(-1, Math.min(1, y / length))) / DEG;
|
|
45
|
+
return { azimuth, elevation };
|
|
46
|
+
}
|
|
19
47
|
export const SKY_PRESET_DAY_FRACTION = {
|
|
20
48
|
night: 0,
|
|
21
49
|
day: 0.5,
|
|
@@ -106,13 +134,37 @@ export function daylightStateAt(dayFraction, config = {}) {
|
|
|
106
134
|
const ambientIntensity = lerp(from.ambientIntensity, to.ambientIntensity, localT);
|
|
107
135
|
const skyTop = lerpHexColor(from.skyTop, to.skyTop, localT);
|
|
108
136
|
const skyBottom = lerpHexColor(from.skyBottom, to.skyBottom, localT);
|
|
137
|
+
const sunPosition = sunPositionAt(wrapped, config.sun);
|
|
138
|
+
return { sunPosition, sunIntensity, ambientIntensity, skyTop, skyBottom, background: skyBottom };
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Sun position for a wrapped day fraction. Without `sun`, the legacy arc: east at dawn, ~68 degrees
|
|
142
|
+
* up at noon, west at dusk, tilted 0.4 toward +Z. With `sun`, the arc is rotated so noon lands
|
|
143
|
+
* exactly on the authored bearing and elevation and the sun still rises 90 degrees before it and
|
|
144
|
+
* sets 90 degrees after it, so a fixed preset (noon, dusk, night fractions) samples the same arc.
|
|
145
|
+
*/
|
|
146
|
+
function sunPositionAt(wrapped, sun) {
|
|
109
147
|
const angle = (wrapped - 0.25) * 2 * Math.PI;
|
|
110
|
-
const
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
148
|
+
const along = Math.cos(angle);
|
|
149
|
+
const up = Math.sin(angle);
|
|
150
|
+
if (sun === undefined) {
|
|
151
|
+
return [along * SUN_DISTANCE, up * SUN_DISTANCE, SUN_DISTANCE * SUN_DEPTH_RATIO];
|
|
152
|
+
}
|
|
153
|
+
const noon = sunDirectionFromBearing(sun.azimuth ?? 0, sun.elevation ?? DEFAULT_SUN_ELEVATION_DEG);
|
|
154
|
+
const horizon = normalize([noon[0], 0, noon[2]], [0, 0, -1]);
|
|
155
|
+
const east = [-horizon[2], 0, horizon[0]];
|
|
156
|
+
const zenith = normalize(cross(east, noon), [0, 1, 0]);
|
|
157
|
+
const rise = normalize(cross(noon, zenith), east);
|
|
158
|
+
return [
|
|
159
|
+
(rise[0] * along + noon[0] * up) * SUN_DISTANCE,
|
|
160
|
+
(rise[1] * along + noon[1] * up) * SUN_DISTANCE,
|
|
161
|
+
(rise[2] * along + noon[2] * up) * SUN_DISTANCE,
|
|
116
162
|
];
|
|
117
|
-
|
|
163
|
+
}
|
|
164
|
+
function cross(a, b) {
|
|
165
|
+
return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
|
|
166
|
+
}
|
|
167
|
+
function normalize(v, fallback) {
|
|
168
|
+
const length = Math.hypot(v[0], v[1], v[2]);
|
|
169
|
+
return length < 1e-6 ? [fallback[0], fallback[1], fallback[2]] : [v[0] / length, v[1] / length, v[2] / length];
|
|
118
170
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One shadow map for an outdoor sun: 2048 texels over a 70 m box that leads the camera, so the
|
|
3
|
+
* ground the player looks at gets ~3.4 cm texels and a PCF radius wide enough to hide the
|
|
4
|
+
* stair-step. A 180 m box centered on the camera wasted half its texels behind the view.
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
export declare const SUN_SHADOW: {
|
|
8
|
+
readonly mapSize: 2048;
|
|
9
|
+
readonly halfExtent: 35;
|
|
10
|
+
readonly lead: 22;
|
|
11
|
+
readonly radius: 6;
|
|
12
|
+
readonly distance: 220;
|
|
13
|
+
readonly near: 10;
|
|
14
|
+
readonly far: 520;
|
|
15
|
+
readonly bias: -0.0004;
|
|
16
|
+
readonly normalBias: 0.02;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Where the sun's shadow box centers this frame: `lead` meters ahead of the camera along its
|
|
20
|
+
* horizontal view, snapped to whole shadow texels so edges hold still while the camera moves.
|
|
21
|
+
* Looking straight down (no horizontal view) centers on the camera.
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
24
|
+
export declare function sunShadowFocus(camera: {
|
|
25
|
+
x: number;
|
|
26
|
+
z: number;
|
|
27
|
+
}, forward: {
|
|
28
|
+
x: number;
|
|
29
|
+
z: number;
|
|
30
|
+
}, out?: {
|
|
31
|
+
x: number;
|
|
32
|
+
z: number;
|
|
33
|
+
}): {
|
|
34
|
+
x: number;
|
|
35
|
+
z: number;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* The sun light's world position for a focus point: `distance` meters back along the sun
|
|
39
|
+
* direction, so near/far cover the box whatever magnitude the sky's sun vector carries.
|
|
40
|
+
* @internal
|
|
41
|
+
*/
|
|
42
|
+
export declare function sunLightPosition(focus: {
|
|
43
|
+
x: number;
|
|
44
|
+
z: number;
|
|
45
|
+
}, sun: readonly [number, number, number], out?: [number, number, number]): [number, number, number];
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One shadow map for an outdoor sun: 2048 texels over a 70 m box that leads the camera, so the
|
|
3
|
+
* ground the player looks at gets ~3.4 cm texels and a PCF radius wide enough to hide the
|
|
4
|
+
* stair-step. A 180 m box centered on the camera wasted half its texels behind the view.
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
export const SUN_SHADOW = {
|
|
8
|
+
mapSize: 2048,
|
|
9
|
+
halfExtent: 35,
|
|
10
|
+
lead: 22,
|
|
11
|
+
radius: 6,
|
|
12
|
+
distance: 220,
|
|
13
|
+
near: 10,
|
|
14
|
+
far: 520,
|
|
15
|
+
bias: -0.0004,
|
|
16
|
+
normalBias: 0.02,
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Where the sun's shadow box centers this frame: `lead` meters ahead of the camera along its
|
|
20
|
+
* horizontal view, snapped to whole shadow texels so edges hold still while the camera moves.
|
|
21
|
+
* Looking straight down (no horizontal view) centers on the camera.
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
24
|
+
export function sunShadowFocus(camera, forward, out = { x: 0, z: 0 }) {
|
|
25
|
+
const length = Math.hypot(forward.x, forward.z);
|
|
26
|
+
const lead = length < 1e-6 ? 0 : SUN_SHADOW.lead / length;
|
|
27
|
+
const texel = (SUN_SHADOW.halfExtent * 2) / SUN_SHADOW.mapSize;
|
|
28
|
+
out.x = Math.round((camera.x + forward.x * lead) / texel) * texel;
|
|
29
|
+
out.z = Math.round((camera.z + forward.z * lead) / texel) * texel;
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The sun light's world position for a focus point: `distance` meters back along the sun
|
|
34
|
+
* direction, so near/far cover the box whatever magnitude the sky's sun vector carries.
|
|
35
|
+
* @internal
|
|
36
|
+
*/
|
|
37
|
+
export function sunLightPosition(focus, sun, out = [0, 0, 0]) {
|
|
38
|
+
const length = Math.hypot(sun[0], sun[1], sun[2]);
|
|
39
|
+
if (length < 1e-6) {
|
|
40
|
+
out[0] = focus.x;
|
|
41
|
+
out[1] = SUN_SHADOW.distance;
|
|
42
|
+
out[2] = focus.z;
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
const scale = SUN_SHADOW.distance / length;
|
|
46
|
+
out[0] = focus.x + sun[0] * scale;
|
|
47
|
+
out[1] = sun[1] * scale;
|
|
48
|
+
out[2] = focus.z + sun[2] * scale;
|
|
49
|
+
return out;
|
|
50
|
+
}
|