@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,118 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
const BARK = "#5b4127";
|
|
3
|
+
const LEAF = "#3f6b30";
|
|
4
|
+
const PINE = "#2f5330";
|
|
5
|
+
const OAK = "#4a7a37";
|
|
6
|
+
const BUSH = "#4d7d3a";
|
|
7
|
+
const ROCK = "#82858c";
|
|
8
|
+
const GRASS = "#5f9a3c";
|
|
9
|
+
const PALM_BARK = "#8a7355";
|
|
10
|
+
const PALM_FROND = "#4c8b3d";
|
|
11
|
+
const CYPRESS = "#31502f";
|
|
12
|
+
/** Concatenates part geometries into one vertex-colored buffer (position + normal + color). */
|
|
13
|
+
function mergeParts(parts) {
|
|
14
|
+
const geos = parts.map((part) => (part.geometry.index === null ? part.geometry : part.geometry.toNonIndexed()));
|
|
15
|
+
let total = 0;
|
|
16
|
+
for (const geo of geos)
|
|
17
|
+
total += geo.attributes.position.count;
|
|
18
|
+
const position = new Float32Array(total * 3);
|
|
19
|
+
const normal = new Float32Array(total * 3);
|
|
20
|
+
const color = new Float32Array(total * 3);
|
|
21
|
+
const tint = new THREE.Color();
|
|
22
|
+
let offset = 0;
|
|
23
|
+
for (let i = 0; i < geos.length; i += 1) {
|
|
24
|
+
const geo = geos[i];
|
|
25
|
+
const pos = geo.attributes.position;
|
|
26
|
+
const nor = geo.attributes.normal;
|
|
27
|
+
position.set(pos.array, offset * 3);
|
|
28
|
+
normal.set(nor.array, offset * 3);
|
|
29
|
+
tint.set(parts[i].color);
|
|
30
|
+
for (let v = 0; v < pos.count; v += 1) {
|
|
31
|
+
color[(offset + v) * 3] = tint.r;
|
|
32
|
+
color[(offset + v) * 3 + 1] = tint.g;
|
|
33
|
+
color[(offset + v) * 3 + 2] = tint.b;
|
|
34
|
+
}
|
|
35
|
+
offset += pos.count;
|
|
36
|
+
}
|
|
37
|
+
for (const geo of geos)
|
|
38
|
+
geo.dispose();
|
|
39
|
+
const merged = new THREE.BufferGeometry();
|
|
40
|
+
merged.setAttribute("position", new THREE.BufferAttribute(position, 3));
|
|
41
|
+
merged.setAttribute("normal", new THREE.BufferAttribute(normal, 3));
|
|
42
|
+
merged.setAttribute("color", new THREE.BufferAttribute(color, 3));
|
|
43
|
+
merged.computeBoundingSphere();
|
|
44
|
+
return merged;
|
|
45
|
+
}
|
|
46
|
+
function trunk(height, radius) {
|
|
47
|
+
const geo = new THREE.CylinderGeometry(radius * 0.8, radius, height, 5);
|
|
48
|
+
geo.translate(0, height / 2, 0);
|
|
49
|
+
return geo;
|
|
50
|
+
}
|
|
51
|
+
function cone(radius, height, y, segments = 7) {
|
|
52
|
+
const geo = new THREE.ConeGeometry(radius, height, segments);
|
|
53
|
+
geo.translate(0, y + height / 2, 0);
|
|
54
|
+
return geo;
|
|
55
|
+
}
|
|
56
|
+
function sphere(radius, y, squash = 1) {
|
|
57
|
+
const geo = new THREE.IcosahedronGeometry(radius, 1);
|
|
58
|
+
geo.scale(1, squash, 1);
|
|
59
|
+
geo.translate(0, y, 0);
|
|
60
|
+
return geo;
|
|
61
|
+
}
|
|
62
|
+
const BUILDERS = {
|
|
63
|
+
tree: () => mergeParts([
|
|
64
|
+
{ geometry: trunk(1.1, 0.12), color: BARK },
|
|
65
|
+
{ geometry: cone(0.8, 1.7, 0.9), color: LEAF },
|
|
66
|
+
]),
|
|
67
|
+
pine: () => mergeParts([
|
|
68
|
+
{ geometry: trunk(0.9, 0.1), color: BARK },
|
|
69
|
+
{ geometry: cone(0.7, 1.0, 0.7), color: PINE },
|
|
70
|
+
{ geometry: cone(0.55, 0.9, 1.35), color: PINE },
|
|
71
|
+
{ geometry: cone(0.38, 0.8, 1.95), color: PINE },
|
|
72
|
+
]),
|
|
73
|
+
oak: () => mergeParts([
|
|
74
|
+
{ geometry: trunk(1.0, 0.14), color: BARK },
|
|
75
|
+
{ geometry: sphere(0.85, 1.55, 0.85), color: OAK },
|
|
76
|
+
]),
|
|
77
|
+
bush: () => mergeParts([{ geometry: sphere(0.55, 0.45, 0.8), color: BUSH }]),
|
|
78
|
+
shrub: () => mergeParts([{ geometry: sphere(0.5, 0.4, 0.75), color: BUSH }]),
|
|
79
|
+
rock: () => mergeParts([{ geometry: sphere(0.5, 0.28, 0.7), color: ROCK }]),
|
|
80
|
+
stone: () => mergeParts([{ geometry: sphere(0.4, 0.22, 0.65), color: ROCK }]),
|
|
81
|
+
grass: () => mergeParts([
|
|
82
|
+
{ geometry: cone(0.09, 0.7, 0), color: GRASS },
|
|
83
|
+
{ geometry: cone(0.08, 0.55, 0).translate(0.12, 0, 0.05), color: GRASS },
|
|
84
|
+
{ geometry: cone(0.08, 0.6, 0).translate(-0.1, 0, -0.06), color: GRASS },
|
|
85
|
+
]),
|
|
86
|
+
palm: () => {
|
|
87
|
+
// Tall bare trunk with a radial crown of drooping fronds — each frond a flattened cone laid
|
|
88
|
+
// near-horizontal, tips sagging below the crown.
|
|
89
|
+
const parts = [{ geometry: trunk(2.6, 0.09), color: PALM_BARK }];
|
|
90
|
+
for (let i = 0; i < 6; i += 1) {
|
|
91
|
+
const yaw = (i / 6) * Math.PI * 2;
|
|
92
|
+
const frond = new THREE.ConeGeometry(0.16, 1.35, 4);
|
|
93
|
+
frond.scale(1, 1, 0.35);
|
|
94
|
+
frond.rotateX(-Math.PI / 2 - 0.5);
|
|
95
|
+
frond.rotateY(yaw);
|
|
96
|
+
frond.translate(Math.sin(yaw) * 0.32, 2.62, Math.cos(yaw) * 0.32);
|
|
97
|
+
parts.push({ geometry: frond, color: PALM_FROND });
|
|
98
|
+
}
|
|
99
|
+
parts.push({ geometry: sphere(0.14, 2.56), color: PALM_BARK });
|
|
100
|
+
return mergeParts(parts);
|
|
101
|
+
},
|
|
102
|
+
cypress: () => mergeParts([
|
|
103
|
+
{ geometry: trunk(0.5, 0.07), color: BARK },
|
|
104
|
+
{ geometry: cone(0.34, 2.5, 0.35, 6), color: CYPRESS },
|
|
105
|
+
{ geometry: cone(0.2, 0.7, 2.75, 5), color: CYPRESS },
|
|
106
|
+
]),
|
|
107
|
+
};
|
|
108
|
+
function defaultProxy() {
|
|
109
|
+
return mergeParts([{ geometry: cone(0.35, 1.2, 0), color: GRASS }]);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Builds the merged proxy geometry for a scatter item id, memoized per builder call by the caller.
|
|
113
|
+
* @internal — the geometry catalog behind the `InstancedScatter` renderer.
|
|
114
|
+
*/
|
|
115
|
+
export function buildScatterProxy(item) {
|
|
116
|
+
const builder = BUILDERS[item];
|
|
117
|
+
return builder === undefined ? defaultProxy() : builder();
|
|
118
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./scatter/InstancedScatter.js";
|
package/dist/scatter.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./scatter/InstancedScatter.js";
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { EditorDocument } from "@jgengine/core/editor/index";
|
|
2
|
+
import type { ModelConfig } from "@jgengine/core/game/playableGame";
|
|
3
|
+
import type { AssetCatalog } from "@jgengine/core/scene/assetCatalog";
|
|
4
|
+
import type { TerrainField } from "@jgengine/core/world/terrain";
|
|
5
|
+
/**
|
|
6
|
+
* Resolves the document AuthoredScene renders: when a {@link getDocumentLiveSync} bus is
|
|
7
|
+
* installed (editor host live), subscribe to document patches; otherwise use the prop.
|
|
8
|
+
* Document is authoritative — runtime overrides never land here unless written back.
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
export declare function useLiveEditorDocument(document: EditorDocument, live?: boolean): EditorDocument;
|
|
12
|
+
/** Props for {@link AuthoredPaths}: the document, the ground field to drape over, and a kind filter. */
|
|
13
|
+
export interface AuthoredPathsProps {
|
|
14
|
+
document: EditorDocument;
|
|
15
|
+
field: TerrainField;
|
|
16
|
+
/** Only render these path kinds; default renders every non-scatter path. */
|
|
17
|
+
kinds?: readonly string[];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Renders a document's non-scatter paths (roads, routes, corridors) as ground-draped ribbons — the
|
|
21
|
+
* editor authors the polyline, the engine drapes it over the live terrain at runtime. Width comes
|
|
22
|
+
* from `path.width`, color from `path.meta.color`/`path.color`. A game never hand-rolls path meshes.
|
|
23
|
+
*/
|
|
24
|
+
export declare function AuthoredPaths({ document, field, kinds }: AuthoredPathsProps): import("react").JSX.Element;
|
|
25
|
+
/** Props for {@link AuthoredObjects}: document, ground field, and optional lift / onExisting. */
|
|
26
|
+
export interface AuthoredObjectsProps {
|
|
27
|
+
document: EditorDocument;
|
|
28
|
+
field: TerrainField;
|
|
29
|
+
/** Extra lift in meters on top of each marker's own verticalOffset (default 0). */
|
|
30
|
+
verticalOffset?: number;
|
|
31
|
+
/**
|
|
32
|
+
* How to treat instance ids already in the object store. Default `"keep"` so an onInit
|
|
33
|
+
* `placeAuthoredObjects` call wins and remounts stay remount-safe.
|
|
34
|
+
*/
|
|
35
|
+
onExisting?: "throw" | "replace" | "keep";
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Places every editor marker with a catalog id into `ctx.scene.object`, grounded on `field`.
|
|
39
|
+
* WorldScene renders them through the game's `objectModels` seam — no bespoke place loop. Pure
|
|
40
|
+
* headless twin: `resolveAuthoredObjects` / `placeAuthoredObjects`.
|
|
41
|
+
*/
|
|
42
|
+
export declare function AuthoredObjects({ document, field, verticalOffset, onExisting, }: AuthoredObjectsProps): null;
|
|
43
|
+
/** Props for {@link AuthoredScene}: the document to render and the ground field to drape/ground on. */
|
|
44
|
+
export interface AuthoredSceneProps {
|
|
45
|
+
document: EditorDocument;
|
|
46
|
+
field: TerrainField;
|
|
47
|
+
/** Restrict rendered path kinds; default renders every non-scatter path. */
|
|
48
|
+
pathKinds?: readonly string[];
|
|
49
|
+
/**
|
|
50
|
+
* Scatter palette item id → model asset id or `ModelConfig`; a mapped item GPU-instances the real
|
|
51
|
+
* catalog GLB instead of the stylized proxy. Keyed by scatter `item` (not entity name — pass a
|
|
52
|
+
* dedicated map even if it shares entries with `entityModels`). Requires `assets`.
|
|
53
|
+
*/
|
|
54
|
+
scatterModels?: Record<string, string | ModelConfig>;
|
|
55
|
+
/** Catalog `scatterModels` string ids resolve through; required together with `scatterModels`. */
|
|
56
|
+
assets?: AssetCatalog;
|
|
57
|
+
/**
|
|
58
|
+
* When true (default), subscribe to the global document live-sync bus if the editor host is
|
|
59
|
+
* mounted so headless/RPC document patches hot-apply without a full reload. Pass false to pin
|
|
60
|
+
* the `document` prop (tests, one-shot previews).
|
|
61
|
+
*/
|
|
62
|
+
live?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Place the document's catalog-id markers into the object
|
|
65
|
+
* store — WorldScene renders them via the game's `objectModels` seam. Omit when the game places
|
|
66
|
+
* props itself in onInit with `placeAuthoredObjects`.
|
|
67
|
+
*/
|
|
68
|
+
placeObjects?: boolean | {
|
|
69
|
+
verticalOffset?: number;
|
|
70
|
+
};
|
|
71
|
+
/** Terrain surface color sampler forwarded to studio renderers (see `SceneKindRenderContext.groundColorAt`). */
|
|
72
|
+
groundColorAt?: (x: number, z: number) => string;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Renders an editor document's scene content — draped paths plus GPU-instanced foliage — from one
|
|
76
|
+
* mount, grounded on the live `field`. The runtime counterpart to authoring a scene in the editor:
|
|
77
|
+
* drag paths and foliage regions, save `editor.scene.json`, and the game plays them with no bespoke
|
|
78
|
+
* render code. When a live-sync bus is installed (editor host), document patches stream in and
|
|
79
|
+
* re-render automatically — document is authoritative; runtime overrides stay ephemeral unless
|
|
80
|
+
* written back. Terrain/collision come from the world's ground field (`environment({ sculpt })`);
|
|
81
|
+
* place markers with your own entity spawns. Pass `scatterModels`+`assets` to resolve palette items to
|
|
82
|
+
* real catalog GLBs; unmapped items keep the stylized proxy.
|
|
83
|
+
*/
|
|
84
|
+
export declare function AuthoredScene({ document, field, pathKinds, scatterModels, assets, live, placeObjects, groundColorAt, }: AuthoredSceneProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment, useEffect, useMemo, useState } from "react";
|
|
3
|
+
import * as THREE from "three";
|
|
4
|
+
import { getDocumentLiveSync, subscribeDocumentLiveSyncInstall, } from "@jgengine/core/editor/index";
|
|
5
|
+
import { placeAuthoredObjects, resolveAuthoredObjects, } from "@jgengine/core/world/authoredObjects";
|
|
6
|
+
import { buildRoadRibbon, roundPathCorners } from "@jgengine/core/world/roads";
|
|
7
|
+
import { isScatterPath, resolveScatter } from "@jgengine/core/world/scatterRegion";
|
|
8
|
+
import { getAssetGenerator } from "@jgengine/core/scene/assetGenerator";
|
|
9
|
+
import { useGameContext } from "@jgengine/react/provider";
|
|
10
|
+
import { createModelMapResolver } from "../render/resolveModel.js";
|
|
11
|
+
import { InstancedScatter } from "../scatter/InstancedScatter.js";
|
|
12
|
+
import { GeneratedAssetInstance } from "./GeneratedAssetRenderer.js";
|
|
13
|
+
import { registerBuiltinSceneKindRenderers } from "./builtinSceneKindRenderers.js";
|
|
14
|
+
import { getSceneKindRenderer } from "./sceneKindRenderers.js";
|
|
15
|
+
/**
|
|
16
|
+
* Resolves the document AuthoredScene renders: when a {@link getDocumentLiveSync} bus is
|
|
17
|
+
* installed (editor host live), subscribe to document patches; otherwise use the prop.
|
|
18
|
+
* Document is authoritative — runtime overrides never land here unless written back.
|
|
19
|
+
* @internal
|
|
20
|
+
*/
|
|
21
|
+
export function useLiveEditorDocument(document, live = true) {
|
|
22
|
+
const [resolved, setResolved] = useState(document);
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (!live) {
|
|
25
|
+
setResolved(document);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
let unsubDoc;
|
|
29
|
+
const attach = () => {
|
|
30
|
+
unsubDoc?.();
|
|
31
|
+
unsubDoc = undefined;
|
|
32
|
+
const sync = getDocumentLiveSync();
|
|
33
|
+
if (sync === null) {
|
|
34
|
+
setResolved(document);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
setResolved(sync.getDocument());
|
|
38
|
+
unsubDoc = sync.subscribeDocument((event) => setResolved(event.document));
|
|
39
|
+
};
|
|
40
|
+
attach();
|
|
41
|
+
const unsubInstall = subscribeDocumentLiveSyncInstall(attach);
|
|
42
|
+
return () => {
|
|
43
|
+
unsubInstall();
|
|
44
|
+
unsubDoc?.();
|
|
45
|
+
};
|
|
46
|
+
}, [document, live]);
|
|
47
|
+
return live ? resolved : document;
|
|
48
|
+
}
|
|
49
|
+
registerBuiltinSceneKindRenderers();
|
|
50
|
+
function markerToKindObject(marker) {
|
|
51
|
+
return { id: marker.id, kind: marker.kind, position: marker.position, ...(marker.rotationY === undefined ? {} : { rotationY: marker.rotationY }), ...(marker.meta === undefined ? {} : { meta: marker.meta }) };
|
|
52
|
+
}
|
|
53
|
+
function volumeToKindObject(volume) {
|
|
54
|
+
return {
|
|
55
|
+
id: volume.id,
|
|
56
|
+
kind: volume.kind,
|
|
57
|
+
center: volume.center,
|
|
58
|
+
...(volume.halfExtents === undefined ? {} : { halfExtents: volume.halfExtents }),
|
|
59
|
+
...(volume.radius === undefined ? {} : { radius: volume.radius }),
|
|
60
|
+
...(volume.meta === undefined ? {} : { meta: volume.meta }),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function pathToKindObject(path) {
|
|
64
|
+
return { id: path.id, kind: path.kind, points: path.points.map((point) => ({ x: point.x, y: point.y, z: point.z })), ...(path.meta === undefined ? {} : { meta: path.meta }) };
|
|
65
|
+
}
|
|
66
|
+
/** Renders every marker placed as a parametric generator asset, re-resolved from its `meta` + seed. */
|
|
67
|
+
function AuthoredGenerators({ document, field }) {
|
|
68
|
+
const markers = useMemo(() => document.markers.filter((marker) => typeof marker.meta?.["assetId"] === "string" && getAssetGenerator(marker.meta["assetId"]) !== undefined), [document.markers]);
|
|
69
|
+
return (_jsx(_Fragment, { children: markers.map((marker) => (_jsx(GeneratedAssetInstance, { meta: marker.meta, position: [marker.position.x, field.sampleHeight(marker.position.x, marker.position.z), marker.position.z], rotationY: marker.rotationY ?? 0 }, marker.id))) }));
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Mounts every registered studio renderer for the kinds present in a document — water and any
|
|
73
|
+
* game/example-registered kind — grouping objects by kind and handing each renderer its slice.
|
|
74
|
+
* Scatter stays on the batched {@link InstancedScatter} path (its cross-region avoid masks need the
|
|
75
|
+
* whole document), so it is skipped here.
|
|
76
|
+
*/
|
|
77
|
+
function AuthoredStudios({ document, context }) {
|
|
78
|
+
const byKind = useMemo(() => {
|
|
79
|
+
const groups = new Map();
|
|
80
|
+
const add = (kind, object) => {
|
|
81
|
+
if (isScatterPath({ kind }) || getSceneKindRenderer(kind) === undefined)
|
|
82
|
+
return;
|
|
83
|
+
const bucket = groups.get(kind);
|
|
84
|
+
if (bucket === undefined)
|
|
85
|
+
groups.set(kind, [object]);
|
|
86
|
+
else
|
|
87
|
+
bucket.push(object);
|
|
88
|
+
};
|
|
89
|
+
for (const marker of document.markers)
|
|
90
|
+
add(marker.kind, markerToKindObject(marker));
|
|
91
|
+
for (const volume of document.volumes)
|
|
92
|
+
add(volume.kind, volumeToKindObject(volume));
|
|
93
|
+
for (const path of document.paths)
|
|
94
|
+
add(path.kind, pathToKindObject(path));
|
|
95
|
+
return groups;
|
|
96
|
+
}, [document]);
|
|
97
|
+
return (_jsx(_Fragment, { children: [...byKind.entries()].map(([kind, objects]) => {
|
|
98
|
+
const renderer = getSceneKindRenderer(kind);
|
|
99
|
+
return renderer === undefined ? null : _jsx(Fragment, { children: renderer({ objects, context }) }, kind);
|
|
100
|
+
}) }));
|
|
101
|
+
}
|
|
102
|
+
const DEFAULT_PATH_COLOR = "#7a6444";
|
|
103
|
+
/** One editor path drawn as a ground-draped ribbon — subdivided and sampled against the live field. */
|
|
104
|
+
function DrapedPath({ points, width, color, field, }) {
|
|
105
|
+
const geometry = useMemo(() => {
|
|
106
|
+
// Fillet sharp turns first so bends read as smooth arcs, not overlapping rectangles.
|
|
107
|
+
const rounded = roundPathCorners(points, Math.max(1.5, width * 0.9), 6);
|
|
108
|
+
const ribbon = buildRoadRibbon(rounded, width, (x, z) => field.sampleHeight(x, z), {
|
|
109
|
+
elevation: 0.18,
|
|
110
|
+
maxSegmentLength: 1,
|
|
111
|
+
});
|
|
112
|
+
const geo = new THREE.BufferGeometry();
|
|
113
|
+
geo.setAttribute("position", new THREE.BufferAttribute(ribbon.positions, 3));
|
|
114
|
+
geo.setIndex(new THREE.BufferAttribute(ribbon.indices, 1));
|
|
115
|
+
geo.computeVertexNormals();
|
|
116
|
+
return geo;
|
|
117
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
118
|
+
}, [points, width, field]);
|
|
119
|
+
useEffect(() => () => geometry.dispose(), [geometry]);
|
|
120
|
+
if (points.length < 2)
|
|
121
|
+
return null;
|
|
122
|
+
return (_jsx("mesh", { geometry: geometry, receiveShadow: true, children: _jsx("meshStandardMaterial", { color: color, roughness: 1, metalness: 0, side: THREE.DoubleSide }) }));
|
|
123
|
+
}
|
|
124
|
+
function pathColor(meta, color) {
|
|
125
|
+
if (typeof meta?.["color"] === "string")
|
|
126
|
+
return meta["color"];
|
|
127
|
+
return color ?? DEFAULT_PATH_COLOR;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Renders a document's non-scatter paths (roads, routes, corridors) as ground-draped ribbons — the
|
|
131
|
+
* editor authors the polyline, the engine drapes it over the live terrain at runtime. Width comes
|
|
132
|
+
* from `path.width`, color from `path.meta.color`/`path.color`. A game never hand-rolls path meshes.
|
|
133
|
+
*/
|
|
134
|
+
export function AuthoredPaths({ document, field, kinds }) {
|
|
135
|
+
const paths = document.paths.filter((path) => !isScatterPath(path) && getSceneKindRenderer(path.kind) === undefined && (kinds === undefined || kinds.includes(path.kind)));
|
|
136
|
+
return (_jsx(_Fragment, { children: paths.map((path) => (_jsx(DrapedPath, { points: path.points.map((point) => [point.x, point.z]), width: path.width ?? 4, color: pathColor(path.meta, path.color), field: field }, path.id))) }));
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Places every editor marker with a catalog id into `ctx.scene.object`, grounded on `field`.
|
|
140
|
+
* WorldScene renders them through the game's `objectModels` seam — no bespoke place loop. Pure
|
|
141
|
+
* headless twin: `resolveAuthoredObjects` / `placeAuthoredObjects`.
|
|
142
|
+
*/
|
|
143
|
+
export function AuthoredObjects({ document, field, verticalOffset = 0, onExisting = "keep", }) {
|
|
144
|
+
const ctx = useGameContext();
|
|
145
|
+
const objects = useMemo(() => resolveAuthoredObjects(document), [document]);
|
|
146
|
+
useEffect(() => {
|
|
147
|
+
if (objects.length === 0)
|
|
148
|
+
return;
|
|
149
|
+
placeAuthoredObjects(ctx.scene.object, objects, (x, z) => field.sampleHeight(x, z), {
|
|
150
|
+
verticalOffset,
|
|
151
|
+
onExisting,
|
|
152
|
+
});
|
|
153
|
+
}, [ctx.scene.object, field, objects, onExisting, verticalOffset]);
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Renders an editor document's scene content — draped paths plus GPU-instanced foliage — from one
|
|
158
|
+
* mount, grounded on the live `field`. The runtime counterpart to authoring a scene in the editor:
|
|
159
|
+
* drag paths and foliage regions, save `editor.scene.json`, and the game plays them with no bespoke
|
|
160
|
+
* render code. When a live-sync bus is installed (editor host), document patches stream in and
|
|
161
|
+
* re-render automatically — document is authoritative; runtime overrides stay ephemeral unless
|
|
162
|
+
* written back. Terrain/collision come from the world's ground field (`environment({ sculpt })`);
|
|
163
|
+
* place markers with your own entity spawns. Pass `scatterModels`+`assets` to resolve palette items to
|
|
164
|
+
* real catalog GLBs; unmapped items keep the stylized proxy.
|
|
165
|
+
*/
|
|
166
|
+
export function AuthoredScene({ document, field, pathKinds, scatterModels, assets, live = true, placeObjects, groundColorAt, }) {
|
|
167
|
+
const liveDocument = useLiveEditorDocument(document, live);
|
|
168
|
+
const instances = useMemo(() => resolveScatter(liveDocument, field), [liveDocument, field]);
|
|
169
|
+
const resolveItem = useMemo(() => createModelMapResolver(scatterModels, assets, "scatterModels"), [scatterModels, assets]);
|
|
170
|
+
const shouldPlaceObjects = placeObjects === true || (typeof placeObjects === "object" && placeObjects !== null);
|
|
171
|
+
const objectVerticalOffset = typeof placeObjects === "object" && placeObjects !== null ? (placeObjects.verticalOffset ?? 0) : 0;
|
|
172
|
+
return (_jsxs(_Fragment, { children: [_jsx(AuthoredPaths, { document: liveDocument, field: field, ...(pathKinds === undefined ? {} : { kinds: pathKinds }) }), _jsx(InstancedScatter, { instances: instances, ...(resolveItem === undefined ? {} : { resolveItem }) }), _jsx(AuthoredStudios, { document: liveDocument, context: {
|
|
173
|
+
document: liveDocument,
|
|
174
|
+
field,
|
|
175
|
+
...(assets === undefined ? {} : { assets }),
|
|
176
|
+
...(groundColorAt === undefined ? {} : { groundColorAt }),
|
|
177
|
+
} }), _jsx(AuthoredGenerators, { document: liveDocument, field: field }), shouldPlaceObjects ? (_jsx(AuthoredObjects, { document: liveDocument, field: field, verticalOffset: objectVerticalOffset })) : null] }));
|
|
178
|
+
}
|