@jgengine/shell 0.10.0 → 0.11.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 +13 -2
- package/LICENSE +203 -661
- package/README.md +1 -1
- package/dist/GamePhaseStamp.d.ts +1 -0
- package/dist/GamePhaseStamp.js +9 -0
- package/dist/GamePlayerShell.d.ts +0 -12
- package/dist/GamePlayerShell.js +88 -1043
- package/dist/GameUiPreview.js +0 -2
- package/dist/audio/audioEngine.js +30 -8
- package/dist/audio/audioWire.d.ts +22 -0
- package/dist/audio/audioWire.js +19 -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 +2 -2
- 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/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 +94 -24
- package/dist/camera/rigMath.js +100 -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/cartridge/runtime.d.ts +15 -0
- package/dist/cartridge/runtime.js +556 -0
- package/dist/cartridge/spec.d.ts +188 -0
- package/dist/cartridge/spec.js +19 -0
- package/dist/cartridge/validate.d.ts +2 -0
- package/dist/cartridge/validate.js +78 -0
- package/dist/cartridge.d.ts +1 -1
- package/dist/cartridge.js +3 -3
- package/dist/commandSink.d.ts +8 -3
- package/dist/commandSink.js +8 -3
- package/dist/defineGame.js +25 -6
- package/dist/devtools/ColPanel.d.ts +1 -0
- package/dist/devtools/ColPanel.js +14 -0
- package/dist/devtools/DevtoolsOverlay.d.ts +7 -3
- package/dist/devtools/DevtoolsOverlay.js +19 -450
- 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 +262 -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 +29 -0
- package/dist/diagnostics/RuntimeDiagnostics.js +69 -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 +32 -6
- 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/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/materialOverride.d.ts +10 -0
- package/dist/materialOverride.js +15 -3
- package/dist/multiplayer.js +8 -2
- package/dist/postfx/PostProcessing.js +47 -9
- package/dist/postfx/gradeShader.js +9 -1
- package/dist/registry.d.ts +3 -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/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 +73 -0
- package/dist/render/modelRender.d.ts +11 -0
- package/dist/render/modelRender.js +15 -0
- package/dist/render/resolveModel.d.ts +39 -3
- package/dist/render/resolveModel.js +79 -2
- 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 +130 -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 +94 -0
- package/dist/scatter.d.ts +1 -0
- package/dist/scatter.js +1 -0
- package/dist/scene/AuthoredScene.d.ts +82 -0
- package/dist/scene/AuthoredScene.js +173 -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 +25 -0
- package/dist/scene/SoilPatchRenderer.d.ts +2 -0
- package/dist/scene/SoilPatchRenderer.js +21 -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 +37 -0
- package/dist/scene/builtinSceneKindRenderers.d.ts +2 -0
- package/dist/scene/builtinSceneKindRenderers.js +22 -0
- package/dist/scene/sceneKindRenderers.d.ts +28 -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 +5 -0
- package/dist/settings/appliedSettings.js +9 -0
- 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 +43 -0
- package/dist/structures/TransformGizmo.js +55 -0
- package/dist/structures/index.d.ts +1 -0
- package/dist/structures/index.js +1 -0
- package/dist/terrain/GrassField.js +1 -1
- 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 +3 -0
- package/dist/terrain/grassGeometry.js +3 -0
- package/dist/terrain/grassMaterial.d.ts +2 -0
- package/dist/terrain/grassMaterial.js +2 -0
- package/dist/terrain/random.d.ts +3 -0
- package/dist/terrain/random.js +3 -0
- package/dist/terrain/soilPatchMaterial.d.ts +10 -0
- package/dist/terrain/soilPatchMaterial.js +109 -0
- package/dist/terrain/terrainDetailMaterial.d.ts +19 -2
- package/dist/terrain/terrainDetailMaterial.js +72 -5
- 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/OceanConfig.d.ts +2 -0
- package/dist/water/OceanConfig.js +2 -0
- 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/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 +159 -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 +5 -0
- package/dist/worldSky.js +11 -0
- package/dist/worldSync.d.ts +2 -1
- package/dist/worldSync.js +2 -1
- package/package.json +6 -6
- package/skills/jgengine/SKILL.md +51 -0
- package/skills/jgengine/api.md +434 -0
- package/skills/jgengine/capabilities.md +29 -0
- package/skills/jgengine/reference-cartridge.md +72 -0
- package/skills/jgengine-ui/SKILL.md +36 -0
- package/skills/jgengine-ui/api.md +1852 -0
- package/skills/jgengine-ui/capabilities.md +142 -0
- package/skills/jgengine-ui/reference.md +500 -0
- package/skills/jgengine-ui/references/visual-scorecard.md +73 -0
- package/skills/jgengine-world/SKILL.md +38 -0
- package/skills/jgengine-world/api.md +2652 -0
- package/skills/jgengine-world/capabilities.md +370 -0
- package/skills/jgengine-world/reference.md +222 -0
- package/llms.txt +0 -2761
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import {} from "@react-three/fiber";
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import * as THREE from "three";
|
|
5
|
+
const DETAIL_SIZE = 64;
|
|
6
|
+
const DETAIL_REPEAT = 3;
|
|
7
|
+
const NORMAL_STRENGTH = 2.2;
|
|
8
|
+
const ROUGHNESS_SPREAD = 0.14;
|
|
9
|
+
function periodicValueNoise(size, cells, seed) {
|
|
10
|
+
const lattice = new Float32Array(cells * cells);
|
|
11
|
+
for (let index = 0; index < lattice.length; index += 1) {
|
|
12
|
+
const h = Math.sin((index + 1) * 12.9898 + seed * 78.233) * 43758.5453;
|
|
13
|
+
lattice[index] = h - Math.floor(h);
|
|
14
|
+
}
|
|
15
|
+
const at = (cx, cy) => lattice[((cy % cells) + cells) % cells * cells + (((cx % cells) + cells) % cells)];
|
|
16
|
+
const field = new Float32Array(size * size);
|
|
17
|
+
const step = cells / size;
|
|
18
|
+
for (let y = 0; y < size; y += 1) {
|
|
19
|
+
for (let x = 0; x < size; x += 1) {
|
|
20
|
+
const fx = x * step;
|
|
21
|
+
const fy = y * step;
|
|
22
|
+
const x0 = Math.floor(fx);
|
|
23
|
+
const y0 = Math.floor(fy);
|
|
24
|
+
const tx = fx - x0;
|
|
25
|
+
const ty = fy - y0;
|
|
26
|
+
const sx = tx * tx * (3 - 2 * tx);
|
|
27
|
+
const sy = ty * ty * (3 - 2 * ty);
|
|
28
|
+
const top = at(x0, y0) * (1 - sx) + at(x0 + 1, y0) * sx;
|
|
29
|
+
const bottom = at(x0, y0 + 1) * (1 - sx) + at(x0 + 1, y0 + 1) * sx;
|
|
30
|
+
field[y * size + x] = top * (1 - sy) + bottom * sy;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return field;
|
|
34
|
+
}
|
|
35
|
+
function heightField(size) {
|
|
36
|
+
const coarse = periodicValueNoise(size, 4, 1);
|
|
37
|
+
const medium = periodicValueNoise(size, 8, 7);
|
|
38
|
+
const fine = periodicValueNoise(size, 16, 13);
|
|
39
|
+
const field = new Float32Array(size * size);
|
|
40
|
+
for (let index = 0; index < field.length; index += 1) {
|
|
41
|
+
field[index] = coarse[index] * 0.55 + medium[index] * 0.3 + fine[index] * 0.15;
|
|
42
|
+
}
|
|
43
|
+
return field;
|
|
44
|
+
}
|
|
45
|
+
let cached = null;
|
|
46
|
+
/**
|
|
47
|
+
* Shared, lazily-built procedural detail maps (a subtle tangent-space normal map plus a
|
|
48
|
+
* roughness-variation map) used by every un-modeled primitive so a flat colored box reads as a
|
|
49
|
+
* surface, not plastic. Pure JS `DataTexture` — no canvas/DOM, so it imports safely under `bun test`.
|
|
50
|
+
* @internal shell-internal default render surface; games never import it.
|
|
51
|
+
*/
|
|
52
|
+
export function detailMaps() {
|
|
53
|
+
if (cached !== null)
|
|
54
|
+
return cached;
|
|
55
|
+
const size = DETAIL_SIZE;
|
|
56
|
+
const height = heightField(size);
|
|
57
|
+
const sample = (x, y) => height[(((y % size) + size) % size) * size + (((x % size) + size) % size)];
|
|
58
|
+
const normalData = new Uint8Array(size * size * 4);
|
|
59
|
+
const roughnessData = new Uint8Array(size * size * 4);
|
|
60
|
+
for (let y = 0; y < size; y += 1) {
|
|
61
|
+
for (let x = 0; x < size; x += 1) {
|
|
62
|
+
const dx = (sample(x - 1, y) - sample(x + 1, y)) * NORMAL_STRENGTH;
|
|
63
|
+
const dy = (sample(x, y - 1) - sample(x, y + 1)) * NORMAL_STRENGTH;
|
|
64
|
+
const length = Math.hypot(dx, dy, 1);
|
|
65
|
+
const offset = (y * size + x) * 4;
|
|
66
|
+
normalData[offset] = Math.round((dx / length * 0.5 + 0.5) * 255);
|
|
67
|
+
normalData[offset + 1] = Math.round((dy / length * 0.5 + 0.5) * 255);
|
|
68
|
+
normalData[offset + 2] = Math.round((1 / length * 0.5 + 0.5) * 255);
|
|
69
|
+
normalData[offset + 3] = 255;
|
|
70
|
+
const rough = Math.round((0.5 + (sample(x, y) - 0.5) * 2 * ROUGHNESS_SPREAD * 0.5) * 255);
|
|
71
|
+
roughnessData[offset] = rough;
|
|
72
|
+
roughnessData[offset + 1] = rough;
|
|
73
|
+
roughnessData[offset + 2] = rough;
|
|
74
|
+
roughnessData[offset + 3] = 255;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const build = (data) => {
|
|
78
|
+
const texture = new THREE.DataTexture(data, size, size, THREE.RGBAFormat);
|
|
79
|
+
texture.wrapS = THREE.RepeatWrapping;
|
|
80
|
+
texture.wrapT = THREE.RepeatWrapping;
|
|
81
|
+
texture.repeat.set(DETAIL_REPEAT, DETAIL_REPEAT);
|
|
82
|
+
texture.minFilter = THREE.LinearMipmapLinearFilter;
|
|
83
|
+
texture.magFilter = THREE.LinearFilter;
|
|
84
|
+
texture.generateMipmaps = true;
|
|
85
|
+
texture.anisotropy = 4;
|
|
86
|
+
texture.needsUpdate = true;
|
|
87
|
+
return texture;
|
|
88
|
+
};
|
|
89
|
+
cached = { normal: build(normalData), roughness: build(roughnessData) };
|
|
90
|
+
return cached;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* The upgraded default material for un-modeled entities/objects: tuned PBR roughness/metalness plus
|
|
94
|
+
* the shared procedural detail maps, so primitive fallbacks catch light and image-based reflections
|
|
95
|
+
* instead of reading as flat plastic. Drop-in replacement for a bare `<meshStandardMaterial color>`.
|
|
96
|
+
* @internal shell-internal default render surface; games never import it.
|
|
97
|
+
*/
|
|
98
|
+
export function DefaultSurface({ color, roughness = 0.72, metalness = 0.04, detail = true, ...rest }) {
|
|
99
|
+
const maps = useMemo(() => (detail ? detailMaps() : null), [detail]);
|
|
100
|
+
return (_jsx("meshStandardMaterial", { color: color, roughness: roughness, metalness: metalness, envMapIntensity: 0.6, normalMap: maps?.normal ?? null, normalScale: maps === null ? undefined : new THREE.Vector2(0.6, 0.6), roughnessMap: maps?.roughness ?? null, ...rest }));
|
|
101
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { GLTFLoader, type GLTF } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
2
|
+
import { type AssetLoadDiagnosis } from "@jgengine/core/scene/assetDiagnostics";
|
|
3
|
+
/**
|
|
4
|
+
* Re-fetch a model URL that just failed to load and classify what actually came
|
|
5
|
+
* back — a 404, a dev-server HTML fallback, a corrupt or non-model body — so the
|
|
6
|
+
* runtime can replace an opaque GLTF parse error with a diagnosis naming the
|
|
7
|
+
* broken asset contract. Runs only on the error path (loads rarely fail), and
|
|
8
|
+
* never throws: a failed probe returns a `missing` diagnosis instead.
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
export declare function probeModelUrl(url: string, logicalId?: string, fetchImpl?: typeof fetch): Promise<AssetLoadDiagnosis>;
|
|
12
|
+
/**
|
|
13
|
+
* A {@link GLTFLoader} whose success path is unchanged but whose failures are
|
|
14
|
+
* enriched: when a load errors, it probes the URL and rejects with an
|
|
15
|
+
* {@link AssetLoadDiagnosis}-derived message (missing / HTML fallback / corrupt /
|
|
16
|
+
* unsupported) instead of the raw parse error. Drop-in for the shared loader, so
|
|
17
|
+
* `useLoader(sharedGltfLoader, url)` benefits with no call-site change.
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
export declare class DiagnosticGLTFLoader extends GLTFLoader {
|
|
21
|
+
load(url: string, onLoad: (gltf: GLTF) => void, onProgress?: (event: ProgressEvent) => void, onError?: (event: unknown) => void): void;
|
|
22
|
+
}
|
|
23
|
+
/** Shared GLTF loader used by shell entity/object model mounts. @internal */
|
|
24
|
+
export declare const sharedGltfLoader: DiagnosticGLTFLoader;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
3
|
+
import { MeshoptDecoder } from "three/examples/jsm/libs/meshopt_decoder.module.js";
|
|
4
|
+
import { classifyAssetResponse, } from "@jgengine/core/scene/assetDiagnostics";
|
|
5
|
+
/**
|
|
6
|
+
* Dedicated LoadingManager for every GLB load instead of THREE.DefaultLoadingManager.
|
|
7
|
+
* The shared default manager is process-wide; under repeated dev-server navigations its
|
|
8
|
+
* AbortController can already be aborted, which silently stalls GLTFLoader.fetch forever
|
|
9
|
+
* with no thrown error. A private manager sidesteps that (ported from the duet-keys ship).
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
const modelLoadingManager = new THREE.LoadingManager();
|
|
13
|
+
/** How many leading bytes to read when probing a failed model URL for its signature. @internal */
|
|
14
|
+
const PROBE_BYTE_LIMIT = 64;
|
|
15
|
+
/**
|
|
16
|
+
* Re-fetch a model URL that just failed to load and classify what actually came
|
|
17
|
+
* back — a 404, a dev-server HTML fallback, a corrupt or non-model body — so the
|
|
18
|
+
* runtime can replace an opaque GLTF parse error with a diagnosis naming the
|
|
19
|
+
* broken asset contract. Runs only on the error path (loads rarely fail), and
|
|
20
|
+
* never throws: a failed probe returns a `missing` diagnosis instead.
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
export async function probeModelUrl(url, logicalId, fetchImpl = fetch) {
|
|
24
|
+
const base = logicalId === undefined ? { url } : { url, logicalId };
|
|
25
|
+
try {
|
|
26
|
+
const response = await fetchImpl(url);
|
|
27
|
+
const buffer = await response.arrayBuffer();
|
|
28
|
+
const bytes = new Uint8Array(buffer.slice(0, PROBE_BYTE_LIMIT));
|
|
29
|
+
return classifyAssetResponse({
|
|
30
|
+
...base,
|
|
31
|
+
status: response.status,
|
|
32
|
+
statusText: response.statusText,
|
|
33
|
+
contentType: response.headers.get("content-type") ?? undefined,
|
|
34
|
+
bytes,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// The probe fetch itself failed (network/CORS) — the file is effectively unreachable.
|
|
39
|
+
return classifyAssetResponse({ ...base, status: 0 });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* A {@link GLTFLoader} whose success path is unchanged but whose failures are
|
|
44
|
+
* enriched: when a load errors, it probes the URL and rejects with an
|
|
45
|
+
* {@link AssetLoadDiagnosis}-derived message (missing / HTML fallback / corrupt /
|
|
46
|
+
* unsupported) instead of the raw parse error. Drop-in for the shared loader, so
|
|
47
|
+
* `useLoader(sharedGltfLoader, url)` benefits with no call-site change.
|
|
48
|
+
* @internal
|
|
49
|
+
*/
|
|
50
|
+
export class DiagnosticGLTFLoader extends GLTFLoader {
|
|
51
|
+
load(url, onLoad, onProgress, onError) {
|
|
52
|
+
super.load(url, onLoad, onProgress, (event) => {
|
|
53
|
+
if (onError === undefined)
|
|
54
|
+
return;
|
|
55
|
+
void probeModelUrl(url)
|
|
56
|
+
.then((diagnosis) => {
|
|
57
|
+
if (diagnosis.ok) {
|
|
58
|
+
// The bytes look fine on re-fetch — surface the original parse error unchanged.
|
|
59
|
+
onError(event);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const enriched = new Error(`[jgengine] ${diagnosis.message}`);
|
|
63
|
+
enriched.diagnosis = diagnosis;
|
|
64
|
+
enriched.cause = event;
|
|
65
|
+
onError(enriched);
|
|
66
|
+
})
|
|
67
|
+
.catch(() => onError(event));
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** Shared GLTF loader used by shell entity/object model mounts. @internal */
|
|
72
|
+
export const sharedGltfLoader = new DiagnosticGLTFLoader(modelLoadingManager);
|
|
73
|
+
sharedGltfLoader.setMeshoptDecoder(MeshoptDecoder);
|
|
@@ -1,23 +1,34 @@
|
|
|
1
1
|
import * as THREE from "three";
|
|
2
2
|
import type { PaintStroke } from "@jgengine/core/scene/paintLayer";
|
|
3
3
|
export declare const PAINT_TEXTURE_SIZE = 512;
|
|
4
|
+
/** @internal */
|
|
4
5
|
export declare function cloneModelScene(source: THREE.Object3D, options?: {
|
|
5
6
|
cloneMaterials?: boolean;
|
|
6
7
|
}): THREE.Object3D;
|
|
8
|
+
/** @internal */
|
|
7
9
|
export declare function disposeClonedMaterials(root: THREE.Object3D): void;
|
|
10
|
+
/** @internal */
|
|
8
11
|
export declare function standardMaterialsOf(root: THREE.Object3D): THREE.MeshStandardMaterial[];
|
|
9
12
|
export interface MaterialCache {
|
|
10
13
|
materials: THREE.MeshStandardMaterial[];
|
|
11
14
|
seedColor: THREE.Color;
|
|
12
15
|
}
|
|
16
|
+
/** @internal */
|
|
13
17
|
export declare function cacheStandardMaterials(root: THREE.Object3D, into?: MaterialCache | null): MaterialCache;
|
|
18
|
+
/** @internal */
|
|
14
19
|
export declare function applyPaintTextureToMaterials(materials: readonly THREE.MeshStandardMaterial[], paint: PaintCanvas): void;
|
|
15
20
|
export interface PaintCanvas {
|
|
16
21
|
canvas: HTMLCanvasElement;
|
|
17
22
|
context: CanvasRenderingContext2D;
|
|
18
23
|
texture: THREE.CanvasTexture;
|
|
19
24
|
}
|
|
25
|
+
/** @internal */
|
|
20
26
|
export declare function createPaintCanvas(seed: THREE.MeshStandardMaterial, size?: number): PaintCanvas;
|
|
27
|
+
/** @internal */
|
|
28
|
+
export declare function disposePaintCanvas(paint: PaintCanvas): void;
|
|
29
|
+
/** @internal */
|
|
21
30
|
export declare function drawPaintStrokes(paint: PaintCanvas, strokes: readonly PaintStroke[]): void;
|
|
31
|
+
/** @internal */
|
|
22
32
|
export declare function applyPaintTexture(root: THREE.Object3D, paint: PaintCanvas): void;
|
|
33
|
+
/** @internal */
|
|
23
34
|
export declare function syncPaintCanvas(paint: PaintCanvas, seedColor: THREE.Color, strokes: readonly PaintStroke[], drawnCount: number): number;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as THREE from "three";
|
|
2
2
|
import { clone as cloneSkinned } from "three/examples/jsm/utils/SkeletonUtils.js";
|
|
3
3
|
export const PAINT_TEXTURE_SIZE = 512;
|
|
4
|
+
/** @internal */
|
|
4
5
|
export function cloneModelScene(source, options) {
|
|
5
6
|
const clone = cloneSkinned(source);
|
|
6
7
|
if (options?.cloneMaterials === false)
|
|
@@ -15,6 +16,7 @@ export function cloneModelScene(source, options) {
|
|
|
15
16
|
});
|
|
16
17
|
return clone;
|
|
17
18
|
}
|
|
19
|
+
/** @internal */
|
|
18
20
|
export function disposeClonedMaterials(root) {
|
|
19
21
|
const seen = new Set();
|
|
20
22
|
root.traverse((node) => {
|
|
@@ -33,6 +35,7 @@ export function disposeClonedMaterials(root) {
|
|
|
33
35
|
function isMeshStandardMaterial(material) {
|
|
34
36
|
return material.isMeshStandardMaterial === true;
|
|
35
37
|
}
|
|
38
|
+
/** @internal */
|
|
36
39
|
export function standardMaterialsOf(root) {
|
|
37
40
|
const materials = [];
|
|
38
41
|
root.traverse((node) => {
|
|
@@ -46,6 +49,7 @@ export function standardMaterialsOf(root) {
|
|
|
46
49
|
});
|
|
47
50
|
return materials;
|
|
48
51
|
}
|
|
52
|
+
/** @internal */
|
|
49
53
|
export function cacheStandardMaterials(root, into) {
|
|
50
54
|
if (into !== null && into !== undefined)
|
|
51
55
|
return into;
|
|
@@ -53,12 +57,14 @@ export function cacheStandardMaterials(root, into) {
|
|
|
53
57
|
const seedColor = materials[0]?.color.clone() ?? new THREE.Color("#ffffff");
|
|
54
58
|
return { materials, seedColor };
|
|
55
59
|
}
|
|
60
|
+
/** @internal */
|
|
56
61
|
export function applyPaintTextureToMaterials(materials, paint) {
|
|
57
62
|
for (const material of materials) {
|
|
58
63
|
material.map = paint.texture;
|
|
59
64
|
material.needsUpdate = true;
|
|
60
65
|
}
|
|
61
66
|
}
|
|
67
|
+
/** @internal */
|
|
62
68
|
export function createPaintCanvas(seed, size = PAINT_TEXTURE_SIZE) {
|
|
63
69
|
const canvas = document.createElement("canvas");
|
|
64
70
|
canvas.width = size;
|
|
@@ -76,6 +82,13 @@ export function createPaintCanvas(seed, size = PAINT_TEXTURE_SIZE) {
|
|
|
76
82
|
texture.colorSpace = seed.map?.colorSpace ?? THREE.SRGBColorSpace;
|
|
77
83
|
return { canvas, context, texture };
|
|
78
84
|
}
|
|
85
|
+
/** @internal */
|
|
86
|
+
export function disposePaintCanvas(paint) {
|
|
87
|
+
paint.texture.dispose();
|
|
88
|
+
paint.canvas.width = 0;
|
|
89
|
+
paint.canvas.height = 0;
|
|
90
|
+
}
|
|
91
|
+
/** @internal */
|
|
79
92
|
export function drawPaintStrokes(paint, strokes) {
|
|
80
93
|
const { canvas, context, texture } = paint;
|
|
81
94
|
for (const stroke of strokes) {
|
|
@@ -86,9 +99,11 @@ export function drawPaintStrokes(paint, strokes) {
|
|
|
86
99
|
}
|
|
87
100
|
texture.needsUpdate = true;
|
|
88
101
|
}
|
|
102
|
+
/** @internal */
|
|
89
103
|
export function applyPaintTexture(root, paint) {
|
|
90
104
|
applyPaintTextureToMaterials(standardMaterialsOf(root), paint);
|
|
91
105
|
}
|
|
106
|
+
/** @internal */
|
|
92
107
|
export function syncPaintCanvas(paint, seedColor, strokes, drawnCount) {
|
|
93
108
|
if (strokes.length < drawnCount) {
|
|
94
109
|
const { canvas, context } = paint;
|
|
@@ -1,14 +1,50 @@
|
|
|
1
1
|
import type { AssetCatalog } from "@jgengine/core/scene/assetCatalog";
|
|
2
2
|
import type { ModelConfig } from "@jgengine/core/game/playableGame";
|
|
3
3
|
export interface ModelResolveContext {
|
|
4
|
-
seam: "entityModels" | "objectModels";
|
|
4
|
+
seam: "entityModels" | "objectModels" | "scatterModels";
|
|
5
5
|
key: string;
|
|
6
6
|
}
|
|
7
7
|
/**
|
|
8
8
|
* Resolve a string asset id or a direct ModelConfig. Missing/misspelled catalog
|
|
9
9
|
* ids throw — silent generic-primitive fallback only happens when the mapping
|
|
10
10
|
* omits the key entirely (or uses tryResolveCatalogModel for optional ids).
|
|
11
|
-
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
12
13
|
export declare function resolveModel(value: string | ModelConfig | undefined, assets: AssetCatalog, context?: ModelResolveContext): ModelConfig | undefined;
|
|
13
|
-
/** Soft lookup used when an object catalog id may double as a model asset id.
|
|
14
|
+
/** Soft lookup used when an object catalog id may double as a model asset id.
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
14
17
|
export declare function tryResolveCatalogModel(id: string, assets: AssetCatalog): ModelConfig | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Resolve an entity model plus bone-attachment / kit-of-parts models through the catalog
|
|
20
|
+
* so mounts receive fully-resolved `ModelConfig`s.
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolveEntityModel(value: string | ModelConfig | undefined, assets: AssetCatalog, key: string): ModelConfig | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* Preferred + optional fallback catalog ids for a single entity/object slot.
|
|
26
|
+
* Soft-resolves through the catalog: when neither id is live (pack not pulled/
|
|
27
|
+
* reindexed yet), the mapping is omitted and the shell keeps its primitive.
|
|
28
|
+
* Re-home later by fixing ids / pulling packs — no Kenney, no hard throws.
|
|
29
|
+
*/
|
|
30
|
+
export type ModelPick = {
|
|
31
|
+
model?: string;
|
|
32
|
+
fallbackModel?: string;
|
|
33
|
+
style?: Omit<ModelConfig, "url" | "dims">;
|
|
34
|
+
};
|
|
35
|
+
/** Soft-resolve `model`, then `fallbackModel`; merge `style` when either hits.
|
|
36
|
+
* @internal
|
|
37
|
+
*/
|
|
38
|
+
export declare function pickModel(assets: AssetCatalog, pick: ModelPick): ModelConfig | undefined;
|
|
39
|
+
/** Build an entityModels/objectModels map from a plan; missing catalog ids drop out (primitive).
|
|
40
|
+
* @internal
|
|
41
|
+
*/
|
|
42
|
+
export declare function resolveModelPlan(assets: AssetCatalog, plan: Record<string, ModelPick>): Record<string, ModelConfig>;
|
|
43
|
+
/**
|
|
44
|
+
* Builds a `(key) => ModelConfig | null` override from a `seam`'s id/config map plus its catalog —
|
|
45
|
+
* `undefined` when either half is missing so the caller can fall back to its own default renderer.
|
|
46
|
+
* An unmapped key resolves to `null` (deliberate fallback); a mapped key with a missing/misspelled
|
|
47
|
+
* catalog id throws via {@link resolveModel}, same as the `entityModels`/`objectModels` seams.
|
|
48
|
+
* @internal
|
|
49
|
+
*/
|
|
50
|
+
export declare function createModelMapResolver(map: Record<string, string | ModelConfig> | undefined, assets: AssetCatalog | undefined, seam: ModelResolveContext["seam"]): ((key: string) => ModelConfig | null) | undefined;
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Resolve a string asset id or a direct ModelConfig. Missing/misspelled catalog
|
|
3
3
|
* ids throw — silent generic-primitive fallback only happens when the mapping
|
|
4
4
|
* omits the key entirely (or uses tryResolveCatalogModel for optional ids).
|
|
5
|
-
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
6
7
|
export function resolveModel(value, assets, context) {
|
|
7
8
|
if (value === undefined)
|
|
8
9
|
return undefined;
|
|
@@ -15,10 +16,86 @@ export function resolveModel(value, assets, context) {
|
|
|
15
16
|
}
|
|
16
17
|
return ref.dims === undefined ? { url: ref.url } : { url: ref.url, dims: ref.dims };
|
|
17
18
|
}
|
|
18
|
-
/** Soft lookup used when an object catalog id may double as a model asset id.
|
|
19
|
+
/** Soft lookup used when an object catalog id may double as a model asset id.
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
19
22
|
export function tryResolveCatalogModel(id, assets) {
|
|
20
23
|
const ref = assets.resolve(id);
|
|
21
24
|
if (ref === null)
|
|
22
25
|
return undefined;
|
|
23
26
|
return ref.dims === undefined ? { url: ref.url } : { url: ref.url, dims: ref.dims };
|
|
24
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Resolve an entity model plus bone-attachment / kit-of-parts models through the catalog
|
|
30
|
+
* so mounts receive fully-resolved `ModelConfig`s.
|
|
31
|
+
* @internal
|
|
32
|
+
*/
|
|
33
|
+
export function resolveEntityModel(value, assets, key) {
|
|
34
|
+
const model = resolveModel(value, assets, { seam: "entityModels", key });
|
|
35
|
+
if (model === undefined)
|
|
36
|
+
return model;
|
|
37
|
+
if (model.attachments === undefined && model.parts === undefined)
|
|
38
|
+
return model;
|
|
39
|
+
return {
|
|
40
|
+
...model,
|
|
41
|
+
...(model.attachments === undefined
|
|
42
|
+
? {}
|
|
43
|
+
: {
|
|
44
|
+
attachments: model.attachments.map((attachment) => ({
|
|
45
|
+
...attachment,
|
|
46
|
+
model: resolveModel(attachment.model, assets) ?? attachment.model,
|
|
47
|
+
})),
|
|
48
|
+
}),
|
|
49
|
+
...(model.parts === undefined
|
|
50
|
+
? {}
|
|
51
|
+
: {
|
|
52
|
+
parts: model.parts.map((part) => ({
|
|
53
|
+
...part,
|
|
54
|
+
model: resolveModel(part.model, assets) ?? part.model,
|
|
55
|
+
})),
|
|
56
|
+
}),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/** Soft-resolve `model`, then `fallbackModel`; merge `style` when either hits.
|
|
60
|
+
* @internal
|
|
61
|
+
*/
|
|
62
|
+
export function pickModel(assets, pick) {
|
|
63
|
+
for (const id of [pick.model, pick.fallbackModel]) {
|
|
64
|
+
if (id === undefined)
|
|
65
|
+
continue;
|
|
66
|
+
const resolved = tryResolveCatalogModel(id, assets);
|
|
67
|
+
if (resolved === undefined)
|
|
68
|
+
continue;
|
|
69
|
+
return pick.style === undefined ? resolved : { ...resolved, ...pick.style };
|
|
70
|
+
}
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
/** Build an entityModels/objectModels map from a plan; missing catalog ids drop out (primitive).
|
|
74
|
+
* @internal
|
|
75
|
+
*/
|
|
76
|
+
export function resolveModelPlan(assets, plan) {
|
|
77
|
+
const out = {};
|
|
78
|
+
for (const [key, pick] of Object.entries(plan)) {
|
|
79
|
+
const model = pickModel(assets, pick);
|
|
80
|
+
if (model !== undefined)
|
|
81
|
+
out[key] = model;
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Builds a `(key) => ModelConfig | null` override from a `seam`'s id/config map plus its catalog —
|
|
87
|
+
* `undefined` when either half is missing so the caller can fall back to its own default renderer.
|
|
88
|
+
* An unmapped key resolves to `null` (deliberate fallback); a mapped key with a missing/misspelled
|
|
89
|
+
* catalog id throws via {@link resolveModel}, same as the `entityModels`/`objectModels` seams.
|
|
90
|
+
* @internal
|
|
91
|
+
*/
|
|
92
|
+
export function createModelMapResolver(map, assets, seam) {
|
|
93
|
+
if (map === undefined || assets === undefined)
|
|
94
|
+
return undefined;
|
|
95
|
+
return (key) => {
|
|
96
|
+
const mapped = map[key];
|
|
97
|
+
if (mapped === undefined)
|
|
98
|
+
return null;
|
|
99
|
+
return resolveModel(mapped, assets, { seam, key }) ?? null;
|
|
100
|
+
};
|
|
101
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type MutableRefObject } from "react";
|
|
2
|
+
import { type EntityRenderCues, type RenderCueTuning } from "@jgengine/core/combat/renderCues";
|
|
3
|
+
/**
|
|
4
|
+
* Live motion + animation cues for one entity, read from a mutable ref inside
|
|
5
|
+
* your own `useFrame` — no re-render per frame, no diffing the parent group's
|
|
6
|
+
* position, no game-side module map for attack/hit timing. Backs both custom
|
|
7
|
+
* `renderEntity` rigs and a custom first-person viewmodel (#542): call it with
|
|
8
|
+
* `entity.id` / the local player's userId and drive bob/recoil/reload poses
|
|
9
|
+
* from `cuesRef.current` inside the calling component's own `useFrame`.
|
|
10
|
+
*
|
|
11
|
+
* @capability entity-render-cues live velocity/bob/fire/reload/hit/death cues for a custom renderEntity or viewmodel component
|
|
12
|
+
*/
|
|
13
|
+
export declare function useEntityRenderCues(instanceId: string | undefined, tuning?: RenderCueTuning): MutableRefObject<EntityRenderCues>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { useFrame } from "@react-three/fiber";
|
|
2
|
+
import { useEffect, useRef } from "react";
|
|
3
|
+
import { advanceMotionCues, applyRenderAnimationEvent, applyRenderDeathEvent, applyRenderHitEvent, DEFAULT_FIRE_PULSE_SECONDS, DEFAULT_HIT_PULSE_SECONDS, DEFAULT_RENDER_CUES, } from "@jgengine/core/combat/renderCues";
|
|
4
|
+
import { groundSpeed } from "@jgengine/core/scene/entityStore";
|
|
5
|
+
import { useGameContext } from "@jgengine/react/provider";
|
|
6
|
+
/**
|
|
7
|
+
* Live motion + animation cues for one entity, read from a mutable ref inside
|
|
8
|
+
* your own `useFrame` — no re-render per frame, no diffing the parent group's
|
|
9
|
+
* position, no game-side module map for attack/hit timing. Backs both custom
|
|
10
|
+
* `renderEntity` rigs and a custom first-person viewmodel (#542): call it with
|
|
11
|
+
* `entity.id` / the local player's userId and drive bob/recoil/reload poses
|
|
12
|
+
* from `cuesRef.current` inside the calling component's own `useFrame`.
|
|
13
|
+
*
|
|
14
|
+
* @capability entity-render-cues live velocity/bob/fire/reload/hit/death cues for a custom renderEntity or viewmodel component
|
|
15
|
+
*/
|
|
16
|
+
export function useEntityRenderCues(instanceId, tuning) {
|
|
17
|
+
const ctx = useGameContext();
|
|
18
|
+
const cuesRef = useRef({ ...DEFAULT_RENDER_CUES });
|
|
19
|
+
const timersRef = useRef({ fire: 0, hit: 0 });
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
cuesRef.current = { ...DEFAULT_RENDER_CUES };
|
|
22
|
+
timersRef.current = { fire: 0, hit: 0 };
|
|
23
|
+
if (instanceId === undefined)
|
|
24
|
+
return;
|
|
25
|
+
const offAnimation = ctx.game.events.on("entity.animation", (event) => {
|
|
26
|
+
if (event.instanceId !== instanceId)
|
|
27
|
+
return;
|
|
28
|
+
cuesRef.current = applyRenderAnimationEvent(cuesRef.current, event.event);
|
|
29
|
+
if (event.event === "fire")
|
|
30
|
+
timersRef.current.fire = tuning?.firePulseSeconds ?? DEFAULT_FIRE_PULSE_SECONDS;
|
|
31
|
+
});
|
|
32
|
+
const offHit = ctx.game.events.on("combat.hitReaction", (event) => {
|
|
33
|
+
if (event.instanceId !== instanceId)
|
|
34
|
+
return;
|
|
35
|
+
cuesRef.current = applyRenderHitEvent(cuesRef.current);
|
|
36
|
+
timersRef.current.hit = tuning?.hitPulseSeconds ?? DEFAULT_HIT_PULSE_SECONDS;
|
|
37
|
+
});
|
|
38
|
+
const offDied = ctx.game.events.on("entity.died", (event) => {
|
|
39
|
+
if (event.instanceId !== instanceId)
|
|
40
|
+
return;
|
|
41
|
+
cuesRef.current = applyRenderDeathEvent(cuesRef.current);
|
|
42
|
+
});
|
|
43
|
+
return () => {
|
|
44
|
+
offAnimation();
|
|
45
|
+
offHit();
|
|
46
|
+
offDied();
|
|
47
|
+
};
|
|
48
|
+
}, [ctx, instanceId, tuning?.firePulseSeconds, tuning?.hitPulseSeconds]);
|
|
49
|
+
useFrame((_state, delta) => {
|
|
50
|
+
if (instanceId === undefined || delta <= 0)
|
|
51
|
+
return;
|
|
52
|
+
const entity = ctx.scene.entity.get(instanceId);
|
|
53
|
+
if (entity === null)
|
|
54
|
+
return;
|
|
55
|
+
const timers = timersRef.current;
|
|
56
|
+
timers.fire = Math.max(0, timers.fire - delta);
|
|
57
|
+
timers.hit = Math.max(0, timers.hit - delta);
|
|
58
|
+
const advanced = advanceMotionCues(cuesRef.current, groundSpeed(entity), delta, tuning);
|
|
59
|
+
cuesRef.current = { ...advanced, firing: timers.fire > 0, hit: timers.hit > 0 };
|
|
60
|
+
});
|
|
61
|
+
return cuesRef;
|
|
62
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ModelConfig } from "@jgengine/core/game/playableGame";
|
|
2
|
+
import { type ScatterInstance } from "@jgengine/core/world/scatterRegion";
|
|
3
|
+
/** Props for {@link InstancedScatter}: the resolved instances plus chunking/instance-cap knobs. */
|
|
4
|
+
export interface InstancedScatterProps {
|
|
5
|
+
instances: readonly ScatterInstance[];
|
|
6
|
+
/** Chunk edge in meters; dense fields split into independently frustum-culled buckets. Default 24. */
|
|
7
|
+
chunkSize?: number;
|
|
8
|
+
/** Hard cap on rendered instances. Default 20000. */
|
|
9
|
+
maxInstances?: number;
|
|
10
|
+
/**
|
|
11
|
+
* Per-species override: return a real catalog `ModelConfig` for an item to GPU-instance the actual
|
|
12
|
+
* GLB instead of the stylized proxy; return `null` (or omit the prop) to keep `buildScatterProxy`.
|
|
13
|
+
*/
|
|
14
|
+
resolveItem?: (item: string) => ModelConfig | null;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Renders resolved {@link ScatterInstance}s grouped into per-chunk, per-species GPU-instanced draws
|
|
18
|
+
* that frustum-cull independently. Species with no `resolveItem` mapping (or a `null` result) render
|
|
19
|
+
* as stylized proxy models (trunked trees, stacked pines, round bushes, faceted rocks, grass tufts);
|
|
20
|
+
* species `resolveItem` resolves to a `ModelConfig` GPU-instance the real catalog GLB instead. The one
|
|
21
|
+
* runtime foliage renderer shared by the editor's scatter preview and games that consume
|
|
22
|
+
* `resolveScatter` — never one node per placement.
|
|
23
|
+
*/
|
|
24
|
+
export declare function InstancedScatter({ instances, chunkSize, maxInstances, resolveItem, }: InstancedScatterProps): import("react").JSX.Element | null;
|