@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
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { useFrame, useThree } from "@react-three/fiber";
|
|
2
|
-
import { useEffect,
|
|
2
|
+
import { useEffect, useRef } from "react";
|
|
3
3
|
import * as THREE from "three";
|
|
4
4
|
import { EffectComposer, RenderPass, UnrealBloomPass } from "three-stdlib";
|
|
5
5
|
import { OutputPass } from "three/examples/jsm/postprocessing/OutputPass.js";
|
|
6
6
|
import { GTAOPass } from "three/examples/jsm/postprocessing/GTAOPass.js";
|
|
7
|
+
import { BokehPass } from "three/examples/jsm/postprocessing/BokehPass.js";
|
|
7
8
|
import { createGradePass } from "./gradeShader.js";
|
|
8
9
|
const TONE_MAPPING = {
|
|
9
10
|
aces: THREE.ACESFilmicToneMapping,
|
|
@@ -13,6 +14,23 @@ const TONE_MAPPING = {
|
|
|
13
14
|
linear: THREE.LinearToneMapping,
|
|
14
15
|
none: THREE.NoToneMapping,
|
|
15
16
|
};
|
|
17
|
+
function disposeGraph(built) {
|
|
18
|
+
for (const pass of built.composer.passes)
|
|
19
|
+
pass.dispose();
|
|
20
|
+
if (built.dof !== null) {
|
|
21
|
+
built.dof.renderTargetDepth.dispose();
|
|
22
|
+
built.dof.materialDepth.dispose();
|
|
23
|
+
built.dof.materialBokeh.dispose();
|
|
24
|
+
built.dof.fsQuad.dispose();
|
|
25
|
+
}
|
|
26
|
+
built.composer.dispose();
|
|
27
|
+
}
|
|
28
|
+
function syncSize(built, width, height, pixelRatio) {
|
|
29
|
+
built.composer.setPixelRatio(pixelRatio);
|
|
30
|
+
built.composer.setSize(width, height);
|
|
31
|
+
if (built.dof !== null)
|
|
32
|
+
built.dof.renderTargetDepth.setSize(width * pixelRatio, height * pixelRatio);
|
|
33
|
+
}
|
|
16
34
|
/**
|
|
17
35
|
* Mounts an `EffectComposer` inside the shell Canvas and takes over rendering
|
|
18
36
|
* (priority-1 `useFrame`, which disables R3F auto-render) to run the configured
|
|
@@ -24,7 +42,8 @@ export function PostProcessing({ config }) {
|
|
|
24
42
|
const scene = useThree((s) => s.scene);
|
|
25
43
|
const camera = useThree((s) => s.camera);
|
|
26
44
|
const size = useThree((s) => s.size);
|
|
27
|
-
const
|
|
45
|
+
const builtRef = useRef(null);
|
|
46
|
+
useEffect(() => {
|
|
28
47
|
const width = Math.max(1, size.width);
|
|
29
48
|
const height = Math.max(1, size.height);
|
|
30
49
|
const target = new THREE.WebGLRenderTarget(width, height, {
|
|
@@ -49,14 +68,30 @@ export function PostProcessing({ config }) {
|
|
|
49
68
|
const b = config.bloom ?? {};
|
|
50
69
|
composer.addPass(new UnrealBloomPass(new THREE.Vector2(width, height), b.strength ?? 0.32, b.radius ?? 0.55, b.threshold ?? 0.85));
|
|
51
70
|
}
|
|
71
|
+
let dof = null;
|
|
72
|
+
if (config.dof !== undefined && config.dof !== false) {
|
|
73
|
+
const d = config.dof;
|
|
74
|
+
dof = new BokehPass(scene, camera, {
|
|
75
|
+
focus: d.focus ?? 18,
|
|
76
|
+
aperture: d.aperture ?? 0.00025,
|
|
77
|
+
maxblur: d.maxBlur ?? 0.01,
|
|
78
|
+
});
|
|
79
|
+
composer.addPass(dof);
|
|
80
|
+
}
|
|
52
81
|
composer.addPass(new OutputPass());
|
|
53
82
|
let grade = null;
|
|
54
83
|
if (config.grade !== false) {
|
|
55
84
|
grade = createGradePass(config.grade ?? {});
|
|
56
85
|
composer.addPass(grade);
|
|
57
86
|
}
|
|
58
|
-
|
|
59
|
-
|
|
87
|
+
const graph = { composer, grade, dof };
|
|
88
|
+
syncSize(graph, width, height, gl.getPixelRatio());
|
|
89
|
+
builtRef.current = graph;
|
|
90
|
+
return () => {
|
|
91
|
+
disposeGraph(graph);
|
|
92
|
+
builtRef.current = null;
|
|
93
|
+
};
|
|
94
|
+
}, [gl, scene, camera, config]);
|
|
60
95
|
useEffect(() => {
|
|
61
96
|
const prevTone = gl.toneMapping;
|
|
62
97
|
const prevExposure = gl.toneMappingExposure;
|
|
@@ -68,12 +103,15 @@ export function PostProcessing({ config }) {
|
|
|
68
103
|
};
|
|
69
104
|
}, [gl, config.toneMapping, config.exposure]);
|
|
70
105
|
useEffect(() => {
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}, [
|
|
106
|
+
const built = builtRef.current;
|
|
107
|
+
if (built === null)
|
|
108
|
+
return;
|
|
109
|
+
syncSize(built, Math.max(1, size.width), Math.max(1, size.height), gl.getPixelRatio());
|
|
110
|
+
}, [gl, size.width, size.height]);
|
|
76
111
|
useFrame((_, delta) => {
|
|
112
|
+
const built = builtRef.current;
|
|
113
|
+
if (built === null)
|
|
114
|
+
return;
|
|
77
115
|
if (built.grade !== null)
|
|
78
116
|
built.grade.uniforms.uTime.value += delta;
|
|
79
117
|
built.composer.render(delta);
|
|
@@ -6,6 +6,7 @@ const DEFAULT_GAMMA = 0.96;
|
|
|
6
6
|
const DEFAULT_SATURATION = 1.12;
|
|
7
7
|
const DEFAULT_VIGNETTE = 0.2;
|
|
8
8
|
const DEFAULT_GRAIN = 0.012;
|
|
9
|
+
const DEFAULT_CHROMA = 0;
|
|
9
10
|
const fragmentShader = /* glsl */ `
|
|
10
11
|
uniform sampler2D tDiffuse;
|
|
11
12
|
uniform vec3 uLift;
|
|
@@ -14,6 +15,7 @@ const fragmentShader = /* glsl */ `
|
|
|
14
15
|
uniform float uSaturation;
|
|
15
16
|
uniform float uVignette;
|
|
16
17
|
uniform float uGrain;
|
|
18
|
+
uniform float uChroma;
|
|
17
19
|
uniform float uTime;
|
|
18
20
|
varying vec2 vUv;
|
|
19
21
|
|
|
@@ -22,7 +24,12 @@ const fragmentShader = /* glsl */ `
|
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
void main() {
|
|
25
|
-
|
|
27
|
+
vec2 dir = vUv - 0.5;
|
|
28
|
+
// Chromatic aberration: split RGB along the radial direction, growing toward the edges.
|
|
29
|
+
vec2 shift = uChroma > 0.0 ? dir * uChroma * dot(dir, dir) * 4.0 : vec2(0.0);
|
|
30
|
+
vec3 c = uChroma > 0.0
|
|
31
|
+
? vec3(texture2D(tDiffuse, vUv + shift).r, texture2D(tDiffuse, vUv).g, texture2D(tDiffuse, vUv - shift).b)
|
|
32
|
+
: texture2D(tDiffuse, vUv).rgb;
|
|
26
33
|
c = c + uLift;
|
|
27
34
|
c = c * uGain;
|
|
28
35
|
c = pow(max(c, 0.0), vec3(uGamma));
|
|
@@ -56,6 +63,7 @@ export function createGradePass(config = {}) {
|
|
|
56
63
|
uSaturation: { value: config.saturation ?? DEFAULT_SATURATION },
|
|
57
64
|
uVignette: { value: config.vignette ?? DEFAULT_VIGNETTE },
|
|
58
65
|
uGrain: { value: config.grain ?? DEFAULT_GRAIN },
|
|
66
|
+
uChroma: { value: config.chromaticAberration ?? DEFAULT_CHROMA },
|
|
59
67
|
uTime: { value: 0 },
|
|
60
68
|
},
|
|
61
69
|
vertexShader,
|
package/dist/registry.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { ComponentType, ReactNode } from "react";
|
|
2
|
-
import type { PlayableGame as EnginePlayableGame } from "@jgengine/core/game/playableGame";
|
|
2
|
+
import type { PlayableGame as EnginePlayableGame, WorldOverlayProps } from "@jgengine/core/game/playableGame";
|
|
3
3
|
import type { SceneEntity } from "@jgengine/core/scene/entityStore";
|
|
4
4
|
import type { SceneObject } from "@jgengine/core/scene/objectStore";
|
|
5
|
+
import type { ViewmodelProps } from "./camera/GameFirstPersonCamera.js";
|
|
5
6
|
export type RenderEntity = (entity: SceneEntity) => ReactNode;
|
|
6
7
|
export type RenderObject = (object: SceneObject) => ReactNode;
|
|
7
|
-
export type PlayableGame = EnginePlayableGame<ComponentType, ComponentType, RenderEntity, RenderObject
|
|
8
|
+
export type PlayableGame = EnginePlayableGame<ComponentType, ComponentType, RenderEntity, RenderObject, ComponentType<ViewmodelProps>, ComponentType<WorldOverlayProps>>;
|
|
8
9
|
export type GameRegistry = Record<string, () => Promise<PlayableGame>>;
|
|
9
10
|
export declare function resolveGameLoader(registry: GameRegistry, gameId: string, fallbackGameId?: string): (() => Promise<PlayableGame>) | undefined;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sets a neutral, network-free image-based-lighting environment on the scene so every
|
|
3
|
+
* `MeshStandardMaterial` — models and un-modeled primitive fallbacks alike — picks up soft
|
|
4
|
+
* reflections and fill instead of rendering dead-flat. A game's own sky/lighting still layers on
|
|
5
|
+
* top; this only supplies the reflective ambient the shell never had. Opt out with `look: "flat"`.
|
|
6
|
+
* @internal shell-internal default lighting; games never import it.
|
|
7
|
+
*/
|
|
8
|
+
export declare function EnvironmentLighting({ intensity }: {
|
|
9
|
+
intensity?: number;
|
|
10
|
+
}): null;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useThree } from "@react-three/fiber";
|
|
2
|
+
import { useEffect } from "react";
|
|
3
|
+
import * as THREE from "three";
|
|
4
|
+
import { RoomEnvironment } from "three/examples/jsm/environments/RoomEnvironment.js";
|
|
5
|
+
/**
|
|
6
|
+
* Sets a neutral, network-free image-based-lighting environment on the scene so every
|
|
7
|
+
* `MeshStandardMaterial` — models and un-modeled primitive fallbacks alike — picks up soft
|
|
8
|
+
* reflections and fill instead of rendering dead-flat. A game's own sky/lighting still layers on
|
|
9
|
+
* top; this only supplies the reflective ambient the shell never had. Opt out with `look: "flat"`.
|
|
10
|
+
* @internal shell-internal default lighting; games never import it.
|
|
11
|
+
*/
|
|
12
|
+
export function EnvironmentLighting({ intensity = 0.28 }) {
|
|
13
|
+
const gl = useThree((state) => state.gl);
|
|
14
|
+
const scene = useThree((state) => state.scene);
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
const pmrem = new THREE.PMREMGenerator(gl);
|
|
17
|
+
const room = new RoomEnvironment();
|
|
18
|
+
const target = pmrem.fromScene(room, 0.04);
|
|
19
|
+
const previous = scene.environment;
|
|
20
|
+
scene.environment = target.texture;
|
|
21
|
+
const sceneWithIntensity = scene;
|
|
22
|
+
const previousIntensity = sceneWithIntensity.environmentIntensity;
|
|
23
|
+
sceneWithIntensity.environmentIntensity = intensity;
|
|
24
|
+
room.dispose?.();
|
|
25
|
+
pmrem.dispose();
|
|
26
|
+
return () => {
|
|
27
|
+
target.dispose();
|
|
28
|
+
scene.environment = previous;
|
|
29
|
+
if (previousIntensity !== undefined)
|
|
30
|
+
sceneWithIntensity.environmentIntensity = previousIntensity;
|
|
31
|
+
};
|
|
32
|
+
}, [gl, scene, intensity]);
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { BackdropConfig, LightingConfig } from "@jgengine/core/game/playableGame";
|
|
2
|
+
export declare function ConfiguredLighting({ lighting }: {
|
|
3
|
+
lighting: LightingConfig;
|
|
4
|
+
}): import("react").JSX.Element;
|
|
5
|
+
export declare function BackdropFog({ fog }: {
|
|
6
|
+
fog: BackdropConfig["fog"];
|
|
7
|
+
}): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
const DEFAULT_BACKDROP_FOG_COLOR = "#1a1c22";
|
|
3
|
+
function DirectionalShadowLight({ entry }) {
|
|
4
|
+
const size = entry.shadowCameraSize ?? 40;
|
|
5
|
+
return (_jsx("directionalLight", { position: [entry.position[0], entry.position[1], entry.position[2]], intensity: entry.intensity ?? 1.3, color: entry.color, castShadow: entry.castShadow ?? false, "shadow-mapSize-width": entry.shadowMapSize ?? 1024, "shadow-mapSize-height": entry.shadowMapSize ?? 1024, "shadow-camera-left": -size, "shadow-camera-right": size, "shadow-camera-top": size, "shadow-camera-bottom": -size, "shadow-camera-near": 0.5, "shadow-camera-far": Math.max(200, size * 6), "shadow-bias": entry.shadowBias ?? -0.0004, "shadow-normalBias": entry.shadowNormalBias ?? 0.02 }));
|
|
6
|
+
}
|
|
7
|
+
export function ConfiguredLighting({ lighting }) {
|
|
8
|
+
return (_jsxs(_Fragment, { children: [lighting.ambient !== undefined ? (_jsx("ambientLight", { color: lighting.ambient.color, intensity: lighting.ambient.intensity ?? 0.55 })) : null, lighting.hemisphere !== undefined ? (_jsx("hemisphereLight", { args: [
|
|
9
|
+
lighting.hemisphere.skyColor ?? "#bfe3ff",
|
|
10
|
+
lighting.hemisphere.groundColor ?? "#4c6b34",
|
|
11
|
+
lighting.hemisphere.intensity ?? 0.55,
|
|
12
|
+
] })) : null, (lighting.directional ?? []).map((entry, index) => (_jsx(DirectionalShadowLight, { entry: entry }, index)))] }));
|
|
13
|
+
}
|
|
14
|
+
export function BackdropFog({ fog }) {
|
|
15
|
+
if (fog === undefined)
|
|
16
|
+
return null;
|
|
17
|
+
return fog.density !== undefined ? (_jsx("fogExp2", { attach: "fog", args: [fog.color ?? DEFAULT_BACKDROP_FOG_COLOR, fog.density] })) : (_jsx("fog", { attach: "fog", args: [fog.color ?? DEFAULT_BACKDROP_FOG_COLOR, fog.near ?? 10, fog.far ?? 200] }));
|
|
18
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { EntitySpriteConfig, ModelConfig } from "@jgengine/core/game/playableGame";
|
|
3
|
+
export declare function EntitySprite({ sprite }: {
|
|
4
|
+
sprite: EntitySpriteConfig;
|
|
5
|
+
}): import("react").JSX.Element;
|
|
6
|
+
export declare function IsolatedEntityModel({ model, instanceId, fallback, }: {
|
|
7
|
+
model: ModelConfig;
|
|
8
|
+
instanceId?: string;
|
|
9
|
+
fallback?: ReactNode;
|
|
10
|
+
}): import("react").JSX.Element;
|
|
11
|
+
export declare function EntityModel({ model, instanceId }: {
|
|
12
|
+
model: ModelConfig;
|
|
13
|
+
instanceId?: string;
|
|
14
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useFrame, useLoader } from "@react-three/fiber";
|
|
3
|
+
import { useTexture } from "@react-three/drei";
|
|
4
|
+
import { Component, Suspense, useEffect, useMemo, useRef } from "react";
|
|
5
|
+
import * as THREE from "three";
|
|
6
|
+
import { resolveOneShotClip } from "@jgengine/core/game/modelAnimation";
|
|
7
|
+
import { useGameContext } from "@jgengine/react/provider";
|
|
8
|
+
import { sharedGltfLoader } from "./modelLoad.js";
|
|
9
|
+
import { applyMaterialOverride } from "../materialOverride.js";
|
|
10
|
+
import { applyPaintTextureToMaterials, cacheStandardMaterials, cloneModelScene, createPaintCanvas, disposeClonedMaterials, disposePaintCanvas, syncPaintCanvas, } from "./modelRender.js";
|
|
11
|
+
export function EntitySprite({ sprite }) {
|
|
12
|
+
const texture = useLoader(THREE.TextureLoader, sprite.url);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
texture.colorSpace = THREE.SRGBColorSpace;
|
|
15
|
+
texture.anisotropy = 4;
|
|
16
|
+
texture.needsUpdate = true;
|
|
17
|
+
}, [texture]);
|
|
18
|
+
return (_jsx("sprite", { "position-y": sprite.y, scale: [sprite.width, sprite.height, 1], children: _jsx("spriteMaterial", { map: texture, transparent: true, alphaTest: 0.08, depthWrite: false }) }));
|
|
19
|
+
}
|
|
20
|
+
class ModelFallbackBoundary extends Component {
|
|
21
|
+
state = { failed: false };
|
|
22
|
+
static getDerivedStateFromError() {
|
|
23
|
+
return { failed: true };
|
|
24
|
+
}
|
|
25
|
+
render() {
|
|
26
|
+
return this.state.failed ? this.props.fallback : this.props.children;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function IsolatedEntityModel({ model, instanceId, fallback, }) {
|
|
30
|
+
return (_jsx(ModelFallbackBoundary, { fallback: fallback ?? null, children: _jsx(Suspense, { fallback: null, children: _jsx(EntityModel, { model: model, instanceId: instanceId }) }) }));
|
|
31
|
+
}
|
|
32
|
+
function BoneAttachment({ rig, model, slot, position, rotation, scale, }) {
|
|
33
|
+
const gltf = useLoader(sharedGltfLoader, model.url);
|
|
34
|
+
const weaponScene = useMemo(() => cloneModelScene(gltf.scene), [gltf]);
|
|
35
|
+
const px = position?.[0] ?? 0;
|
|
36
|
+
const py = position?.[1] ?? 0;
|
|
37
|
+
const pz = position?.[2] ?? 0;
|
|
38
|
+
const rx = rotation?.[0] ?? 0;
|
|
39
|
+
const ry = rotation?.[1] ?? 0;
|
|
40
|
+
const rz = rotation?.[2] ?? 0;
|
|
41
|
+
const s = scale ?? 1;
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
const bone = rig.getObjectByName(slot);
|
|
44
|
+
if (bone === undefined) {
|
|
45
|
+
if (typeof console !== "undefined") {
|
|
46
|
+
console.warn(`[jgengine] entityModels attachment: bone/slot "${slot}" not found on the rig`);
|
|
47
|
+
}
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
weaponScene.position.set(px, py, pz);
|
|
51
|
+
weaponScene.rotation.set(rx, ry, rz);
|
|
52
|
+
weaponScene.scale.setScalar(s);
|
|
53
|
+
bone.add(weaponScene);
|
|
54
|
+
return () => {
|
|
55
|
+
bone.remove(weaponScene);
|
|
56
|
+
};
|
|
57
|
+
}, [rig, weaponScene, slot, px, py, pz, rx, ry, rz, s]);
|
|
58
|
+
useEffect(() => () => disposeClonedMaterials(weaponScene), [weaponScene]);
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
/** Renders a static kit-of-parts child at a fixed local offset under the parent model — no bone/rig lookup, unlike `BoneAttachment`. The nested `EntityModel` still applies its own `dims`/anchor centering, so scale/pivot sanity carries over per part. */
|
|
62
|
+
function ModelPartGroup({ model, position, rotation, scale, }) {
|
|
63
|
+
return (_jsx("group", { position: position ?? [0, 0, 0], rotation: rotation ?? [0, 0, 0], scale: scale ?? 1, children: _jsx(EntityModel, { model: model }) }));
|
|
64
|
+
}
|
|
65
|
+
function ModelMaterialMapsApplier({ scene, maps }) {
|
|
66
|
+
const entries = useMemo(() => {
|
|
67
|
+
const record = {};
|
|
68
|
+
if (maps.color !== undefined)
|
|
69
|
+
record.color = maps.color;
|
|
70
|
+
if (maps.normal !== undefined)
|
|
71
|
+
record.normal = maps.normal;
|
|
72
|
+
if (maps.roughness !== undefined)
|
|
73
|
+
record.roughness = maps.roughness;
|
|
74
|
+
if (maps.ao !== undefined)
|
|
75
|
+
record.ao = maps.ao;
|
|
76
|
+
return record;
|
|
77
|
+
}, [maps.color, maps.normal, maps.roughness, maps.ao]);
|
|
78
|
+
const textures = useTexture(entries);
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
if (textures.color !== undefined)
|
|
81
|
+
textures.color.colorSpace = THREE.SRGBColorSpace;
|
|
82
|
+
applyMaterialOverride(scene, {}, { clone: false, textures });
|
|
83
|
+
}, [scene, textures]);
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
export function EntityModel({ model, instanceId }) {
|
|
87
|
+
const gltf = useLoader(sharedGltfLoader, model.url);
|
|
88
|
+
const ctx = useGameContext();
|
|
89
|
+
const material = model.material;
|
|
90
|
+
const baseY = model.y ?? 0;
|
|
91
|
+
const dims = model.dims;
|
|
92
|
+
const scene = useMemo(() => {
|
|
93
|
+
const cloned = cloneModelScene(gltf.scene);
|
|
94
|
+
if (material !== undefined)
|
|
95
|
+
applyMaterialOverride(cloned, material, { clone: false });
|
|
96
|
+
return cloned;
|
|
97
|
+
}, [gltf, material]);
|
|
98
|
+
const measured = useMemo(() => {
|
|
99
|
+
if (model.targetHeight === undefined)
|
|
100
|
+
return null;
|
|
101
|
+
const box = new THREE.Box3().setFromObject(scene);
|
|
102
|
+
const height = box.max.y - box.min.y;
|
|
103
|
+
if (!Number.isFinite(height) || height <= 0)
|
|
104
|
+
return null;
|
|
105
|
+
return {
|
|
106
|
+
normalize: model.targetHeight / height,
|
|
107
|
+
minY: box.min.y,
|
|
108
|
+
centerX: (box.min.x + box.max.x) / 2,
|
|
109
|
+
centerZ: (box.min.z + box.max.z) / 2,
|
|
110
|
+
};
|
|
111
|
+
}, [scene, model.targetHeight]);
|
|
112
|
+
const scale = (model.scale ?? 1) * (measured?.normalize ?? 1);
|
|
113
|
+
const centered = (model.anchor ?? "center") === "center" && dims !== undefined;
|
|
114
|
+
const position = measured !== null
|
|
115
|
+
? [-scale * measured.centerX, baseY - scale * measured.minY, -scale * measured.centerZ]
|
|
116
|
+
: centered
|
|
117
|
+
? [-scale * dims.center.x, baseY - scale * dims.minY, -scale * dims.center.z]
|
|
118
|
+
: [0, baseY, 0];
|
|
119
|
+
useEffect(() => () => {
|
|
120
|
+
disposeClonedMaterials(scene);
|
|
121
|
+
}, [scene]);
|
|
122
|
+
const animation = model.animation;
|
|
123
|
+
const mixerRef = useRef(null);
|
|
124
|
+
const animationPausedRef = useRef(false);
|
|
125
|
+
const stateActionsRef = useRef(null);
|
|
126
|
+
const states = animation?.states;
|
|
127
|
+
const oneShots = animation?.oneShots;
|
|
128
|
+
const oneShotPlayRef = useRef(null);
|
|
129
|
+
const activeOneShotRef = useRef(null);
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
if (animation === undefined || gltf.animations.length === 0) {
|
|
132
|
+
mixerRef.current = null;
|
|
133
|
+
stateActionsRef.current = null;
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const mixer = new THREE.AnimationMixer(scene);
|
|
137
|
+
if (states !== undefined) {
|
|
138
|
+
const clipFor = (name) => THREE.AnimationClip.findByName(gltf.animations, name) ?? gltf.animations[0];
|
|
139
|
+
const actions = {
|
|
140
|
+
idle: mixer.clipAction(clipFor(states.idle)),
|
|
141
|
+
walk: mixer.clipAction(clipFor(states.walk)),
|
|
142
|
+
...(states.run === undefined ? {} : { run: mixer.clipAction(clipFor(states.run)) }),
|
|
143
|
+
};
|
|
144
|
+
for (const action of Object.values(actions)) {
|
|
145
|
+
action.setLoop(THREE.LoopRepeat, Infinity);
|
|
146
|
+
action.timeScale = animation.timeScale ?? 1;
|
|
147
|
+
action.enabled = true;
|
|
148
|
+
}
|
|
149
|
+
actions.idle.play();
|
|
150
|
+
mixer.update(0);
|
|
151
|
+
mixerRef.current = mixer;
|
|
152
|
+
stateActionsRef.current = { actions, active: "idle", lastPos: null, smoothedSpeed: 0 };
|
|
153
|
+
animationPausedRef.current = false;
|
|
154
|
+
let onOneShotFinished = null;
|
|
155
|
+
if (oneShots !== undefined) {
|
|
156
|
+
const clipNames = new Set();
|
|
157
|
+
for (const spec of Object.values(oneShots)) {
|
|
158
|
+
if (typeof spec === "string")
|
|
159
|
+
clipNames.add(spec);
|
|
160
|
+
else
|
|
161
|
+
for (const name of spec)
|
|
162
|
+
clipNames.add(name);
|
|
163
|
+
}
|
|
164
|
+
const oneShotActions = new Map();
|
|
165
|
+
for (const name of clipNames) {
|
|
166
|
+
const found = THREE.AnimationClip.findByName(gltf.animations, name);
|
|
167
|
+
if (found === null)
|
|
168
|
+
continue;
|
|
169
|
+
const oneShotAction = mixer.clipAction(found);
|
|
170
|
+
oneShotAction.setLoop(THREE.LoopOnce, 1);
|
|
171
|
+
oneShotAction.enabled = true;
|
|
172
|
+
oneShotActions.set(name, oneShotAction);
|
|
173
|
+
}
|
|
174
|
+
onOneShotFinished = ({ action }) => {
|
|
175
|
+
const active = activeOneShotRef.current;
|
|
176
|
+
if (active === null || action !== active.action || active.isDeath)
|
|
177
|
+
return;
|
|
178
|
+
const machine = stateActionsRef.current;
|
|
179
|
+
const back = machine?.actions[machine.active];
|
|
180
|
+
action.fadeOut(0.15);
|
|
181
|
+
if (back !== undefined)
|
|
182
|
+
back.reset().fadeIn(0.15).play();
|
|
183
|
+
activeOneShotRef.current = null;
|
|
184
|
+
};
|
|
185
|
+
mixer.addEventListener("finished", onOneShotFinished);
|
|
186
|
+
oneShotPlayRef.current = (event) => {
|
|
187
|
+
const active = activeOneShotRef.current;
|
|
188
|
+
if (active !== null && active.isDeath)
|
|
189
|
+
return;
|
|
190
|
+
const clipName = resolveOneShotClip(oneShots, event, Math.random());
|
|
191
|
+
if (clipName === null)
|
|
192
|
+
return;
|
|
193
|
+
const oneShotAction = oneShotActions.get(clipName);
|
|
194
|
+
if (oneShotAction === undefined)
|
|
195
|
+
return;
|
|
196
|
+
const machine = stateActionsRef.current;
|
|
197
|
+
machine?.actions[machine.active]?.fadeOut(0.1);
|
|
198
|
+
if (active !== null && active.action !== oneShotAction)
|
|
199
|
+
active.action.stop();
|
|
200
|
+
oneShotAction.clampWhenFinished = event === "death";
|
|
201
|
+
oneShotAction.reset().fadeIn(0.1).play();
|
|
202
|
+
activeOneShotRef.current = { action: oneShotAction, isDeath: event === "death" };
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
return () => {
|
|
206
|
+
if (onOneShotFinished !== null)
|
|
207
|
+
mixer.removeEventListener("finished", onOneShotFinished);
|
|
208
|
+
oneShotPlayRef.current = null;
|
|
209
|
+
activeOneShotRef.current = null;
|
|
210
|
+
mixer.stopAllAction();
|
|
211
|
+
mixerRef.current = null;
|
|
212
|
+
stateActionsRef.current = null;
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
const clip = (animation.clip !== undefined ? THREE.AnimationClip.findByName(gltf.animations, animation.clip) : undefined) ??
|
|
216
|
+
gltf.animations[0];
|
|
217
|
+
const action = mixer.clipAction(clip);
|
|
218
|
+
action.setLoop(animation.loop === false ? THREE.LoopOnce : THREE.LoopRepeat, Infinity);
|
|
219
|
+
action.clampWhenFinished = animation.loop === false;
|
|
220
|
+
action.timeScale = animation.timeScale ?? 1;
|
|
221
|
+
action.enabled = true;
|
|
222
|
+
action.paused = animation.paused === true;
|
|
223
|
+
action.play();
|
|
224
|
+
if (animation.time !== undefined)
|
|
225
|
+
action.time = animation.time;
|
|
226
|
+
mixer.update(0);
|
|
227
|
+
mixerRef.current = mixer;
|
|
228
|
+
animationPausedRef.current = animation.paused === true;
|
|
229
|
+
return () => {
|
|
230
|
+
mixer.stopAllAction();
|
|
231
|
+
mixerRef.current = null;
|
|
232
|
+
};
|
|
233
|
+
}, [
|
|
234
|
+
scene,
|
|
235
|
+
gltf,
|
|
236
|
+
animation?.clip,
|
|
237
|
+
animation?.loop,
|
|
238
|
+
animation?.timeScale,
|
|
239
|
+
animation?.paused,
|
|
240
|
+
animation?.time,
|
|
241
|
+
states,
|
|
242
|
+
oneShots,
|
|
243
|
+
]);
|
|
244
|
+
useEffect(() => {
|
|
245
|
+
if (instanceId === undefined || oneShots === undefined)
|
|
246
|
+
return;
|
|
247
|
+
const fire = (event) => oneShotPlayRef.current?.(event);
|
|
248
|
+
const offAnimation = ctx.game.events.on("entity.animation", (event) => {
|
|
249
|
+
if (event.instanceId === instanceId)
|
|
250
|
+
fire(event.event);
|
|
251
|
+
});
|
|
252
|
+
const offHit = ctx.game.events.on("combat.hitReaction", (event) => {
|
|
253
|
+
if (event.instanceId === instanceId)
|
|
254
|
+
fire("hit");
|
|
255
|
+
});
|
|
256
|
+
const offDied = ctx.game.events.on("entity.died", (event) => {
|
|
257
|
+
if (event.instanceId === instanceId)
|
|
258
|
+
fire("death");
|
|
259
|
+
});
|
|
260
|
+
return () => {
|
|
261
|
+
offAnimation();
|
|
262
|
+
offHit();
|
|
263
|
+
offDied();
|
|
264
|
+
};
|
|
265
|
+
}, [ctx, instanceId, oneShots]);
|
|
266
|
+
const paintCanvasRef = useRef(null);
|
|
267
|
+
const paintDrawnCountRef = useRef(0);
|
|
268
|
+
const paintVersionRef = useRef(-1);
|
|
269
|
+
const materialCacheRef = useRef(null);
|
|
270
|
+
useEffect(() => () => {
|
|
271
|
+
if (paintCanvasRef.current !== null) {
|
|
272
|
+
disposePaintCanvas(paintCanvasRef.current);
|
|
273
|
+
paintCanvasRef.current = null;
|
|
274
|
+
}
|
|
275
|
+
paintDrawnCountRef.current = 0;
|
|
276
|
+
paintVersionRef.current = -1;
|
|
277
|
+
materialCacheRef.current = null;
|
|
278
|
+
}, [scene]);
|
|
279
|
+
useFrame((_state, delta) => {
|
|
280
|
+
const stateMachine = stateActionsRef.current;
|
|
281
|
+
if (stateMachine !== null && states !== undefined && instanceId !== undefined && delta > 0) {
|
|
282
|
+
const entity = ctx.scene.entity.get(instanceId);
|
|
283
|
+
if (entity !== null) {
|
|
284
|
+
const [x, , z] = entity.position;
|
|
285
|
+
if (stateMachine.lastPos !== null) {
|
|
286
|
+
const instantSpeed = Math.hypot(x - stateMachine.lastPos[0], z - stateMachine.lastPos[2]) / delta;
|
|
287
|
+
stateMachine.smoothedSpeed +=
|
|
288
|
+
(instantSpeed - stateMachine.smoothedSpeed) * Math.min(1, delta * 12);
|
|
289
|
+
}
|
|
290
|
+
stateMachine.lastPos = [x, entity.position[1], z];
|
|
291
|
+
const walkSpeed = states.walkSpeed ?? 0.5;
|
|
292
|
+
const runSpeed = states.runSpeed ?? 6;
|
|
293
|
+
const next = stateMachine.smoothedSpeed < walkSpeed
|
|
294
|
+
? "idle"
|
|
295
|
+
: stateMachine.actions.run !== undefined && stateMachine.smoothedSpeed >= runSpeed
|
|
296
|
+
? "run"
|
|
297
|
+
: "walk";
|
|
298
|
+
if (next !== stateMachine.active) {
|
|
299
|
+
if (activeOneShotRef.current === null) {
|
|
300
|
+
const fade = states.fadeSec ?? 0.2;
|
|
301
|
+
const from = stateMachine.actions[stateMachine.active];
|
|
302
|
+
const to = stateMachine.actions[next];
|
|
303
|
+
if (from !== undefined && to !== undefined) {
|
|
304
|
+
to.reset().fadeIn(fade).play();
|
|
305
|
+
from.fadeOut(fade);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
stateMachine.active = next;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
if (mixerRef.current !== null && !animationPausedRef.current)
|
|
313
|
+
mixerRef.current.update(delta);
|
|
314
|
+
if (instanceId === undefined)
|
|
315
|
+
return;
|
|
316
|
+
const paint = ctx.scene.entity.paint;
|
|
317
|
+
const version = paint.version(instanceId);
|
|
318
|
+
if (version === paintVersionRef.current)
|
|
319
|
+
return;
|
|
320
|
+
paintVersionRef.current = version;
|
|
321
|
+
const strokes = paint.strokes(instanceId);
|
|
322
|
+
const cache = cacheStandardMaterials(scene, materialCacheRef.current);
|
|
323
|
+
materialCacheRef.current = cache;
|
|
324
|
+
if (paintCanvasRef.current === null) {
|
|
325
|
+
if (strokes.length === 0)
|
|
326
|
+
return;
|
|
327
|
+
const seed = cache.materials[0];
|
|
328
|
+
if (seed === undefined)
|
|
329
|
+
return;
|
|
330
|
+
const paintCanvas = createPaintCanvas(seed);
|
|
331
|
+
paintCanvasRef.current = paintCanvas;
|
|
332
|
+
applyPaintTextureToMaterials(cache.materials, paintCanvas);
|
|
333
|
+
}
|
|
334
|
+
paintDrawnCountRef.current = syncPaintCanvas(paintCanvasRef.current, cache.seedColor, strokes, paintDrawnCountRef.current);
|
|
335
|
+
});
|
|
336
|
+
return (_jsxs(_Fragment, { children: [_jsx("primitive", { object: scene, position: position, scale: [scale, scale, scale] }), material?.maps !== undefined ? _jsx(ModelMaterialMapsApplier, { scene: scene, maps: material.maps }) : null, (model.attachments ?? []).map((attachment, index) => typeof attachment.model === "string" ? null : (_jsx(BoneAttachment, { rig: scene, model: attachment.model, slot: attachment.slot, position: attachment.position, rotation: attachment.rotation, scale: attachment.scale }, `${attachment.slot}-${index}`))), (model.parts ?? []).map((part, index) => typeof part.model === "string" ? null : (_jsx(ModelPartGroup, { model: part.model, position: part.position, rotation: part.rotation, scale: part.scale }, index)))] }));
|
|
337
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type ThreeElements } from "@react-three/fiber";
|
|
2
|
+
import * as THREE from "three";
|
|
3
|
+
/**
|
|
4
|
+
* Shared, lazily-built procedural detail maps (a subtle tangent-space normal map plus a
|
|
5
|
+
* roughness-variation map) used by every un-modeled primitive so a flat colored box reads as a
|
|
6
|
+
* surface, not plastic. Pure JS `DataTexture` — no canvas/DOM, so it imports safely under `bun test`.
|
|
7
|
+
* @internal shell-internal default render surface; games never import it.
|
|
8
|
+
*/
|
|
9
|
+
export declare function detailMaps(): {
|
|
10
|
+
normal: THREE.DataTexture;
|
|
11
|
+
roughness: THREE.DataTexture;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Props for {@link DefaultSurface}: a required base `color` plus optional PBR overrides.
|
|
15
|
+
* @internal shell-internal default render surface; games never import it.
|
|
16
|
+
*/
|
|
17
|
+
export interface DefaultSurfaceProps extends Omit<ThreeElements["meshStandardMaterial"], "args" | "normalMap" | "roughnessMap"> {
|
|
18
|
+
color: THREE.ColorRepresentation;
|
|
19
|
+
roughness?: number;
|
|
20
|
+
metalness?: number;
|
|
21
|
+
detail?: boolean;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The upgraded default material for un-modeled entities/objects: tuned PBR roughness/metalness plus
|
|
25
|
+
* the shared procedural detail maps, so primitive fallbacks catch light and image-based reflections
|
|
26
|
+
* instead of reading as flat plastic. Drop-in replacement for a bare `<meshStandardMaterial color>`.
|
|
27
|
+
* @internal shell-internal default render surface; games never import it.
|
|
28
|
+
*/
|
|
29
|
+
export declare function DefaultSurface({ color, roughness, metalness, detail, ...rest }: DefaultSurfaceProps): import("react").JSX.Element;
|