@jgengine/shell 0.10.0 → 0.12.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 +39 -2
- package/LICENSE +203 -661
- package/README.md +1 -1
- package/dist/CombatPresentation.d.ts +5 -0
- package/dist/CombatPresentation.js +8 -0
- package/dist/GameHost.d.ts +25 -1
- package/dist/GameHost.js +60 -4
- package/dist/GamePhaseStamp.d.ts +1 -0
- package/dist/GamePhaseStamp.js +9 -0
- package/dist/GamePlayer.d.ts +5 -0
- package/dist/GamePlayer.js +5 -0
- package/dist/GamePlayerShell.d.ts +5 -12
- package/dist/GamePlayerShell.js +67 -1332
- package/dist/GameUiPreview.js +3 -2
- package/dist/Shell3dPresentation.d.ts +63 -0
- package/dist/Shell3dPresentation.js +302 -0
- package/dist/ShellChrome.d.ts +51 -0
- package/dist/ShellChrome.js +51 -0
- package/dist/ShellHudPresentation.d.ts +41 -0
- package/dist/ShellHudPresentation.js +22 -0
- package/dist/audio/audioEngine.d.ts +4 -0
- package/dist/audio/audioEngine.js +128 -21
- package/dist/audio/audioWire.d.ts +40 -0
- package/dist/audio/audioWire.js +62 -0
- package/dist/audio/loopParams.d.ts +20 -0
- package/dist/audio/loopParams.js +28 -0
- package/dist/behaviour.d.ts +2 -1
- package/dist/behaviour.js +2 -1
- package/dist/behaviourAttach.d.ts +1 -0
- package/dist/behaviourAttach.js +1 -0
- package/dist/behaviourDriver.d.ts +1 -0
- package/dist/behaviourDriver.js +1 -0
- package/dist/boundActionDispatch.d.ts +13 -0
- package/dist/boundActionDispatch.js +37 -0
- package/dist/camera/GameCameraRig.d.ts +5 -2
- package/dist/camera/GameCameraRig.js +8 -3
- package/dist/camera/GameFirstPersonCamera.d.ts +9 -2
- package/dist/camera/GameFirstPersonCamera.js +8 -3
- package/dist/camera/GameInspectionCamera.d.ts +1 -1
- package/dist/camera/GameInspectionCamera.js +2 -2
- package/dist/camera/GameOrbitCamera.js +24 -9
- package/dist/camera/cameraBlendMath.d.ts +3 -0
- package/dist/camera/cameraBlendMath.js +3 -0
- package/dist/camera/cameraRigs.d.ts +3 -1
- package/dist/camera/cameraRigs.js +21 -3
- package/dist/camera/fovPreference.d.ts +6 -1
- package/dist/camera/fovPreference.js +6 -1
- package/dist/camera/index.d.ts +4 -7
- package/dist/camera/index.js +4 -7
- package/dist/camera/inspectionCameraMath.d.ts +7 -2
- package/dist/camera/inspectionCameraMath.js +7 -2
- package/dist/camera/orbitCameraMath.d.ts +36 -6
- package/dist/camera/orbitCameraMath.js +56 -6
- package/dist/camera/rigMath.d.ts +114 -24
- package/dist/camera/rigMath.js +129 -24
- package/dist/camera/rigResolve.d.ts +4 -2
- package/dist/camera/rigResolve.js +4 -2
- package/dist/camera/shakeChannel.d.ts +6 -2
- package/dist/camera/shakeChannel.js +6 -2
- package/dist/camera/shakeChannelMath.d.ts +1 -0
- package/dist/camera/shakeChannelMath.js +1 -0
- package/dist/commandSink.d.ts +8 -3
- package/dist/commandSink.js +8 -3
- package/dist/defineGame.d.ts +15 -1
- package/dist/defineGame.js +50 -9
- package/dist/devtools/ColPanel.d.ts +1 -0
- package/dist/devtools/ColPanel.js +14 -0
- package/dist/devtools/DevtoolsOverlay.d.ts +11 -7
- package/dist/devtools/DevtoolsOverlay.js +47 -462
- package/dist/devtools/KeysPanel.d.ts +4 -0
- package/dist/devtools/KeysPanel.js +31 -0
- package/dist/devtools/LogsPanel.d.ts +1 -0
- package/dist/devtools/LogsPanel.js +12 -0
- package/dist/devtools/NetPanel.d.ts +4 -0
- package/dist/devtools/NetPanel.js +10 -0
- package/dist/devtools/PerfPanel.d.ts +4 -0
- package/dist/devtools/PerfPanel.js +53 -0
- package/dist/devtools/TunePanel.d.ts +3 -0
- package/dist/devtools/TunePanel.js +309 -0
- package/dist/devtools/agentBridge.d.ts +41 -0
- package/dist/devtools/agentBridge.js +266 -0
- package/dist/devtools/collisionDebug.d.ts +6 -0
- package/dist/devtools/collisionDebug.js +6 -0
- package/dist/devtools/collisionDebugMath.d.ts +8 -2
- package/dist/devtools/collisionDebugMath.js +24 -5
- package/dist/devtools/devtoolsOverrides.d.ts +6 -0
- package/dist/devtools/devtoolsOverrides.js +48 -0
- package/dist/devtools/panelAtoms.d.ts +9 -0
- package/dist/devtools/panelAtoms.js +10 -0
- package/dist/devtools/perfDiagnose.d.ts +2 -0
- package/dist/devtools/perfDiagnose.js +43 -0
- package/dist/diagnostics/RuntimeDiagnostics.d.ts +36 -0
- package/dist/diagnostics/RuntimeDiagnostics.js +80 -0
- package/dist/drivers/FrameDriver.d.ts +37 -0
- package/dist/drivers/FrameDriver.js +232 -0
- package/dist/drivers/HudOnlyDriver.d.ts +16 -0
- package/dist/drivers/HudOnlyDriver.js +60 -0
- package/dist/environment/Daylight.d.ts +23 -7
- package/dist/environment/Daylight.js +106 -13
- package/dist/environment/EnvironmentScene.js +44 -9
- package/dist/environment/VolumetricClouds.d.ts +17 -0
- package/dist/environment/VolumetricClouds.js +202 -0
- package/dist/environment/daylightCycle.d.ts +3 -1
- package/dist/environment/daylightCycle.js +3 -1
- package/dist/environment/groundPadMath.d.ts +3 -0
- package/dist/environment/groundPadMath.js +3 -0
- package/dist/environment/index.d.ts +1 -0
- package/dist/environment/index.js +1 -0
- package/dist/environment/skyLightingPolicy.d.ts +2 -0
- package/dist/environment/skyLightingPolicy.js +2 -0
- package/dist/environment/terrainGroundColor.d.ts +9 -0
- package/dist/environment/terrainGroundColor.js +22 -0
- package/dist/gameKit.d.ts +27 -0
- package/dist/gameKit.js +29 -0
- package/dist/hotbarActions.d.ts +26 -0
- package/dist/hotbarActions.js +27 -0
- package/dist/inputSink.d.ts +16 -4
- package/dist/inputSink.js +57 -7
- package/dist/map/MapMarkerBeacons.d.ts +3 -2
- package/dist/materialOverride.d.ts +10 -0
- package/dist/materialOverride.js +15 -3
- package/dist/multiplayer.js +8 -2
- package/dist/postfx/PostProcessing.d.ts +9 -1
- package/dist/postfx/PostProcessing.js +56 -11
- package/dist/postfx/gradeShader.js +9 -1
- package/dist/presentationResolve.d.ts +35 -0
- package/dist/presentationResolve.js +51 -0
- package/dist/registry.d.ts +4 -2
- package/dist/render/EnvironmentLighting.d.ts +10 -0
- package/dist/render/EnvironmentLighting.js +34 -0
- package/dist/render/SceneLighting.d.ts +7 -0
- package/dist/render/SceneLighting.js +18 -0
- package/dist/render/SceneModels.d.ts +14 -0
- package/dist/render/SceneModels.js +337 -0
- package/dist/render/assetBase.d.ts +17 -0
- package/dist/render/assetBase.js +35 -0
- package/dist/render/defaultSurface.d.ts +29 -0
- package/dist/render/defaultSurface.js +101 -0
- package/dist/render/modelLoad.d.ts +24 -0
- package/dist/render/modelLoad.js +76 -0
- package/dist/render/modelRender.d.ts +11 -0
- package/dist/render/modelRender.js +15 -0
- package/dist/render/resolveModel.d.ts +54 -3
- package/dist/render/resolveModel.js +116 -4
- package/dist/render/useEntityRenderCues.d.ts +13 -0
- package/dist/render/useEntityRenderCues.js +62 -0
- package/dist/scatter/InstancedScatter.d.ts +24 -0
- package/dist/scatter/InstancedScatter.js +135 -0
- package/dist/scatter/scatterModels.d.ts +25 -0
- package/dist/scatter/scatterModels.js +61 -0
- package/dist/scatter/scatterProxies.d.ts +6 -0
- package/dist/scatter/scatterProxies.js +118 -0
- package/dist/scatter.d.ts +1 -0
- package/dist/scatter.js +1 -0
- package/dist/scene/AuthoredScene.d.ts +84 -0
- package/dist/scene/AuthoredScene.js +178 -0
- package/dist/scene/CityRenderer.d.ts +2 -0
- package/dist/scene/CityRenderer.js +1031 -0
- package/dist/scene/GeneratedAssetRenderer.d.ts +14 -0
- package/dist/scene/GeneratedAssetRenderer.js +59 -0
- package/dist/scene/GrassFieldRenderer.d.ts +2 -0
- package/dist/scene/GrassFieldRenderer.js +73 -0
- package/dist/scene/PoleLineRenderer.d.ts +2 -0
- package/dist/scene/PoleLineRenderer.js +287 -0
- package/dist/scene/SoilPatchRenderer.d.ts +2 -0
- package/dist/scene/SoilPatchRenderer.js +84 -0
- package/dist/scene/StudioStage.d.ts +37 -0
- package/dist/scene/StudioStage.js +42 -0
- package/dist/scene/WaterRenderer.d.ts +2 -0
- package/dist/scene/WaterRenderer.js +61 -0
- package/dist/scene/builtinSceneKindRenderers.d.ts +2 -0
- package/dist/scene/builtinSceneKindRenderers.js +26 -0
- package/dist/scene/cityWindowMaterial.d.ts +16 -0
- package/dist/scene/cityWindowMaterial.js +81 -0
- package/dist/scene/sceneKindRenderers.d.ts +30 -0
- package/dist/scene/sceneKindRenderers.js +17 -0
- package/dist/scene.d.ts +1 -0
- package/dist/scene.js +1 -0
- package/dist/settings/appliedSettings.d.ts +7 -1
- package/dist/settings/appliedSettings.js +13 -2
- package/dist/settings/settingsController.d.ts +2 -0
- package/dist/settings/settingsController.js +22 -1
- package/dist/shellConstants.d.ts +4 -0
- package/dist/shellConstants.js +4 -0
- package/dist/shellMovement.d.ts +8 -0
- package/dist/shellMovement.js +11 -0
- package/dist/shellPointer.d.ts +20 -0
- package/dist/shellPointer.js +28 -0
- package/dist/structures/PlacementGhost.d.ts +4 -0
- package/dist/structures/PlacementGhost.js +4 -0
- package/dist/structures/TransformGizmo.d.ts +53 -0
- package/dist/structures/TransformGizmo.js +59 -0
- package/dist/structures/index.d.ts +1 -0
- package/dist/structures/index.js +1 -0
- package/dist/terrain/GrassField.d.ts +11 -3
- package/dist/terrain/GrassField.js +16 -6
- package/dist/terrain/TerraformBrushCursor.js +3 -0
- package/dist/terrain/grassBudget.d.ts +11 -2
- package/dist/terrain/grassBudget.js +20 -4
- package/dist/terrain/grassGeometry.d.ts +35 -0
- package/dist/terrain/grassGeometry.js +85 -20
- package/dist/terrain/grassMaterial.d.ts +20 -0
- package/dist/terrain/grassMaterial.js +68 -13
- package/dist/terrain/heightfieldGeometry.d.ts +34 -0
- package/dist/terrain/heightfieldGeometry.js +109 -0
- package/dist/terrain/index.d.ts +1 -0
- package/dist/terrain/index.js +1 -0
- package/dist/terrain/random.d.ts +3 -0
- package/dist/terrain/random.js +3 -0
- package/dist/terrain/soilPatchMaterial.d.ts +23 -0
- package/dist/terrain/soilPatchMaterial.js +147 -0
- package/dist/terrain/terrainDetailMaterial.d.ts +19 -2
- package/dist/terrain/terrainDetailMaterial.js +87 -7
- package/dist/terrain/terrainMath.d.ts +8 -1
- package/dist/terrain/terrainMath.js +8 -1
- package/dist/touch/TouchControlsOverlay.d.ts +9 -5
- package/dist/touch/TouchControlsOverlay.js +201 -22
- package/dist/useShellMultiplayerSync.d.ts +9 -0
- package/dist/useShellMultiplayerSync.js +98 -0
- package/dist/visibility/CullingProvider.js +1 -1
- package/dist/water/Ocean.d.ts +7 -1
- package/dist/water/Ocean.js +16 -3
- package/dist/water/OceanConfig.d.ts +11 -0
- package/dist/water/OceanConfig.js +8 -0
- package/dist/water/OceanMaterial.d.ts +12 -0
- package/dist/water/OceanMaterial.js +9 -0
- package/dist/water/OceanShader.d.ts +2 -2
- package/dist/water/OceanShader.js +78 -10
- package/dist/weather/fireSpreadPose.d.ts +1 -0
- package/dist/weather/fireSpreadPose.js +1 -0
- package/dist/weather/weatherGeometry.d.ts +1 -0
- package/dist/weather/weatherGeometry.js +1 -0
- package/dist/weather/weatherMath.d.ts +3 -0
- package/dist/weather/weatherMath.js +3 -0
- package/dist/weather/weatherUniforms.d.ts +3 -0
- package/dist/weather/weatherUniforms.js +3 -0
- package/dist/world/PlaceScene.d.ts +11 -0
- package/dist/world/PlaceScene.js +27 -0
- package/dist/world/RetainedVfx.d.ts +10 -0
- package/dist/world/RetainedVfx.js +136 -0
- package/dist/world/WorldHud.d.ts +49 -2
- package/dist/world/WorldHud.js +87 -2
- package/dist/world/WorldScene.d.ts +21 -0
- package/dist/world/WorldScene.js +186 -0
- package/dist/world/WorldVfx.d.ts +9 -0
- package/dist/world/WorldVfx.js +184 -0
- package/dist/world/entityPose.d.ts +2 -0
- package/dist/world/entityPose.js +2 -0
- package/dist/world/floatTextStyle.d.ts +1 -0
- package/dist/world/floatTextStyle.js +1 -0
- package/dist/world/telegraphPulse.d.ts +1 -0
- package/dist/world/telegraphPulse.js +1 -0
- package/dist/world/worldBarSamples.d.ts +25 -0
- package/dist/world/worldBarSamples.js +38 -0
- package/dist/worldSky.d.ts +11 -0
- package/dist/worldSky.js +21 -0
- package/dist/worldSync.d.ts +2 -1
- package/dist/worldSync.js +2 -1
- package/package.json +6 -6
- package/skills/game-design/SKILL.md +28 -0
- package/skills/game-design/agents/openai.yaml +4 -0
- package/skills/game-design/references/application-playbook.md +170 -0
- package/skills/game-design/references/field-guide.md +288 -0
- package/skills/jgengine/SKILL.md +54 -0
- package/skills/jgengine/api.md +527 -0
- package/skills/jgengine/capabilities.md +131 -0
- package/skills/jgengine/recipes/minimal-game.md +88 -0
- package/skills/jgengine-ui/SKILL.md +41 -0
- package/skills/jgengine-ui/api.md +2007 -0
- package/skills/jgengine-ui/capabilities.md +211 -0
- package/skills/jgengine-ui/recipes/portable-minimap.md +150 -0
- package/skills/jgengine-ui/reference.md +513 -0
- package/skills/jgengine-ui/references/visual-scorecard.md +73 -0
- package/skills/jgengine-world/SKILL.md +51 -0
- package/skills/jgengine-world/api.md +3056 -0
- package/skills/jgengine-world/capabilities.md +476 -0
- package/skills/jgengine-world/recipes/commanding-entities.md +128 -0
- package/skills/jgengine-world/reference.md +261 -0
- package/skills/level-design/SKILL.md +54 -0
- package/skills/level-design/agents/openai.yaml +4 -0
- package/skills/level-design/references/application-playbook.md +192 -0
- package/skills/level-design/references/field-guide.md +337 -0
- package/skills/level-design/references/live-editor-loop.md +179 -0
- package/dist/cartridge.d.ts +0 -145
- package/dist/cartridge.js +0 -246
- package/llms.txt +0 -2761
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as THREE from "three";
|
|
2
2
|
import { buildOceanWaveUniforms } from "./OceanConfig.js";
|
|
3
3
|
import { oceanFragmentShader, oceanVertexShader } from "./OceanShader.js";
|
|
4
|
+
/** Matches the day-sky sun heading closely enough for stylized glints — not a lighting contract. */
|
|
5
|
+
const OCEAN_SUN_DIRECTION = new THREE.Vector3(0.45, 0.78, 0.35).normalize();
|
|
4
6
|
export function createOceanMaterial(config) {
|
|
5
7
|
const material = new THREE.ShaderMaterial({
|
|
6
8
|
vertexShader: oceanVertexShader,
|
|
@@ -24,6 +26,10 @@ export function createOceanMaterial(config) {
|
|
|
24
26
|
uFoamSoftness: { value: config.foam.softness },
|
|
25
27
|
uFoamIntensity: { value: config.foam.intensity },
|
|
26
28
|
uFoamCoverage: { value: config.foam.coverage },
|
|
29
|
+
uDepthRange: { value: config.color.depthRange },
|
|
30
|
+
uShoreFoamWidth: { value: config.foam.shoreWidth },
|
|
31
|
+
uSparkle: { value: config.color.sparkle },
|
|
32
|
+
uSunDirection: { value: OCEAN_SUN_DIRECTION.clone() },
|
|
27
33
|
},
|
|
28
34
|
});
|
|
29
35
|
syncOceanMaterial(material, config, 0);
|
|
@@ -46,6 +52,9 @@ export function syncOceanMaterial(material, config, elapsedSeconds) {
|
|
|
46
52
|
material.uniforms.uFoamSoftness.value = config.foam.softness;
|
|
47
53
|
material.uniforms.uFoamIntensity.value = config.foam.intensity;
|
|
48
54
|
material.uniforms.uFoamCoverage.value = config.foam.coverage;
|
|
55
|
+
material.uniforms.uDepthRange.value = config.color.depthRange;
|
|
56
|
+
material.uniforms.uShoreFoamWidth.value = config.foam.shoreWidth;
|
|
57
|
+
material.uniforms.uSparkle.value = config.color.sparkle;
|
|
49
58
|
material.transparent = config.color.opacity < 1;
|
|
50
59
|
material.depthWrite = config.color.opacity >= 0.98;
|
|
51
60
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const oceanVertexShader = "\nuniform float uTime;\nuniform vec2 uWaveDirections[6];\nuniform vec4 uWaveParams[6];\nuniform float uChoppiness;\nuniform float uFoamThreshold;\nuniform float uFoamSoftness;\nuniform float uFoamCoverage;\n\nvarying vec3 vWorldPosition;\nvarying vec3 vNormal;\nvarying float vCrest;\nvarying float vWaveHeight;\n\nvoid main() {\n vec3 displaced = position;\n vec3 tangent = vec3(1.0, 0.0, 0.0);\n vec3 bitangent = vec3(0.0, 0.0, 1.0);\n float crest = 0.0;\n\n for (int i = 0; i < 6; i++) {\n vec2 direction = normalize(uWaveDirections[i]);\n vec4 wave = uWaveParams[i];\n float k = wave.x;\n float amplitude = wave.y;\n float steepness = wave.z;\n float omega = wave.w;\n float phase = k * dot(direction, position.xz) - omega * uTime;\n float sine = sin(phase);\n float cosine = cos(phase);\n float horizontal = steepness * amplitude * uChoppiness;\n displaced.x += horizontal * direction.x * cosine;\n displaced.z += horizontal * direction.y * cosine;\n displaced.y += amplitude * sine;\n float slopeTerm = horizontal * k * sine;\n tangent += vec3(-slopeTerm * direction.x * direction.x, amplitude * k * direction.x * cosine, -slopeTerm * direction.x * direction.y);\n bitangent += vec3(-slopeTerm * direction.x * direction.y, amplitude * k * direction.y * cosine, -slopeTerm * direction.y * direction.y);\n crest = max(crest, smoothstep(uFoamThreshold, uFoamThreshold + uFoamSoftness, sine * steepness + uFoamCoverage * 0.35));\n }\n\n vNormal = normalize(cross(bitangent, tangent));\n vCrest = crest;\n vWaveHeight = displaced.y;\n vec4 worldPosition = modelMatrix * vec4(displaced, 1.0);\n vWorldPosition = worldPosition.xyz;\n gl_Position = projectionMatrix * viewMatrix * worldPosition;\n}\n";
|
|
2
|
-
export declare const oceanFragmentShader = "\nuniform vec3 uShallowColor;\nuniform vec3 uDeepColor;\nuniform vec3 uCrestColor;\nuniform vec3 uFoamColor;\nuniform float uOpacity;\nuniform float uFresnelStrength;\nuniform float uHorizonBlend;\nuniform float uFoamIntensity;\n\nvarying vec3 vWorldPosition;\nvarying vec3 vNormal;\nvarying float vCrest;\nvarying float vWaveHeight;\n\
|
|
1
|
+
export declare const oceanVertexShader = "\nuniform float uTime;\nuniform vec2 uWaveDirections[6];\nuniform vec4 uWaveParams[6];\nuniform float uChoppiness;\nuniform float uFoamThreshold;\nuniform float uFoamSoftness;\nuniform float uFoamCoverage;\n\nattribute float aDepth;\n\nvarying vec3 vWorldPosition;\nvarying vec3 vNormal;\nvarying float vCrest;\nvarying float vWaveHeight;\nvarying float vDepth;\nvarying vec2 vOceanUv;\n\nvoid main() {\n vOceanUv = uv;\n vec3 displaced = position;\n vec3 tangent = vec3(1.0, 0.0, 0.0);\n vec3 bitangent = vec3(0.0, 0.0, 1.0);\n float crest = 0.0;\n // Waves flatten as the bed shoals so swells die at the shoreline instead of clipping it.\n float depthDamp = smoothstep(0.05, 1.4, aDepth);\n\n for (int i = 0; i < 6; i++) {\n vec2 direction = normalize(uWaveDirections[i]);\n vec4 wave = uWaveParams[i];\n float k = wave.x;\n float amplitude = wave.y * depthDamp;\n float steepness = wave.z;\n float omega = wave.w;\n float phase = k * dot(direction, position.xz) - omega * uTime;\n float sine = sin(phase);\n float cosine = cos(phase);\n float horizontal = steepness * amplitude * uChoppiness;\n displaced.x += horizontal * direction.x * cosine;\n displaced.z += horizontal * direction.y * cosine;\n displaced.y += amplitude * sine;\n float slopeTerm = horizontal * k * sine;\n tangent += vec3(-slopeTerm * direction.x * direction.x, amplitude * k * direction.x * cosine, -slopeTerm * direction.x * direction.y);\n bitangent += vec3(-slopeTerm * direction.x * direction.y, amplitude * k * direction.y * cosine, -slopeTerm * direction.y * direction.y);\n crest = max(crest, smoothstep(uFoamThreshold, uFoamThreshold + uFoamSoftness, sine * steepness + uFoamCoverage * 0.35));\n }\n\n vNormal = normalize(cross(bitangent, tangent));\n vCrest = crest;\n vWaveHeight = displaced.y;\n vDepth = aDepth;\n vec4 worldPosition = modelMatrix * vec4(displaced, 1.0);\n vWorldPosition = worldPosition.xyz;\n gl_Position = projectionMatrix * viewMatrix * worldPosition;\n}\n";
|
|
2
|
+
export declare const oceanFragmentShader = "\nuniform float uTime;\nuniform vec3 uShallowColor;\nuniform vec3 uDeepColor;\nuniform vec3 uCrestColor;\nuniform vec3 uFoamColor;\nuniform float uOpacity;\nuniform float uFresnelStrength;\nuniform float uHorizonBlend;\nuniform float uFoamIntensity;\nuniform float uDepthRange;\nuniform float uShoreFoamWidth;\nuniform float uSparkle;\nuniform vec3 uSunDirection;\n\nvarying vec3 vWorldPosition;\nvarying vec3 vNormal;\nvarying float vCrest;\nvarying float vWaveHeight;\nvarying float vDepth;\nvarying vec2 vOceanUv;\n\nfloat jgWaterHash(vec2 p) {\n uvec2 q = uvec2(ivec2(floor(p)));\n uint x = q.x * 1664525u + q.y * 1013904223u;\n x = (x ^ (x >> 16u)) * 2246822519u;\n x = (x ^ (x >> 13u)) * 3266489917u;\n return float(x ^ (x >> 16u)) * (1.0 / 4294967296.0);\n}\n\nfloat jgWaterNoise(vec2 p) {\n vec2 i = floor(p);\n vec2 f = fract(p);\n vec2 u = f * f * (3.0 - 2.0 * f);\n float a = jgWaterHash(i);\n float b = jgWaterHash(i + vec2(1.0, 0.0));\n float c = jgWaterHash(i + vec2(0.0, 1.0));\n float d = jgWaterHash(i + vec2(1.0, 1.0));\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nvoid main() {\n vec3 viewDirection = normalize(cameraPosition - vWorldPosition);\n\n // Fine scrolling normal perturbation: two counter-drifting noise layers ripple the surface\n // between the big Gerstner swells.\n vec2 wp = vWorldPosition.xz;\n float ripple1 = jgWaterNoise(wp * 1.7 + vec2(uTime * 0.22, uTime * 0.15));\n float ripple2 = jgWaterNoise(wp * 3.1 - vec2(uTime * 0.17, uTime * 0.26));\n vec3 normal = normalize(normalize(vNormal) + vec3(ripple1 - 0.5, 0.0, ripple2 - 0.5) * 0.14);\n\n // Depth-based body color: warm shallows at the shore ramp falling to the deep tone.\n float depthMix = smoothstep(0.0, max(uDepthRange, 0.001), vDepth);\n vec3 waterColor = mix(uShallowColor, uDeepColor, depthMix);\n waterColor = mix(waterColor, uCrestColor, smoothstep(0.15, 1.2, vWaveHeight) * 0.22);\n\n // Restrained fresnel: a soft sky tint at grazing angles, never a white-out.\n float fresnel = pow(1.0 - max(dot(normal, viewDirection), 0.0), 5.0);\n waterColor = mix(waterColor, vec3(0.62, 0.78, 0.85), min(fresnel * uFresnelStrength, 0.45));\n float horizon = smoothstep(0.0, 1.0, 1.0 - abs(normal.y));\n waterColor = mix(waterColor, uShallowColor, horizon * uHorizonBlend * 0.6);\n\n // Sun glints: sparkle where the perturbed normal mirrors the sun, gated by noise so it\n // twinkles in cells instead of sheening uniformly.\n vec3 halfway = normalize(viewDirection + normalize(uSunDirection));\n float glint = pow(max(dot(normal, halfway), 0.0), 150.0);\n float glintMask = smoothstep(0.68, 0.92, jgWaterNoise(wp * 2.3 + vec2(uTime * 0.4, -uTime * 0.31)));\n waterColor += vec3(1.0, 0.98, 0.9) * glint * glintMask * uSparkle;\n\n // Foam: a noise-eaten shoreline lick plus crest foam. A second, larger noise gates whole\n // stretches of shoreline so foam laps in patches instead of outlining the bank uniformly.\n float foamNoise = jgWaterNoise(wp * 2.6 + vec2(uTime * 0.28, uTime * 0.2));\n float foamStretch = smoothstep(0.3, 0.8, jgWaterNoise(wp * 0.33 + vec2(3.7, 9.2) + vec2(uTime * 0.05, 0.0)));\n float shoreFoam = (1.0 - smoothstep(0.0, max(uShoreFoamWidth, 0.001), vDepth - foamNoise * uShoreFoamWidth * 0.9))\n * smoothstep(0.25, 0.75, foamNoise) * (0.2 + 0.6 * foamStretch);\n float crestFoam = clamp(vCrest * uFoamIntensity, 0.0, 1.0) * smoothstep(0.35, 0.75, foamNoise);\n float foam = clamp(shoreFoam + crestFoam, 0.0, 1.0);\n vec3 color = mix(waterColor, uFoamColor, foam);\n\n // Soft shore: the sheet turns glass-clear over the last stretch of shoaling bed, so the\n // waterline is a blend into the terrain, not a hard tarp edge.\n float shoreAlpha = smoothstep(0.0, 0.55, vDepth);\n float alpha = uOpacity * mix(0.35, 1.0, shoreAlpha) + foam * 0.3;\n // The sheet's own border always dissolves \u2014 a bounded water plane must never end in a\n // visible straight cut, whatever the bed underneath is doing.\n float borderDist = min(min(vOceanUv.x, 1.0 - vOceanUv.x), min(vOceanUv.y, 1.0 - vOceanUv.y));\n alpha *= smoothstep(0.0, 0.045, borderDist);\n gl_FragColor = vec4(color, clamp(alpha, 0.0, 1.0));\n}\n";
|
|
@@ -7,22 +7,29 @@ uniform float uFoamThreshold;
|
|
|
7
7
|
uniform float uFoamSoftness;
|
|
8
8
|
uniform float uFoamCoverage;
|
|
9
9
|
|
|
10
|
+
attribute float aDepth;
|
|
11
|
+
|
|
10
12
|
varying vec3 vWorldPosition;
|
|
11
13
|
varying vec3 vNormal;
|
|
12
14
|
varying float vCrest;
|
|
13
15
|
varying float vWaveHeight;
|
|
16
|
+
varying float vDepth;
|
|
17
|
+
varying vec2 vOceanUv;
|
|
14
18
|
|
|
15
19
|
void main() {
|
|
20
|
+
vOceanUv = uv;
|
|
16
21
|
vec3 displaced = position;
|
|
17
22
|
vec3 tangent = vec3(1.0, 0.0, 0.0);
|
|
18
23
|
vec3 bitangent = vec3(0.0, 0.0, 1.0);
|
|
19
24
|
float crest = 0.0;
|
|
25
|
+
// Waves flatten as the bed shoals so swells die at the shoreline instead of clipping it.
|
|
26
|
+
float depthDamp = smoothstep(0.05, 1.4, aDepth);
|
|
20
27
|
|
|
21
28
|
for (int i = 0; i < 6; i++) {
|
|
22
29
|
vec2 direction = normalize(uWaveDirections[i]);
|
|
23
30
|
vec4 wave = uWaveParams[i];
|
|
24
31
|
float k = wave.x;
|
|
25
|
-
float amplitude = wave.y;
|
|
32
|
+
float amplitude = wave.y * depthDamp;
|
|
26
33
|
float steepness = wave.z;
|
|
27
34
|
float omega = wave.w;
|
|
28
35
|
float phase = k * dot(direction, position.xz) - omega * uTime;
|
|
@@ -41,12 +48,14 @@ void main() {
|
|
|
41
48
|
vNormal = normalize(cross(bitangent, tangent));
|
|
42
49
|
vCrest = crest;
|
|
43
50
|
vWaveHeight = displaced.y;
|
|
51
|
+
vDepth = aDepth;
|
|
44
52
|
vec4 worldPosition = modelMatrix * vec4(displaced, 1.0);
|
|
45
53
|
vWorldPosition = worldPosition.xyz;
|
|
46
54
|
gl_Position = projectionMatrix * viewMatrix * worldPosition;
|
|
47
55
|
}
|
|
48
56
|
`;
|
|
49
57
|
export const oceanFragmentShader = `
|
|
58
|
+
uniform float uTime;
|
|
50
59
|
uniform vec3 uShallowColor;
|
|
51
60
|
uniform vec3 uDeepColor;
|
|
52
61
|
uniform vec3 uCrestColor;
|
|
@@ -55,24 +64,83 @@ uniform float uOpacity;
|
|
|
55
64
|
uniform float uFresnelStrength;
|
|
56
65
|
uniform float uHorizonBlend;
|
|
57
66
|
uniform float uFoamIntensity;
|
|
67
|
+
uniform float uDepthRange;
|
|
68
|
+
uniform float uShoreFoamWidth;
|
|
69
|
+
uniform float uSparkle;
|
|
70
|
+
uniform vec3 uSunDirection;
|
|
58
71
|
|
|
59
72
|
varying vec3 vWorldPosition;
|
|
60
73
|
varying vec3 vNormal;
|
|
61
74
|
varying float vCrest;
|
|
62
75
|
varying float vWaveHeight;
|
|
76
|
+
varying float vDepth;
|
|
77
|
+
varying vec2 vOceanUv;
|
|
78
|
+
|
|
79
|
+
float jgWaterHash(vec2 p) {
|
|
80
|
+
uvec2 q = uvec2(ivec2(floor(p)));
|
|
81
|
+
uint x = q.x * 1664525u + q.y * 1013904223u;
|
|
82
|
+
x = (x ^ (x >> 16u)) * 2246822519u;
|
|
83
|
+
x = (x ^ (x >> 13u)) * 3266489917u;
|
|
84
|
+
return float(x ^ (x >> 16u)) * (1.0 / 4294967296.0);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
float jgWaterNoise(vec2 p) {
|
|
88
|
+
vec2 i = floor(p);
|
|
89
|
+
vec2 f = fract(p);
|
|
90
|
+
vec2 u = f * f * (3.0 - 2.0 * f);
|
|
91
|
+
float a = jgWaterHash(i);
|
|
92
|
+
float b = jgWaterHash(i + vec2(1.0, 0.0));
|
|
93
|
+
float c = jgWaterHash(i + vec2(0.0, 1.0));
|
|
94
|
+
float d = jgWaterHash(i + vec2(1.0, 1.0));
|
|
95
|
+
return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
|
|
96
|
+
}
|
|
63
97
|
|
|
64
98
|
void main() {
|
|
65
|
-
vec3 normal = normalize(vNormal);
|
|
66
99
|
vec3 viewDirection = normalize(cameraPosition - vWorldPosition);
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
100
|
+
|
|
101
|
+
// Fine scrolling normal perturbation: two counter-drifting noise layers ripple the surface
|
|
102
|
+
// between the big Gerstner swells.
|
|
103
|
+
vec2 wp = vWorldPosition.xz;
|
|
104
|
+
float ripple1 = jgWaterNoise(wp * 1.7 + vec2(uTime * 0.22, uTime * 0.15));
|
|
105
|
+
float ripple2 = jgWaterNoise(wp * 3.1 - vec2(uTime * 0.17, uTime * 0.26));
|
|
106
|
+
vec3 normal = normalize(normalize(vNormal) + vec3(ripple1 - 0.5, 0.0, ripple2 - 0.5) * 0.14);
|
|
107
|
+
|
|
108
|
+
// Depth-based body color: warm shallows at the shore ramp falling to the deep tone.
|
|
109
|
+
float depthMix = smoothstep(0.0, max(uDepthRange, 0.001), vDepth);
|
|
110
|
+
vec3 waterColor = mix(uShallowColor, uDeepColor, depthMix);
|
|
111
|
+
waterColor = mix(waterColor, uCrestColor, smoothstep(0.15, 1.2, vWaveHeight) * 0.22);
|
|
112
|
+
|
|
113
|
+
// Restrained fresnel: a soft sky tint at grazing angles, never a white-out.
|
|
114
|
+
float fresnel = pow(1.0 - max(dot(normal, viewDirection), 0.0), 5.0);
|
|
115
|
+
waterColor = mix(waterColor, vec3(0.62, 0.78, 0.85), min(fresnel * uFresnelStrength, 0.45));
|
|
72
116
|
float horizon = smoothstep(0.0, 1.0, 1.0 - abs(normal.y));
|
|
73
|
-
waterColor = mix(waterColor, uShallowColor, horizon * uHorizonBlend);
|
|
74
|
-
|
|
117
|
+
waterColor = mix(waterColor, uShallowColor, horizon * uHorizonBlend * 0.6);
|
|
118
|
+
|
|
119
|
+
// Sun glints: sparkle where the perturbed normal mirrors the sun, gated by noise so it
|
|
120
|
+
// twinkles in cells instead of sheening uniformly.
|
|
121
|
+
vec3 halfway = normalize(viewDirection + normalize(uSunDirection));
|
|
122
|
+
float glint = pow(max(dot(normal, halfway), 0.0), 150.0);
|
|
123
|
+
float glintMask = smoothstep(0.68, 0.92, jgWaterNoise(wp * 2.3 + vec2(uTime * 0.4, -uTime * 0.31)));
|
|
124
|
+
waterColor += vec3(1.0, 0.98, 0.9) * glint * glintMask * uSparkle;
|
|
125
|
+
|
|
126
|
+
// Foam: a noise-eaten shoreline lick plus crest foam. A second, larger noise gates whole
|
|
127
|
+
// stretches of shoreline so foam laps in patches instead of outlining the bank uniformly.
|
|
128
|
+
float foamNoise = jgWaterNoise(wp * 2.6 + vec2(uTime * 0.28, uTime * 0.2));
|
|
129
|
+
float foamStretch = smoothstep(0.3, 0.8, jgWaterNoise(wp * 0.33 + vec2(3.7, 9.2) + vec2(uTime * 0.05, 0.0)));
|
|
130
|
+
float shoreFoam = (1.0 - smoothstep(0.0, max(uShoreFoamWidth, 0.001), vDepth - foamNoise * uShoreFoamWidth * 0.9))
|
|
131
|
+
* smoothstep(0.25, 0.75, foamNoise) * (0.2 + 0.6 * foamStretch);
|
|
132
|
+
float crestFoam = clamp(vCrest * uFoamIntensity, 0.0, 1.0) * smoothstep(0.35, 0.75, foamNoise);
|
|
133
|
+
float foam = clamp(shoreFoam + crestFoam, 0.0, 1.0);
|
|
75
134
|
vec3 color = mix(waterColor, uFoamColor, foam);
|
|
76
|
-
|
|
135
|
+
|
|
136
|
+
// Soft shore: the sheet turns glass-clear over the last stretch of shoaling bed, so the
|
|
137
|
+
// waterline is a blend into the terrain, not a hard tarp edge.
|
|
138
|
+
float shoreAlpha = smoothstep(0.0, 0.55, vDepth);
|
|
139
|
+
float alpha = uOpacity * mix(0.35, 1.0, shoreAlpha) + foam * 0.3;
|
|
140
|
+
// The sheet's own border always dissolves — a bounded water plane must never end in a
|
|
141
|
+
// visible straight cut, whatever the bed underneath is doing.
|
|
142
|
+
float borderDist = min(min(vOceanUv.x, 1.0 - vOceanUv.x), min(vOceanUv.y, 1.0 - vOceanUv.y));
|
|
143
|
+
alpha *= smoothstep(0.0, 0.045, borderDist);
|
|
144
|
+
gl_FragColor = vec4(color, clamp(alpha, 0.0, 1.0));
|
|
77
145
|
}
|
|
78
146
|
`;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as THREE from "three";
|
|
2
2
|
import { createWeatherSeedAttributes } from "./weatherMath.js";
|
|
3
|
+
/** @internal */
|
|
3
4
|
export function createWeatherQuadGeometry(maxCount, seed) {
|
|
4
5
|
const count = Math.max(0, Math.floor(maxCount));
|
|
5
6
|
const geometry = new THREE.InstancedBufferGeometry();
|
|
@@ -2,10 +2,13 @@ export declare const DEFAULT_RAIN_COUNT = 2000;
|
|
|
2
2
|
export declare const DEFAULT_SNOW_COUNT = 1500;
|
|
3
3
|
export declare const DEFAULT_RAIN_DENSITY = 0.45;
|
|
4
4
|
export declare const DEFAULT_SNOW_DENSITY = 0.5;
|
|
5
|
+
/** @internal */
|
|
5
6
|
export declare function clampWeatherRatio(value: number): number;
|
|
7
|
+
/** @internal */
|
|
6
8
|
export declare function resolveWeatherInstanceCount(maxCount: number, density: number, budget?: number): number;
|
|
7
9
|
export interface WeatherSeedAttributes {
|
|
8
10
|
spawn: Float32Array;
|
|
9
11
|
drift: Float32Array;
|
|
10
12
|
}
|
|
13
|
+
/** @internal */
|
|
11
14
|
export declare function createWeatherSeedAttributes(maxCount: number, seed: number): WeatherSeedAttributes;
|
|
@@ -2,6 +2,7 @@ export const DEFAULT_RAIN_COUNT = 2000;
|
|
|
2
2
|
export const DEFAULT_SNOW_COUNT = 1500;
|
|
3
3
|
export const DEFAULT_RAIN_DENSITY = 0.45;
|
|
4
4
|
export const DEFAULT_SNOW_DENSITY = 0.5;
|
|
5
|
+
/** @internal */
|
|
5
6
|
export function clampWeatherRatio(value) {
|
|
6
7
|
if (!Number.isFinite(value))
|
|
7
8
|
return 0;
|
|
@@ -11,11 +12,13 @@ export function clampWeatherRatio(value) {
|
|
|
11
12
|
return 1;
|
|
12
13
|
return value;
|
|
13
14
|
}
|
|
15
|
+
/** @internal */
|
|
14
16
|
export function resolveWeatherInstanceCount(maxCount, density, budget) {
|
|
15
17
|
const safeMax = Math.max(0, Math.floor(maxCount));
|
|
16
18
|
const capped = budget === undefined ? safeMax : Math.min(safeMax, Math.max(0, Math.floor(budget)));
|
|
17
19
|
return Math.floor(capped * clampWeatherRatio(density));
|
|
18
20
|
}
|
|
21
|
+
/** @internal */
|
|
19
22
|
export function createWeatherSeedAttributes(maxCount, seed) {
|
|
20
23
|
const count = Math.max(0, Math.floor(maxCount));
|
|
21
24
|
const spawn = new Float32Array(count * 3);
|
|
@@ -11,8 +11,11 @@ export interface WeatherUniformOptions {
|
|
|
11
11
|
lightning?: number;
|
|
12
12
|
timeScale?: number;
|
|
13
13
|
}
|
|
14
|
+
/** @internal */
|
|
14
15
|
export declare function createWeatherUniformSet(options?: WeatherUniformOptions): WeatherUniformSet;
|
|
16
|
+
/** @internal */
|
|
15
17
|
export declare function WeatherUniformProvider({ children, ...options }: WeatherUniformOptions & {
|
|
16
18
|
children: ReactNode;
|
|
17
19
|
}): import("react").JSX.Element;
|
|
20
|
+
/** @internal */
|
|
18
21
|
export declare function useWeatherUniformSet(options?: WeatherUniformOptions): WeatherUniformSet;
|
|
@@ -3,6 +3,7 @@ import { createContext, useContext, useMemo } from "react";
|
|
|
3
3
|
import { useFrame } from "@react-three/fiber";
|
|
4
4
|
import * as THREE from "three";
|
|
5
5
|
const WeatherUniformContext = createContext(null);
|
|
6
|
+
/** @internal */
|
|
6
7
|
export function createWeatherUniformSet(options = {}) {
|
|
7
8
|
const wind = options.wind ?? [0, 0, 0];
|
|
8
9
|
return {
|
|
@@ -21,11 +22,13 @@ function useUniformTicker(uniforms, options, enabled = true) {
|
|
|
21
22
|
uniforms.lightning.value = options.lightning ?? 0;
|
|
22
23
|
});
|
|
23
24
|
}
|
|
25
|
+
/** @internal */
|
|
24
26
|
export function WeatherUniformProvider({ children, ...options }) {
|
|
25
27
|
const uniforms = useMemo(() => createWeatherUniformSet(options), []);
|
|
26
28
|
useUniformTicker(uniforms, options);
|
|
27
29
|
return _jsx(WeatherUniformContext.Provider, { value: uniforms, children: children });
|
|
28
30
|
}
|
|
31
|
+
/** @internal */
|
|
29
32
|
export function useWeatherUniformSet(options = {}) {
|
|
30
33
|
const shared = useContext(WeatherUniformContext);
|
|
31
34
|
const local = useMemo(() => createWeatherUniformSet(options), []);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { PlaceWorldFeature } from "@jgengine/core/world/place";
|
|
2
|
+
/**
|
|
3
|
+
* The substrate of a place world, rendered plainly: a flat slab or a round planet under the engine
|
|
4
|
+
* default sky. This is deliberately not a diorama — sky look, foliage, props, and sculpt are scene
|
|
5
|
+
* content the editor authors; `voxel` grounds are realized by the game's generation systems and
|
|
6
|
+
* `board` grounds by the game's own 2D face, so both mount nothing here.
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
export declare function PlaceScene({ feature }: {
|
|
10
|
+
feature: PlaceWorldFeature;
|
|
11
|
+
}): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
import { DefaultSurface } from "../render/defaultSurface.js";
|
|
4
|
+
/** Render extent (m) standing in for an `Infinity` flat-ground axis — far past the default fog. */
|
|
5
|
+
const UNBOUNDED_RENDER_EXTENT = 2048;
|
|
6
|
+
const GROUND_COLOR = "#5c6b52";
|
|
7
|
+
function clampExtent(value) {
|
|
8
|
+
return Number.isFinite(value) ? value : UNBOUNDED_RENDER_EXTENT;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The substrate of a place world, rendered plainly: a flat slab or a round planet under the engine
|
|
12
|
+
* default sky. This is deliberately not a diorama — sky look, foliage, props, and sculpt are scene
|
|
13
|
+
* content the editor authors; `voxel` grounds are realized by the game's generation systems and
|
|
14
|
+
* `board` grounds by the game's own 2D face, so both mount nothing here.
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
export function PlaceScene({ feature }) {
|
|
18
|
+
const ground = feature.ground;
|
|
19
|
+
const size = useMemo(() => ground.mode === "flat" ? [clampExtent(ground.size.x), clampExtent(ground.size.z)] : undefined, [ground]);
|
|
20
|
+
if (ground.mode === "flat" && size !== undefined) {
|
|
21
|
+
return (_jsxs("mesh", { "rotation-x": -Math.PI / 2, receiveShadow: true, children: [_jsx("planeGeometry", { args: [size[0], size[1]] }), _jsx(DefaultSurface, { color: GROUND_COLOR, roughness: 0.95 })] }));
|
|
22
|
+
}
|
|
23
|
+
if (ground.mode === "round") {
|
|
24
|
+
return (_jsxs("mesh", { position: [0, -ground.size.radius, 0], receiveShadow: true, children: [_jsx("sphereGeometry", { args: [ground.size.radius, 64, 48] }), _jsx(DefaultSurface, { color: GROUND_COLOR, roughness: 0.95 })] }));
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renders retained VFX instances (`combat.vfxInstance` ops from {@link GameContext}'s `scene.entity.vfxInstance`
|
|
3
|
+
* store) as long-lived beams whose endpoints follow live entity poses. Auto-mounted by the player shell alongside
|
|
4
|
+
* the one-shot {@link WorldSpellVfx} overlay. `upsert`/`update` mutate render resources imperatively (no per-frame
|
|
5
|
+
* React allocation); only create/stop change the mounted set, and `stop` fades out over its requested duration.
|
|
6
|
+
* Beam is the first shipped retained renderer; other kinds are ignored here for a future renderer to claim.
|
|
7
|
+
*
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
export declare function RetainedVfx(): import("react").JSX.Element;
|
|
@@ -0,0 +1,136 @@
|
|
|
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, useRef, useState } from "react";
|
|
4
|
+
import * as THREE from "three";
|
|
5
|
+
import { useGameContext } from "@jgengine/react/provider";
|
|
6
|
+
const UP = new THREE.Vector3(0, 1, 0);
|
|
7
|
+
/**
|
|
8
|
+
* Resolve an endpoint into `out`. A world point is used literally; an entity id resolves to its live pose lifted
|
|
9
|
+
* by `lift` (so a beam meets an entity near chest height, not its feet). Returns false when an entity ref is
|
|
10
|
+
* missing, letting the caller hide the effect until the target exists again.
|
|
11
|
+
*/
|
|
12
|
+
function resolveRef(ctx, ref, lift, out) {
|
|
13
|
+
if (ref === undefined)
|
|
14
|
+
return false;
|
|
15
|
+
if (typeof ref === "string") {
|
|
16
|
+
const entity = ctx.scene.entity.get(ref);
|
|
17
|
+
if (entity === null)
|
|
18
|
+
return false;
|
|
19
|
+
out.set(entity.position[0], entity.position[1] + lift, entity.position[2]);
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
out.set(ref[0], ref[1], ref[2]);
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* One retained beam mesh. It reads its authoritative state from the shared live map every frame and resolves
|
|
27
|
+
* `from`/`to` refs against the entity store, so an endpoint bound to a moving entity follows it with no per-frame
|
|
28
|
+
* command traffic or React re-render — parameter updates mutate the render resources imperatively.
|
|
29
|
+
*/
|
|
30
|
+
function RetainedBeam({ id, live, onFadeComplete, }) {
|
|
31
|
+
const ctx = useGameContext();
|
|
32
|
+
const meshRef = useRef(null);
|
|
33
|
+
const matRef = useRef(null);
|
|
34
|
+
const from = useRef(new THREE.Vector3());
|
|
35
|
+
const to = useRef(new THREE.Vector3());
|
|
36
|
+
const delta = useRef(new THREE.Vector3());
|
|
37
|
+
const quat = useRef(new THREE.Quaternion());
|
|
38
|
+
useFrame(() => {
|
|
39
|
+
const entry = live.get(id);
|
|
40
|
+
const mesh = meshRef.current;
|
|
41
|
+
const mat = matRef.current;
|
|
42
|
+
if (entry === undefined || mesh === null || mat === null)
|
|
43
|
+
return;
|
|
44
|
+
const lift = entry.state.params?.lift ?? 1;
|
|
45
|
+
const hasFrom = resolveRef(ctx, entry.state.from, lift, from.current);
|
|
46
|
+
const hasTo = resolveRef(ctx, entry.state.to, lift, to.current);
|
|
47
|
+
if (!hasFrom || !hasTo) {
|
|
48
|
+
mesh.visible = false;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
delta.current.subVectors(to.current, from.current);
|
|
52
|
+
const length = Math.max(0.001, delta.current.length());
|
|
53
|
+
mesh.visible = true;
|
|
54
|
+
mesh.position.set((from.current.x + to.current.x) / 2, (from.current.y + to.current.y) / 2, (from.current.z + to.current.z) / 2);
|
|
55
|
+
quat.current.setFromUnitVectors(UP, delta.current.normalize());
|
|
56
|
+
mesh.quaternion.copy(quat.current);
|
|
57
|
+
const thickness = entry.state.radius ?? 0.12;
|
|
58
|
+
mesh.scale.set(thickness, length, thickness);
|
|
59
|
+
mat.color.set(entry.state.color);
|
|
60
|
+
const baseOpacity = entry.state.params?.opacity ?? 0.85;
|
|
61
|
+
if (entry.fade === null) {
|
|
62
|
+
mat.opacity = baseOpacity;
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
const p = (performance.now() - entry.fade.startedAt) / Math.max(1, entry.fade.ms);
|
|
66
|
+
if (p >= 1) {
|
|
67
|
+
onFadeComplete(id);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
mat.opacity = baseOpacity * (1 - p);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
return (_jsxs("mesh", { ref: meshRef, renderOrder: 998, visible: false, children: [_jsx("cylinderGeometry", { args: [1, 1, 1, 8, 1, true] }), _jsx("meshBasicMaterial", { ref: matRef, transparent: true, side: THREE.DoubleSide, depthWrite: false, blending: THREE.AdditiveBlending, toneMapped: false, opacity: 0 })] }));
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Renders retained VFX instances (`combat.vfxInstance` ops from {@link GameContext}'s `scene.entity.vfxInstance`
|
|
77
|
+
* store) as long-lived beams whose endpoints follow live entity poses. Auto-mounted by the player shell alongside
|
|
78
|
+
* the one-shot {@link WorldSpellVfx} overlay. `upsert`/`update` mutate render resources imperatively (no per-frame
|
|
79
|
+
* React allocation); only create/stop change the mounted set, and `stop` fades out over its requested duration.
|
|
80
|
+
* Beam is the first shipped retained renderer; other kinds are ignored here for a future renderer to claim.
|
|
81
|
+
*
|
|
82
|
+
* @internal
|
|
83
|
+
*/
|
|
84
|
+
export function RetainedVfx() {
|
|
85
|
+
const ctx = useGameContext();
|
|
86
|
+
const live = useRef(new Map()).current;
|
|
87
|
+
const [ids, setIds] = useState([]);
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
// Seed from any instances created before this overlay mounted.
|
|
90
|
+
for (const state of ctx.scene.entity.vfxInstance.list()) {
|
|
91
|
+
if (state.kind !== "beam")
|
|
92
|
+
continue;
|
|
93
|
+
live.set(state.id, { state, fade: null });
|
|
94
|
+
}
|
|
95
|
+
if (live.size > 0)
|
|
96
|
+
setIds([...live.keys()]);
|
|
97
|
+
const off = ctx.game.events.on("combat.vfxInstance", (op) => {
|
|
98
|
+
if (op.op === "stop") {
|
|
99
|
+
const entry = live.get(op.id);
|
|
100
|
+
if (entry === undefined)
|
|
101
|
+
return;
|
|
102
|
+
if (op.fadeMs !== undefined && op.fadeMs > 0) {
|
|
103
|
+
entry.fade = { ms: op.fadeMs, startedAt: performance.now() };
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
live.delete(op.id);
|
|
107
|
+
setIds([...live.keys()]);
|
|
108
|
+
}
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const state = op.instance;
|
|
112
|
+
if (state === undefined || state.kind !== "beam")
|
|
113
|
+
return;
|
|
114
|
+
const existing = live.get(op.id);
|
|
115
|
+
if (existing === undefined) {
|
|
116
|
+
live.set(op.id, { state, fade: null });
|
|
117
|
+
setIds([...live.keys()]);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
// Update: mutate state in place; RetainedBeam reads it next frame (no React re-render).
|
|
121
|
+
existing.state = state;
|
|
122
|
+
existing.fade = null;
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
return () => {
|
|
126
|
+
off();
|
|
127
|
+
live.clear();
|
|
128
|
+
setIds([]);
|
|
129
|
+
};
|
|
130
|
+
}, [ctx, live]);
|
|
131
|
+
function removeId(id) {
|
|
132
|
+
live.delete(id);
|
|
133
|
+
setIds((current) => current.filter((entry) => entry !== id));
|
|
134
|
+
}
|
|
135
|
+
return (_jsx(_Fragment, { children: ids.map((id) => (_jsx(RetainedBeam, { id: id, live: live, onFadeComplete: removeId }, id))) }));
|
|
136
|
+
}
|
package/dist/world/WorldHud.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
1
2
|
import type { SceneEntity } from "@jgengine/core/scene/entityStore";
|
|
2
3
|
import type { CatalogEntityRole } from "@jgengine/core/runtime/gameContext";
|
|
3
|
-
|
|
4
|
-
export {
|
|
4
|
+
import { type NameplateSample } from "./worldBarSamples.js";
|
|
5
|
+
export type { NameplateSample, WorldBarSample } from "./worldBarSamples.js";
|
|
6
|
+
export { collectNameplateSamples, collectWorldBarSamples, paintWorldBarSamples } from "./worldBarSamples.js";
|
|
5
7
|
export { telegraphPulseOpacity } from "./telegraphPulse.js";
|
|
6
8
|
export declare function WorldEntityBars({ statId, height, roles, resolveRole, maxDistance, }: {
|
|
7
9
|
statId: string;
|
|
@@ -11,6 +13,51 @@ export declare function WorldEntityBars({ statId, height, roles, resolveRole, ma
|
|
|
11
13
|
/** Hide bars for entities farther than this from the player (world units). Default 60. */
|
|
12
14
|
maxDistance?: number;
|
|
13
15
|
}): import("react").JSX.Element;
|
|
16
|
+
/** Props for `WorldNameplates` — entity filter, refresh rate, and headless className/render hooks. */
|
|
17
|
+
export interface WorldNameplatesProps {
|
|
18
|
+
statId?: string;
|
|
19
|
+
height?: number;
|
|
20
|
+
roles?: readonly CatalogEntityRole[];
|
|
21
|
+
resolveRole?: (entity: SceneEntity) => CatalogEntityRole | undefined;
|
|
22
|
+
/** Hide nameplates for entities farther than this from the player (world units). Default 40. */
|
|
23
|
+
maxDistance?: number;
|
|
24
|
+
/** Minimum ms between position/health refreshes — trades smoothness for fewer re-renders at scale. Default 120. */
|
|
25
|
+
tickMs?: number;
|
|
26
|
+
className?: string;
|
|
27
|
+
nameplateClassName?: string;
|
|
28
|
+
nameClassName?: string;
|
|
29
|
+
barClassName?: string;
|
|
30
|
+
fillClassName?: string;
|
|
31
|
+
/** Full override for one nameplate's markup; receives the raw sample (name, percent, screen x/y). */
|
|
32
|
+
renderNameplate?: (sample: NameplateSample) => ReactNode;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Billboarded name + 78×6px HP bar over every nearby non-local entity that
|
|
36
|
+
* passes `roles`/`maxDistance` — headless (className/data-* slots on every
|
|
37
|
+
* part, `renderNameplate` for a full swap), turned on declaratively via
|
|
38
|
+
* `defineGame({ nameplates })` rather than mounted by hand.
|
|
39
|
+
*
|
|
40
|
+
* @capability nameplates billboarded name + HP bar over nearby entities
|
|
41
|
+
*/
|
|
42
|
+
export declare function WorldNameplates({ statId, height, roles, resolveRole, maxDistance, tickMs, className, nameplateClassName, nameClassName, barClassName, fillClassName, renderNameplate, }: WorldNameplatesProps): import("react").JSX.Element;
|
|
43
|
+
/** Props for {@link WorldObjectHighlights}. */
|
|
44
|
+
export interface WorldObjectHighlightsProps {
|
|
45
|
+
/** Ring tint. Default a construction-select amber. */
|
|
46
|
+
color?: string;
|
|
47
|
+
/** Fixed ring radius; omitted derives one from the object's catalog `halfExtents` (falls back to 1.2). */
|
|
48
|
+
radius?: number;
|
|
49
|
+
/** Ground offset so the ring doesn't z-fight the object's base. Default 0.05. */
|
|
50
|
+
y?: number;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Ground ring over every `ctx.scene.object.selection`-ed placed object — the object-layer counterpart
|
|
54
|
+
* to `WorldEntityBars`/`WorldNameplates`. Mount it once in the game's scene (headless: no defaults are
|
|
55
|
+
* imposed beyond a visible ring) instead of hand-rolling a selection highlight through `WorldOverlay`
|
|
56
|
+
* against external state.
|
|
57
|
+
*
|
|
58
|
+
* @capability world-object-highlights ground-ring highlight over every selected placed object
|
|
59
|
+
*/
|
|
60
|
+
export declare function WorldObjectHighlights({ color, radius, y }: WorldObjectHighlightsProps): import("react").JSX.Element;
|
|
14
61
|
export declare function WorldFloatText({ height, lifeMs }: {
|
|
15
62
|
height?: number;
|
|
16
63
|
lifeMs?: number;
|