@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
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useMemo } from "react";
|
|
3
|
+
import { resolveWaterObject } from "@jgengine/core/world/waterKind";
|
|
4
|
+
import { Ocean } from "../water/Ocean.js";
|
|
5
|
+
import { registerSceneKindRenderer } from "./sceneKindRenderers.js";
|
|
6
|
+
/** Map the editor-authored water params onto the shell Ocean shader config. */
|
|
7
|
+
function toOceanConfig(rules, size, depthRange) {
|
|
8
|
+
const maxSpan = Math.max(size[0], size[1]);
|
|
9
|
+
return {
|
|
10
|
+
size: size[0],
|
|
11
|
+
depth: size[1],
|
|
12
|
+
// ~2 vertices per meter (bounded): the old span/2 gave a 30 m lake a 15×15 grid, which waved
|
|
13
|
+
// like a blanket instead of water.
|
|
14
|
+
resolution: Math.min(220, Math.max(48, Math.round(maxSpan * 2))),
|
|
15
|
+
amplitude: rules.waveHeight,
|
|
16
|
+
speed: rules.waveSpeed,
|
|
17
|
+
timeScale: rules.waveSpeed,
|
|
18
|
+
waveScale: Math.min(300, Math.max(2, 6 / rules.waveScale)),
|
|
19
|
+
color: {
|
|
20
|
+
deep: rules.color,
|
|
21
|
+
shallow: rules.shallowColor,
|
|
22
|
+
opacity: rules.opacity,
|
|
23
|
+
// The editor's reflectivity slider stays restrained: full slider ≈ a soft sky sheen at
|
|
24
|
+
// grazing angles, never the white-out the old fresnel washed the whole sheet with.
|
|
25
|
+
fresnelStrength: rules.reflectivity * 0.9,
|
|
26
|
+
depthRange,
|
|
27
|
+
sparkle: 0.25 + rules.reflectivity * 0.3,
|
|
28
|
+
},
|
|
29
|
+
foam: {
|
|
30
|
+
coverage: Math.min(1, rules.foam / 4),
|
|
31
|
+
intensity: Math.min(1.2, rules.foam / 3),
|
|
32
|
+
shoreWidth: Math.min(1.5, rules.foam * 0.35),
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* One authored water volume → a bounded, animated Ocean sheet at the box top, draped over the live
|
|
38
|
+
* terrain's depth: the carved lake bed (see `lakebedFromWaterVolumes`) gives every vertex a real
|
|
39
|
+
* water-column depth, which drives shallow→deep color, shore transparency/foam, and wave damping.
|
|
40
|
+
*/
|
|
41
|
+
function OneWater({ object, context }) {
|
|
42
|
+
const resolved = useMemo(() => resolveWaterObject(object), [object]);
|
|
43
|
+
const surfaceY = resolved === null ? 0 : resolved.center[1];
|
|
44
|
+
const centerX = resolved === null ? 0 : resolved.center[0];
|
|
45
|
+
const centerZ = resolved === null ? 0 : resolved.center[2];
|
|
46
|
+
const depthAt = useCallback((x, z) => surfaceY - context.field.sampleHeight(centerX + x, centerZ + z), [context.field, surfaceY, centerX, centerZ]);
|
|
47
|
+
const config = useMemo(() => {
|
|
48
|
+
if (resolved === null)
|
|
49
|
+
return null;
|
|
50
|
+
// Full deep color roughly where the carved bed bottoms out (box depth + undercut headroom).
|
|
51
|
+
const boxDepth = Math.max(0.4, resolved.center[1] - (object.center?.y ?? resolved.center[1]) + 0.001) * 2;
|
|
52
|
+
return toOceanConfig(resolved.rules, resolved.size, Math.max(0.8, boxDepth * 0.9));
|
|
53
|
+
}, [resolved, object]);
|
|
54
|
+
if (resolved === null || config === null)
|
|
55
|
+
return null;
|
|
56
|
+
return _jsx(Ocean, { config: config, position: [centerX, surfaceY, centerZ], depthAt: depthAt });
|
|
57
|
+
}
|
|
58
|
+
/** Registers the `water` volume runtime renderer. Called by `registerBuiltinSceneKindRenderers`. @internal */
|
|
59
|
+
export function registerWaterRenderer() {
|
|
60
|
+
registerSceneKindRenderer("water", ({ objects, context }) => (_jsx(_Fragment, { children: objects.map((object) => (_jsx(OneWater, { object: object, context: context }, object.id))) })));
|
|
61
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registers the engine's built-in *environment* scene-kind renderers (the shell half of #809) and
|
|
3
|
+
* ensures the matching core kinds are registered too. Idempotent; `AuthoredScene` calls it at module
|
|
4
|
+
* load so a document with a `water`/`grass_field`/`soil` volume renders without the game wiring
|
|
5
|
+
* anything up. Domain studios (poles, …) register their own renderer via the public
|
|
6
|
+
* `registerSceneKindRenderer` — no edit here.
|
|
7
|
+
*/
|
|
8
|
+
import { registerBuiltinSceneKinds } from "@jgengine/core/scene/builtinSceneKinds";
|
|
9
|
+
import { registerGrassFieldRenderer } from "./GrassFieldRenderer.js";
|
|
10
|
+
import { registerWaterRenderer } from "./WaterRenderer.js";
|
|
11
|
+
import { registerSoilPatchRenderer } from "./SoilPatchRenderer.js";
|
|
12
|
+
import { registerCityRenderer } from "./CityRenderer.js";
|
|
13
|
+
import { registerPoleLineRenderer } from "./PoleLineRenderer.js";
|
|
14
|
+
let registered = false;
|
|
15
|
+
/** Registers the engine's built-in environment scene-kind renderers. Called by `AuthoredScene`. @internal */
|
|
16
|
+
export function registerBuiltinSceneKindRenderers() {
|
|
17
|
+
if (registered)
|
|
18
|
+
return;
|
|
19
|
+
registered = true;
|
|
20
|
+
registerBuiltinSceneKinds();
|
|
21
|
+
registerWaterRenderer();
|
|
22
|
+
registerGrassFieldRenderer();
|
|
23
|
+
registerSoilPatchRenderer();
|
|
24
|
+
registerCityRenderer();
|
|
25
|
+
registerPoleLineRenderer();
|
|
26
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
/**
|
|
3
|
+
* Instanced facade material for city massing walls: a `MeshStandardMaterial` whose fragment stage
|
|
4
|
+
* paints procedural window strips on the side faces of every instanced unit box — floor bands from
|
|
5
|
+
* the district's real `floorHeight`, window columns on a fixed pitch, and a per-cell hash that
|
|
6
|
+
* dims/lights individual windows so no two facades read identical. Ground floors (plus any buried
|
|
7
|
+
* foundation, via the `instanceGroundOffset` attribute) stay solid wall so street level reads as
|
|
8
|
+
* storefront/lobby rather than floating glass. Same `onBeforeCompile` idiom as the grass/terrain
|
|
9
|
+
* materials; instance colors keep tinting the wall exactly like the plain massing material.
|
|
10
|
+
* @internal — created by `CityRenderer` for the banded-piece InstancedMesh.
|
|
11
|
+
*/
|
|
12
|
+
export declare function createCityWindowMaterial(options: {
|
|
13
|
+
floorHeight: number;
|
|
14
|
+
windowColor: string;
|
|
15
|
+
roughness?: number;
|
|
16
|
+
}): THREE.MeshStandardMaterial;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
/**
|
|
3
|
+
* Instanced facade material for city massing walls: a `MeshStandardMaterial` whose fragment stage
|
|
4
|
+
* paints procedural window strips on the side faces of every instanced unit box — floor bands from
|
|
5
|
+
* the district's real `floorHeight`, window columns on a fixed pitch, and a per-cell hash that
|
|
6
|
+
* dims/lights individual windows so no two facades read identical. Ground floors (plus any buried
|
|
7
|
+
* foundation, via the `instanceGroundOffset` attribute) stay solid wall so street level reads as
|
|
8
|
+
* storefront/lobby rather than floating glass. Same `onBeforeCompile` idiom as the grass/terrain
|
|
9
|
+
* materials; instance colors keep tinting the wall exactly like the plain massing material.
|
|
10
|
+
* @internal — created by `CityRenderer` for the banded-piece InstancedMesh.
|
|
11
|
+
*/
|
|
12
|
+
export function createCityWindowMaterial(options) {
|
|
13
|
+
const material = new THREE.MeshStandardMaterial({
|
|
14
|
+
color: "#ffffff",
|
|
15
|
+
roughness: options.roughness ?? 0.82,
|
|
16
|
+
metalness: 0.04,
|
|
17
|
+
});
|
|
18
|
+
const uniforms = {
|
|
19
|
+
uFloorHeight: { value: Math.max(2, options.floorHeight) },
|
|
20
|
+
uWindowColor: { value: new THREE.Color(options.windowColor) },
|
|
21
|
+
};
|
|
22
|
+
material.onBeforeCompile = (shader) => {
|
|
23
|
+
Object.assign(shader.uniforms, uniforms);
|
|
24
|
+
shader.vertexShader = shader.vertexShader
|
|
25
|
+
.replace("#include <common>", `
|
|
26
|
+
#include <common>
|
|
27
|
+
attribute float instanceGroundOffset;
|
|
28
|
+
varying vec3 vCityLocal;
|
|
29
|
+
varying vec3 vCityScale;
|
|
30
|
+
varying vec3 vCityNormal;
|
|
31
|
+
varying float vCityGround;
|
|
32
|
+
`)
|
|
33
|
+
.replace("#include <begin_vertex>", `
|
|
34
|
+
#include <begin_vertex>
|
|
35
|
+
vCityLocal = position;
|
|
36
|
+
vCityScale = vec3(length(instanceMatrix[0].xyz), length(instanceMatrix[1].xyz), length(instanceMatrix[2].xyz));
|
|
37
|
+
vCityNormal = normal;
|
|
38
|
+
vCityGround = instanceGroundOffset;
|
|
39
|
+
`);
|
|
40
|
+
shader.fragmentShader = shader.fragmentShader
|
|
41
|
+
.replace("#include <common>", `
|
|
42
|
+
#include <common>
|
|
43
|
+
uniform float uFloorHeight;
|
|
44
|
+
uniform vec3 uWindowColor;
|
|
45
|
+
varying vec3 vCityLocal;
|
|
46
|
+
varying vec3 vCityScale;
|
|
47
|
+
varying vec3 vCityNormal;
|
|
48
|
+
varying float vCityGround;
|
|
49
|
+
float cityHash(vec2 cell) {
|
|
50
|
+
return fract(sin(dot(cell, vec2(127.1, 311.7))) * 43758.5453);
|
|
51
|
+
}
|
|
52
|
+
`)
|
|
53
|
+
.replace("#include <color_fragment>", `
|
|
54
|
+
#include <color_fragment>
|
|
55
|
+
{
|
|
56
|
+
// Side faces only — roofs and undersides stay plain wall.
|
|
57
|
+
if (abs(vCityNormal.y) < 0.5) {
|
|
58
|
+
float along = abs(vCityNormal.x) > 0.5 ? (vCityLocal.z + 0.5) * vCityScale.z : (vCityLocal.x + 0.5) * vCityScale.x;
|
|
59
|
+
float faceWidth = abs(vCityNormal.x) > 0.5 ? vCityScale.z : vCityScale.x;
|
|
60
|
+
float up = (vCityLocal.y + 0.5) * vCityScale.y - vCityGround;
|
|
61
|
+
float pitch = 2.4;
|
|
62
|
+
float floorT = fract(up / uFloorHeight);
|
|
63
|
+
float colT = fract(along / pitch);
|
|
64
|
+
float floorIndex = floor(up / uFloorHeight);
|
|
65
|
+
float colIndex = floor(along / pitch);
|
|
66
|
+
bool window = up > uFloorHeight * 1.05 && floorT > 0.32 && floorT < 0.78 && colT > 0.2 && colT < 0.8
|
|
67
|
+
&& along > 0.7 && along < faceWidth - 0.7;
|
|
68
|
+
if (window) {
|
|
69
|
+
float cell = cityHash(vec2(floorIndex, colIndex) + vec2(vCityScale.x, vCityScale.z));
|
|
70
|
+
vec3 pane = uWindowColor * mix(0.55, 1.25, cell);
|
|
71
|
+
diffuseColor.rgb = mix(diffuseColor.rgb, pane, 0.88);
|
|
72
|
+
}
|
|
73
|
+
// A thin darker plinth line right above grade grounds the facade.
|
|
74
|
+
if (up > 0.0 && up < 0.35) diffuseColor.rgb *= 0.8;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
`);
|
|
78
|
+
};
|
|
79
|
+
material.customProgramCacheKey = () => `jgengine-city-windows`;
|
|
80
|
+
return material;
|
|
81
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import type { EditorDocument } from "@jgengine/core/editor/index";
|
|
3
|
+
import type { AssetCatalog } from "@jgengine/core/scene/assetCatalog";
|
|
4
|
+
import type { SceneKindObject } from "@jgengine/core/scene/sceneKinds";
|
|
5
|
+
import type { TerrainField } from "@jgengine/core/world/terrain";
|
|
6
|
+
/** Context a scene-kind renderer receives alongside the matching document objects. */
|
|
7
|
+
export interface SceneKindRenderContext {
|
|
8
|
+
document: EditorDocument;
|
|
9
|
+
/** Ground field to drape/ground kind geometry on. */
|
|
10
|
+
field: TerrainField;
|
|
11
|
+
/** Catalog for kinds that instance real GLBs (pole assets, etc.). */
|
|
12
|
+
assets?: AssetCatalog;
|
|
13
|
+
/** Terrain surface color at a world XZ — lets kind geometry (grass roots, soil borders) blend into the ground it sits on. */
|
|
14
|
+
groundColorAt?: (x: number, z: number) => string;
|
|
15
|
+
}
|
|
16
|
+
/** A registered renderer: given every document object of its kind, returns the render tree for them. */
|
|
17
|
+
export type SceneKindRenderer = (props: {
|
|
18
|
+
objects: readonly SceneKindObject[];
|
|
19
|
+
context: SceneKindRenderContext;
|
|
20
|
+
}) => ReactElement | null;
|
|
21
|
+
/**
|
|
22
|
+
* Register the runtime renderer for a scene kind — the shell half of the #809 seam. `AuthoredScene`
|
|
23
|
+
* groups a document's objects by kind and mounts the registered renderer for each, so a new studio
|
|
24
|
+
* renders from the document with no fork of `AuthoredScene`. Idempotent per kind (last wins).
|
|
25
|
+
*/
|
|
26
|
+
export declare function registerSceneKindRenderer(kind: string, renderer: SceneKindRenderer): void;
|
|
27
|
+
/** The renderer registered for a kind, or undefined. @internal — `AuthoredScene` uses it internally. */
|
|
28
|
+
export declare function getSceneKindRenderer(kind: string): SceneKindRenderer | undefined;
|
|
29
|
+
/** Every kind that has a registered renderer. @internal */
|
|
30
|
+
export declare function renderedSceneKinds(): string[];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const renderers = new Map();
|
|
2
|
+
/**
|
|
3
|
+
* Register the runtime renderer for a scene kind — the shell half of the #809 seam. `AuthoredScene`
|
|
4
|
+
* groups a document's objects by kind and mounts the registered renderer for each, so a new studio
|
|
5
|
+
* renders from the document with no fork of `AuthoredScene`. Idempotent per kind (last wins).
|
|
6
|
+
*/
|
|
7
|
+
export function registerSceneKindRenderer(kind, renderer) {
|
|
8
|
+
renderers.set(kind, renderer);
|
|
9
|
+
}
|
|
10
|
+
/** The renderer registered for a kind, or undefined. @internal — `AuthoredScene` uses it internally. */
|
|
11
|
+
export function getSceneKindRenderer(kind) {
|
|
12
|
+
return renderers.get(kind);
|
|
13
|
+
}
|
|
14
|
+
/** Every kind that has a registered renderer. @internal */
|
|
15
|
+
export function renderedSceneKinds() {
|
|
16
|
+
return [...renderers.keys()];
|
|
17
|
+
}
|
package/dist/scene.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./scene/AuthoredScene.js";
|
package/dist/scene.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./scene/AuthoredScene.js";
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import type { AudioBusDef } from "@jgengine/core/audio/audioFalloff";
|
|
2
|
-
import { type SettingsStore } from "@jgengine/core/settings/settingsModel";
|
|
2
|
+
import { type GraphicsQuality, type SettingsStore } from "@jgengine/core/settings/settingsModel";
|
|
3
|
+
import { type TouchStyle } from "@jgengine/core/input/touchScheme";
|
|
3
4
|
import type { AudioEngine } from "../audio/audioEngine.js";
|
|
5
|
+
/** Sentinel Controls value meaning "defer to the game's suggested touch skin". */
|
|
6
|
+
export declare const TOUCH_STYLE_AUTO = "auto";
|
|
7
|
+
/** Shell-internal: binds the SettingsStore touch-style choice to the touch renderer. @internal */
|
|
8
|
+
export declare function useTouchStyle(store: SettingsStore, fallback: TouchStyle): TouchStyle;
|
|
4
9
|
export declare function useSettingsRevision(store: SettingsStore): number;
|
|
5
10
|
export declare function useGraphicsSettings(store: SettingsStore, shadowsDefault: boolean): {
|
|
6
11
|
shadows: boolean;
|
|
7
12
|
dpr: number;
|
|
8
13
|
uiScale: number;
|
|
14
|
+
quality: GraphicsQuality;
|
|
9
15
|
};
|
|
10
16
|
export declare function AudioSettingsBridge({ store, engine, buses, }: {
|
|
11
17
|
store: SettingsStore;
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { useEffect, useReducer } from "react";
|
|
2
2
|
import { busVolumeSettingId, DEFAULT_GRAPHICS_QUALITY, DEFAULT_MASTER_VOLUME, DEFAULT_UI_SCALE, GRAPHICS_QUALITY_DPR, SETTING_IDS, UI_SCALE_MAX, UI_SCALE_MIN, } from "@jgengine/core/settings/settingsModel";
|
|
3
|
+
import { TOUCH_STYLES } from "@jgengine/core/input/touchScheme";
|
|
4
|
+
/** Sentinel Controls value meaning "defer to the game's suggested touch skin". */
|
|
5
|
+
export const TOUCH_STYLE_AUTO = "auto";
|
|
6
|
+
/** Shell-internal: binds the SettingsStore touch-style choice to the touch renderer. @internal */
|
|
7
|
+
export function useTouchStyle(store, fallback) {
|
|
8
|
+
useSettingsRevision(store);
|
|
9
|
+
const raw = store.get(SETTING_IDS.touchStyle, TOUCH_STYLE_AUTO);
|
|
10
|
+
return TOUCH_STYLES.includes(raw) ? raw : fallback;
|
|
11
|
+
}
|
|
3
12
|
export function useSettingsRevision(store) {
|
|
4
13
|
const [rev, bump] = useReducer((n) => n + 1, 0);
|
|
5
14
|
useEffect(() => store.subscribe(() => bump()), [store]);
|
|
@@ -7,12 +16,14 @@ export function useSettingsRevision(store) {
|
|
|
7
16
|
}
|
|
8
17
|
export function useGraphicsSettings(store, shadowsDefault) {
|
|
9
18
|
useSettingsRevision(store);
|
|
10
|
-
const
|
|
19
|
+
const rawQuality = store.get(SETTING_IDS.graphicsQuality, DEFAULT_GRAPHICS_QUALITY);
|
|
20
|
+
const quality = GRAPHICS_QUALITY_DPR[rawQuality] !== undefined ? rawQuality : "high";
|
|
11
21
|
const rawUiScale = store.get(SETTING_IDS.graphicsUiScale, DEFAULT_UI_SCALE);
|
|
12
22
|
return {
|
|
13
23
|
shadows: store.get(SETTING_IDS.graphicsShadows, shadowsDefault),
|
|
14
|
-
dpr: GRAPHICS_QUALITY_DPR[quality]
|
|
24
|
+
dpr: GRAPHICS_QUALITY_DPR[quality],
|
|
15
25
|
uiScale: Math.min(UI_SCALE_MAX, Math.max(UI_SCALE_MIN, rawUiScale)),
|
|
26
|
+
quality,
|
|
16
27
|
};
|
|
17
28
|
}
|
|
18
29
|
export function AudioSettingsBridge({ store, engine, buses, }) {
|
|
@@ -12,6 +12,8 @@ export interface SettingsControllerInput {
|
|
|
12
12
|
hide: readonly SettingCategory[];
|
|
13
13
|
fovEnabled: boolean;
|
|
14
14
|
hideBindings: readonly string[];
|
|
15
|
+
/** Show the touch-controls skin selector in Controls (touch device with a dock). */
|
|
16
|
+
touchStyle: boolean;
|
|
15
17
|
overrides: BindingOverrides;
|
|
16
18
|
rebind: (action: string, code: string) => void;
|
|
17
19
|
resetBinding: (action: string) => void;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { useEffect, useReducer } from "react";
|
|
2
2
|
import { actionLabel, bindingLabel } from "@jgengine/core/input/actionBindings";
|
|
3
3
|
import { BUILT_IN_SETTING_CATEGORIES, busVolumeSettingId, DEFAULT_GRAPHICS_QUALITY, DEFAULT_GRAPHICS_SHADOWS, DEFAULT_MASTER_VOLUME, DEFAULT_UI_SCALE, GRAPHICS_QUALITY_OPTIONS, SETTING_IDS, UI_SCALE_MAX, UI_SCALE_MIN, } from "@jgengine/core/settings/settingsModel";
|
|
4
|
+
import { TOUCH_STYLE_OPTIONS } from "@jgengine/core/input/touchScheme";
|
|
5
|
+
import { TOUCH_STYLE_AUTO } from "./appliedSettings.js";
|
|
4
6
|
import { useSettingsStore, } from "@jgengine/react/settings";
|
|
5
7
|
import { usePlayerFov } from "../camera/PlayerFov.js";
|
|
6
8
|
const DEFAULT_CATEGORY_LABELS = {
|
|
@@ -10,6 +12,10 @@ const DEFAULT_CATEGORY_LABELS = {
|
|
|
10
12
|
controls: "Controls",
|
|
11
13
|
};
|
|
12
14
|
const percent = (value) => `${Math.round(value * 100)}%`;
|
|
15
|
+
const TOUCH_STYLE_SELECT_OPTIONS = [
|
|
16
|
+
{ value: TOUCH_STYLE_AUTO, label: "Auto (game default)" },
|
|
17
|
+
...TOUCH_STYLE_OPTIONS.map((option) => ({ value: option.value, label: option.label })),
|
|
18
|
+
];
|
|
13
19
|
export function useSettingsCategories(config) {
|
|
14
20
|
const store = useSettingsStore();
|
|
15
21
|
const fov = usePlayerFov();
|
|
@@ -119,11 +125,26 @@ export function useSettingsCategories(config) {
|
|
|
119
125
|
reset: () => config.resetBinding(action),
|
|
120
126
|
};
|
|
121
127
|
});
|
|
128
|
+
const controlsRows = [
|
|
129
|
+
...(config.touchStyle
|
|
130
|
+
? [
|
|
131
|
+
{
|
|
132
|
+
id: SETTING_IDS.touchStyle,
|
|
133
|
+
label: "Touch controls",
|
|
134
|
+
kind: "select",
|
|
135
|
+
value: store.get(SETTING_IDS.touchStyle, TOUCH_STYLE_AUTO),
|
|
136
|
+
options: TOUCH_STYLE_SELECT_OPTIONS,
|
|
137
|
+
set: (value) => store.set(SETTING_IDS.touchStyle, value),
|
|
138
|
+
},
|
|
139
|
+
]
|
|
140
|
+
: []),
|
|
141
|
+
...extrasFor("controls"),
|
|
142
|
+
];
|
|
122
143
|
const builtInRows = {
|
|
123
144
|
sound: soundRows,
|
|
124
145
|
graphics: graphicsRows,
|
|
125
146
|
gameplay: gameplayRows,
|
|
126
|
-
controls:
|
|
147
|
+
controls: controlsRows,
|
|
127
148
|
};
|
|
128
149
|
const builtInKeybinds = { controls: keybinds };
|
|
129
150
|
const declaredLabels = new Map(config.categories.map((c) => [c.id, c.label]));
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** No action names are reserved when no camera rig is active (hud/none presentation): games may bind `turnLeft`/`interact`/etc. as their own. */
|
|
2
|
+
export declare const EMPTY_RESERVED: ReadonlySet<string>;
|
|
3
|
+
/** Empty action list — published while the orientation gate is up to suppress all held input without touching the tracker. */
|
|
4
|
+
export declare const NO_ACTIONS: string[];
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** No action names are reserved when no camera rig is active (hud/none presentation): games may bind `turnLeft`/`interact`/etc. as their own. */
|
|
2
|
+
export const EMPTY_RESERVED = new Set();
|
|
3
|
+
/** Empty action list — published while the orientation gate is up to suppress all held input without touching the tracker. */
|
|
4
|
+
export const NO_ACTIONS = [];
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Movement action names the shell drives via the walk controller. @internal */
|
|
2
|
+
export declare const SHELL_MOVEMENT_ACTIONS: readonly ["moveForward", "moveBack", "moveLeft", "moveRight", "jump"];
|
|
3
|
+
/**
|
|
4
|
+
* True when the game's input map binds any walk-controller action — the shell owns pose
|
|
5
|
+
* for those games. Games that omit all five drive pose themselves.
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
export declare function shellDrivesPlayerPose(input: Record<string, unknown> | undefined): boolean;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Movement action names the shell drives via the walk controller. @internal */
|
|
2
|
+
export const SHELL_MOVEMENT_ACTIONS = ["moveForward", "moveBack", "moveLeft", "moveRight", "jump"];
|
|
3
|
+
/**
|
|
4
|
+
* True when the game's input map binds any walk-controller action — the shell owns pose
|
|
5
|
+
* for those games. Games that omit all five drive pose themselves.
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
export function shellDrivesPlayerPose(input) {
|
|
9
|
+
const bound = input ?? {};
|
|
10
|
+
return SHELL_MOVEMENT_ACTIONS.some((action) => action in bound);
|
|
11
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type ContextMenu } from "@jgengine/core/interaction/contextMenu";
|
|
2
|
+
import type { Aim } from "@jgengine/core/scene/spatial";
|
|
3
|
+
import type { GameContext } from "@jgengine/core/runtime/gameContext";
|
|
4
|
+
import type { PointerService } from "./pointer/pointerService.js";
|
|
5
|
+
/** Minimal playable slice for context-menu verb lookup. @internal */
|
|
6
|
+
export type PointerPlayable = {
|
|
7
|
+
content: {
|
|
8
|
+
entityById?: (name: string) => {
|
|
9
|
+
verbs?: ContextMenu["verbs"];
|
|
10
|
+
} | null | undefined;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
/** Aim from the possessed/local player eye toward the current pointer world hit. @internal */
|
|
14
|
+
export declare function pointerAimFor(ctx: GameContext, service: PointerService): Aim | undefined;
|
|
15
|
+
/** Context menu for an entity/object pointer hit, or null for empty ground. @internal */
|
|
16
|
+
export declare function pointerContextMenu(ctx: GameContext, playable: PointerPlayable, hit: {
|
|
17
|
+
point: readonly [number, number, number];
|
|
18
|
+
entity: string | null;
|
|
19
|
+
object: string | null;
|
|
20
|
+
}): ContextMenu | null;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { buildContextMenu, } from "@jgengine/core/interaction/contextMenu";
|
|
2
|
+
import { aimToPoint } from "@jgengine/core/input/pointer";
|
|
3
|
+
import { eyeHeightFromColliders } from "@jgengine/core/combat/shotOrigin";
|
|
4
|
+
/** Aim from the possessed/local player eye toward the current pointer world hit. @internal */
|
|
5
|
+
export function pointerAimFor(ctx, service) {
|
|
6
|
+
const hit = service.worldHit();
|
|
7
|
+
if (hit === null)
|
|
8
|
+
return undefined;
|
|
9
|
+
const shooter = ctx.scene.entity.get(ctx.player.possession.active(ctx.player.userId)) ??
|
|
10
|
+
ctx.scene.entity.get(ctx.player.userId);
|
|
11
|
+
if (shooter === null)
|
|
12
|
+
return undefined;
|
|
13
|
+
const eye = eyeHeightFromColliders(ctx.scene.entity.collidersOf(shooter.id));
|
|
14
|
+
return aimToPoint([shooter.position[0], shooter.position[1] + eye, shooter.position[2]], hit.point);
|
|
15
|
+
}
|
|
16
|
+
/** Context menu for an entity/object pointer hit, or null for empty ground. @internal */
|
|
17
|
+
export function pointerContextMenu(ctx, playable, hit) {
|
|
18
|
+
if (hit.entity !== null) {
|
|
19
|
+
const entity = ctx.scene.entity.get(hit.entity);
|
|
20
|
+
const verbs = entity === null ? undefined : playable.content.entityById?.(entity.name)?.verbs;
|
|
21
|
+
return buildContextMenu({ kind: "entity", targetId: hit.entity, verbs, point: hit.point });
|
|
22
|
+
}
|
|
23
|
+
if (hit.object !== null) {
|
|
24
|
+
const verbs = ctx.scene.object.catalog(hit.object)?.verbs;
|
|
25
|
+
return buildContextMenu({ kind: "object", targetId: hit.object, verbs, point: hit.point });
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
@@ -5,4 +5,8 @@ export interface PlacementGhostProps {
|
|
|
5
5
|
validColor?: string;
|
|
6
6
|
invalidColor?: string;
|
|
7
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* Cursor-following build ghost: valid/invalid tint from a placement controller preview.
|
|
10
|
+
* @capability placement-ghost render valid/invalid placement preview mesh
|
|
11
|
+
*/
|
|
8
12
|
export declare function PlacementGhost({ preview, height, validColor, invalidColor, }: PlacementGhostProps): import("react").JSX.Element | null;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useMemo } from "react";
|
|
3
3
|
import { quarterTurnsToRotationY } from "@jgengine/core/world/placementController";
|
|
4
|
+
/**
|
|
5
|
+
* Cursor-following build ghost: valid/invalid tint from a placement controller preview.
|
|
6
|
+
* @capability placement-ghost render valid/invalid placement preview mesh
|
|
7
|
+
*/
|
|
4
8
|
export function PlacementGhost({ preview, height = 1, validColor = "#34d399", invalidColor = "#f87171", }) {
|
|
5
9
|
const footprint = preview?.footprint;
|
|
6
10
|
const size = useMemo(() => [footprint?.w ?? 1, height, footprint?.d ?? 1], [footprint?.w, footprint?.d, height]);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/** Active TransformControls mode for the runtime selection gizmo. */
|
|
2
|
+
export type TransformGizmoMode = "translate" | "rotate" | "scale";
|
|
3
|
+
/** Snap policy: grid quantize, free drag, or ground-height sample on release. */
|
|
4
|
+
export type TransformGizmoSnap = "grid" | "free" | "ground";
|
|
5
|
+
/** Gizmo handle orientation: world axes, or the target's local (yaw-rotated) axes. */
|
|
6
|
+
export type TransformGizmoSpace = "world" | "local";
|
|
7
|
+
/** Default rotation snap increment (radians) when callers don't configure one. */
|
|
8
|
+
export declare const DEFAULT_ROTATION_SNAP: number;
|
|
9
|
+
/** Pose reported by {@link TransformGizmo} when the user releases a drag. */
|
|
10
|
+
export interface TransformGizmoPose {
|
|
11
|
+
position: {
|
|
12
|
+
x: number;
|
|
13
|
+
y: number;
|
|
14
|
+
z: number;
|
|
15
|
+
};
|
|
16
|
+
rotationY: number;
|
|
17
|
+
scale: {
|
|
18
|
+
x: number;
|
|
19
|
+
y: number;
|
|
20
|
+
z: number;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/** Props for the shared runtime/editor selection gizmo. */
|
|
24
|
+
export interface TransformGizmoProps {
|
|
25
|
+
position: {
|
|
26
|
+
x: number;
|
|
27
|
+
y: number;
|
|
28
|
+
z: number;
|
|
29
|
+
};
|
|
30
|
+
rotationY?: number;
|
|
31
|
+
mode?: TransformGizmoMode;
|
|
32
|
+
snapMode?: TransformGizmoSnap;
|
|
33
|
+
gridSize?: number;
|
|
34
|
+
/** Handle orientation; `"local"` aligns handles to the target's yaw. */
|
|
35
|
+
space?: TransformGizmoSpace;
|
|
36
|
+
/** Rotation snap increment in radians; `null` rotates freely. */
|
|
37
|
+
rotationSnap?: number | null;
|
|
38
|
+
/** Scale snap increment; `null` (default) scales freely. */
|
|
39
|
+
scaleSnap?: number | null;
|
|
40
|
+
/** Vertical offset so the gizmo sits above ground markers. */
|
|
41
|
+
lift?: number;
|
|
42
|
+
size?: number;
|
|
43
|
+
groundSnap?: (x: number, z: number) => number;
|
|
44
|
+
/** Disable orbit/pan while dragging (shell/editor cameras). */
|
|
45
|
+
onDraggingChange?: (dragging: boolean) => void;
|
|
46
|
+
onRelease: (pose: TransformGizmoPose) => void;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Runtime selection/move gizmo shared by games and the editor.
|
|
50
|
+
* Wraps TransformControls; callers own selection and commit side-effects.
|
|
51
|
+
* @capability transform-gizmo in-game select/move/rotate/scale gizmo
|
|
52
|
+
*/
|
|
53
|
+
export declare const TransformGizmo: import("react").MemoExoticComponent<({ position, rotationY, mode, snapMode, gridSize, space, rotationSnap, scaleSnap, lift, size, groundSnap, onDraggingChange, onRelease, }: TransformGizmoProps) => import("react").JSX.Element>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { TransformControls } from "@react-three/drei";
|
|
3
|
+
import { useThree } from "@react-three/fiber";
|
|
4
|
+
import { memo, useEffect, useRef, useState } from "react";
|
|
5
|
+
/** Default rotation snap increment (radians) when callers don't configure one. */
|
|
6
|
+
export const DEFAULT_ROTATION_SNAP = Math.PI / 12;
|
|
7
|
+
/**
|
|
8
|
+
* Runtime selection/move gizmo shared by games and the editor.
|
|
9
|
+
* Wraps TransformControls; callers own selection and commit side-effects.
|
|
10
|
+
* @capability transform-gizmo in-game select/move/rotate/scale gizmo
|
|
11
|
+
*/
|
|
12
|
+
export const TransformGizmo = memo(function TransformGizmo({ position, rotationY = 0, mode = "translate", snapMode = "free", gridSize = 1, space = "world", rotationSnap = DEFAULT_ROTATION_SNAP, scaleSnap = null, lift = 0, size = 0.85, groundSnap, onDraggingChange, onRelease, }) {
|
|
13
|
+
const groupRef = useRef(null);
|
|
14
|
+
const [object, setObject] = useState(null);
|
|
15
|
+
const draggingRef = useRef(false);
|
|
16
|
+
const controls = useThree((state) => state.controls);
|
|
17
|
+
const anchorKey = `${position.x}:${position.y}:${position.z}:${rotationY}:${mode}`;
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
setObject(groupRef.current);
|
|
20
|
+
}, [anchorKey]);
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
const group = groupRef.current;
|
|
23
|
+
if (group === null || draggingRef.current)
|
|
24
|
+
return;
|
|
25
|
+
group.position.set(position.x, position.y + lift, position.z);
|
|
26
|
+
group.rotation.set(0, rotationY, 0);
|
|
27
|
+
group.scale.set(1, 1, 1);
|
|
28
|
+
}, [position.x, position.y, position.z, rotationY, lift, anchorKey, mode]);
|
|
29
|
+
const snapProps = {
|
|
30
|
+
translationSnap: snapMode === "grid" ? gridSize : null,
|
|
31
|
+
rotationSnap,
|
|
32
|
+
scaleSnap,
|
|
33
|
+
};
|
|
34
|
+
const handleRelease = () => {
|
|
35
|
+
draggingRef.current = false;
|
|
36
|
+
if (controls !== null && "enabled" in controls)
|
|
37
|
+
controls.enabled = true;
|
|
38
|
+
onDraggingChange?.(false);
|
|
39
|
+
const current = groupRef.current;
|
|
40
|
+
if (current === null)
|
|
41
|
+
return;
|
|
42
|
+
let y = current.position.y - lift;
|
|
43
|
+
if (snapMode === "ground" && groundSnap !== undefined) {
|
|
44
|
+
y = groundSnap(current.position.x, current.position.z);
|
|
45
|
+
current.position.y = y + lift;
|
|
46
|
+
}
|
|
47
|
+
onRelease({
|
|
48
|
+
position: { x: current.position.x, y, z: current.position.z },
|
|
49
|
+
rotationY: current.rotation.y,
|
|
50
|
+
scale: { x: current.scale.x, y: current.scale.y, z: current.scale.z },
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
return (_jsxs(_Fragment, { children: [_jsx("group", { ref: groupRef }), object !== null ? (_jsx(TransformControls, { object: object, mode: mode, size: size, space: space, ...snapProps, onMouseDown: () => {
|
|
54
|
+
draggingRef.current = true;
|
|
55
|
+
if (controls !== null && "enabled" in controls)
|
|
56
|
+
controls.enabled = false;
|
|
57
|
+
onDraggingChange?.(true);
|
|
58
|
+
}, onMouseUp: handleRelease })) : null] }));
|
|
59
|
+
});
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
export { BuildingBlock, GeneratedBuilding, InstancedBuildings, type BuildingBlockProps, type GeneratedBuildingProps, type InstancedBuildingPlacement, type InstancedBuildingsProps, } from "./GeneratedBuilding.js";
|
|
2
2
|
export { PlacementGhost, type PlacementGhostProps } from "./PlacementGhost.js";
|
|
3
|
+
export { TransformGizmo, type TransformGizmoMode, type TransformGizmoPose, type TransformGizmoProps, type TransformGizmoSnap, } from "./TransformGizmo.js";
|
package/dist/structures/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { type ThreeElements } from "@react-three/fiber";
|
|
2
|
-
import { type GrassBladeGeometryOptions, type GrassRange } from "./grassGeometry.js";
|
|
3
|
-
import { type GrassMaterialOptions, type GrassWindOptions } from "./grassMaterial.js";
|
|
2
|
+
import { type GrassBladeGeometryOptions, type GrassExclusion, type GrassRange } from "./grassGeometry.js";
|
|
3
|
+
import { type GrassDistanceFadeOptions, type GrassMaterialOptions, type GrassWindOptions } from "./grassMaterial.js";
|
|
4
4
|
import type { TerrainArea, TerrainHeightSampler } from "./terrainMath.js";
|
|
5
5
|
export { DEFAULT_GRASS_COUNT, DEFAULT_GRASS_DENSITY, resolveGrassInstanceBudget } from "./grassBudget.js";
|
|
6
|
+
export { GRASS_TUFT_BLADES } from "./grassGeometry.js";
|
|
6
7
|
export interface GrassFieldProps extends Omit<ThreeElements["mesh"], "args" | "children" | "geometry" | "material"> {
|
|
7
8
|
count?: number;
|
|
8
9
|
density?: number;
|
|
@@ -13,11 +14,18 @@ export interface GrassFieldProps extends Omit<ThreeElements["mesh"], "args" | "c
|
|
|
13
14
|
bladeHeight?: GrassRange;
|
|
14
15
|
bladeWidth?: GrassRange;
|
|
15
16
|
bladeBend?: GrassRange;
|
|
17
|
+
tuftBlades?: number;
|
|
18
|
+
tuftRadius?: number;
|
|
19
|
+
edgeFeather?: number;
|
|
20
|
+
exclude?: readonly GrassExclusion[];
|
|
16
21
|
heightAt?: TerrainHeightSampler;
|
|
17
22
|
colorBase?: GrassMaterialOptions["colorBase"];
|
|
18
23
|
colorTip?: GrassMaterialOptions["colorTip"];
|
|
24
|
+
colorGround?: GrassMaterialOptions["colorGround"];
|
|
19
25
|
colorVariation?: number;
|
|
20
26
|
wind?: GrassWindOptions | false;
|
|
27
|
+
distanceFade?: GrassDistanceFadeOptions | false;
|
|
28
|
+
normalLift?: number;
|
|
21
29
|
roughness?: number;
|
|
22
30
|
}
|
|
23
|
-
export declare function GrassField({ count, density, budget, area, seed, segments, bladeHeight, bladeWidth, bladeBend, heightAt, colorBase, colorTip, colorVariation, wind, roughness, castShadow, receiveShadow, frustumCulled, ...meshProps }: GrassFieldProps): import("react").JSX.Element;
|
|
31
|
+
export declare function GrassField({ count, density, budget, area, seed, segments, bladeHeight, bladeWidth, bladeBend, tuftBlades, tuftRadius, edgeFeather, exclude, heightAt, colorBase, colorTip, colorGround, colorVariation, wind, distanceFade, normalLift, roughness, castShadow, receiveShadow, frustumCulled, ...meshProps }: GrassFieldProps): import("react").JSX.Element;
|