@jgengine/shell 0.19.0 → 0.22.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 +364 -0
- package/dist/GameHost.d.ts +12 -1
- package/dist/GameHost.js +14 -1
- package/dist/GamePlayerShell.js +113 -15
- package/dist/Shell3dPresentation.d.ts +2 -2
- package/dist/Shell3dPresentation.js +49 -7
- package/dist/ShellChrome.d.ts +2 -1
- package/dist/ShellChrome.js +22 -6
- package/dist/ShellHudPresentation.d.ts +11 -1
- package/dist/ShellHudPresentation.js +20 -6
- package/dist/audio/audioEngine.d.ts +16 -0
- package/dist/audio/audioEngine.js +178 -89
- package/dist/audio/audioWire.js +2 -2
- package/dist/audio/synthEngine.d.ts +7 -1
- package/dist/audio/synthEngine.js +103 -38
- package/dist/camera/GameCameraRig.js +2 -1
- package/dist/camera/GameFirstPersonCamera.d.ts +2 -2
- package/dist/camera/GameFirstPersonCamera.js +17 -13
- package/dist/camera/cameraRigs.js +8 -2
- package/dist/camera/chaseHeading.d.ts +9 -0
- package/dist/camera/chaseHeading.js +10 -0
- package/dist/camera/customMuzzle.d.ts +14 -0
- package/dist/camera/customMuzzle.js +68 -0
- package/dist/camera/index.d.ts +1 -0
- package/dist/camera/index.js +1 -0
- package/dist/camera/rigResolve.d.ts +3 -0
- package/dist/camera/rigResolve.js +11 -0
- package/dist/combat/ProjectileModels.d.ts +31 -0
- package/dist/combat/ProjectileModels.js +70 -0
- package/dist/commandSink.d.ts +1 -2
- package/dist/commandSink.js +3 -2
- package/dist/defineGame.d.ts +12 -5
- package/dist/defineGame.js +25 -10
- package/dist/devtools/DevtoolsOverlay.d.ts +2 -69
- package/dist/devtools/DevtoolsOverlay.js +14 -168
- package/dist/devtools/DevtoolsRuntime.d.ts +7 -0
- package/dist/devtools/DevtoolsRuntime.js +61 -0
- package/dist/devtools/agentBridge.js +1 -1
- package/dist/devtools/devtoolsReports.d.ts +68 -0
- package/dist/devtools/devtoolsReports.js +100 -0
- package/dist/devtools/latencyInstrumentation.d.ts +3 -0
- package/dist/devtools/latencyInstrumentation.js +11 -0
- package/dist/devtools/playtestProbe.d.ts +4 -0
- package/dist/devtools/playtestProbe.js +22 -0
- package/dist/drivers/FrameDriver.js +25 -21
- package/dist/drivers/HudOnlyDriver.d.ts +6 -1
- package/dist/drivers/HudOnlyDriver.js +32 -14
- package/dist/environment/EnvironmentScene.js +19 -15
- package/dist/environment/terrainGroundColor.d.ts +5 -4
- package/dist/environment/terrainGroundColor.js +19 -8
- package/dist/inputSink.d.ts +6 -6
- package/dist/inputSink.js +60 -21
- package/dist/lazyShell3dPresentation.d.ts +9 -0
- package/dist/lazyShell3dPresentation.js +24 -0
- package/dist/materialOverride.d.ts +42 -0
- package/dist/materialOverride.js +95 -36
- package/dist/multiplayer.d.ts +33 -3
- package/dist/multiplayer.js +105 -22
- package/dist/pointer/pointerService.d.ts +2 -0
- package/dist/pointer/pointerService.js +12 -2
- package/dist/postfx/PostProcessing.d.ts +10 -0
- package/dist/postfx/PostProcessing.js +9 -41
- package/dist/postfx/gradeShader.d.ts +1 -1
- package/dist/postfx/gradeShader.js +30 -2
- package/dist/postfx/shadowPrepass.d.ts +3 -0
- package/dist/postfx/shadowPrepass.js +18 -0
- package/dist/presentationRecovery.d.ts +75 -0
- package/dist/presentationRecovery.js +204 -0
- package/dist/presentationRecovery3d.d.ts +4 -0
- package/dist/presentationRecovery3d.js +32 -0
- package/dist/presentationResolve.d.ts +8 -1
- package/dist/presentationResolve.js +5 -2
- package/dist/render/CascadedShadows.d.ts +7 -2
- package/dist/render/CascadedShadows.js +98 -20
- package/dist/render/EnvironmentLighting.js +74 -35
- package/dist/render/HairCards.d.ts +7 -0
- package/dist/render/HairCards.js +23 -0
- package/dist/render/SceneModels.js +51 -77
- package/dist/render/authoredSurfaceMaterial.d.ts +43 -0
- package/dist/render/authoredSurfaceMaterial.js +108 -0
- package/dist/render/materialAppearanceSignals.d.ts +23 -0
- package/dist/render/materialAppearanceSignals.js +58 -0
- package/dist/render/materialAsset.d.ts +98 -0
- package/dist/render/materialAsset.js +454 -0
- package/dist/render/measureBounds.js +16 -3
- package/dist/render/measureCollisionMesh.js +8 -1
- package/dist/render/modelAssets.d.ts +8 -0
- package/dist/render/modelAssets.js +43 -0
- package/dist/render/modelBindPose.d.ts +5 -0
- package/dist/render/modelBindPose.js +35 -0
- package/dist/render/modelLoad.d.ts +3 -0
- package/dist/render/modelLoad.js +9 -0
- package/dist/render/modelNodeVisibility.d.ts +6 -0
- package/dist/render/modelNodeVisibility.js +18 -0
- package/dist/render/modelRender.d.ts +18 -1
- package/dist/render/modelRender.js +81 -3
- package/dist/render/projectedOverdraw.d.ts +35 -0
- package/dist/render/projectedOverdraw.js +204 -0
- package/dist/render/resolveModel.d.ts +12 -2
- package/dist/render/resolveModel.js +21 -4
- package/dist/render/rigNode.d.ts +26 -0
- package/dist/render/rigNode.js +32 -0
- package/dist/render/spriteRender.d.ts +4 -0
- package/dist/render/spriteRender.js +5 -0
- package/dist/render/staticShapeInstances.d.ts +34 -0
- package/dist/render/staticShapeInstances.js +78 -0
- package/dist/render/useFootIk.js +76 -11
- package/dist/render/useModelAnimation.d.ts +28 -2
- package/dist/render/useModelAnimation.js +194 -69
- package/dist/render/useModelInstance.d.ts +25 -0
- package/dist/render/useModelInstance.js +43 -0
- package/dist/scatter/scatterModels.d.ts +3 -2
- package/dist/scatter/scatterModels.js +15 -7
- package/dist/scene/AuthoredScene.d.ts +14 -6
- package/dist/scene/AuthoredScene.js +30 -9
- package/dist/scene/GeneratedAssetRenderer.js +11 -42
- package/dist/scene/GrassFieldRenderer.js +20 -2
- package/dist/settings/SettingsMenu.js +11 -18
- package/dist/settings/SettingsRuntime.d.ts +5 -0
- package/dist/settings/SettingsRuntime.js +29 -2
- package/dist/shellControlSuspension.d.ts +18 -0
- package/dist/shellControlSuspension.js +28 -0
- package/dist/shellInputPublication.d.ts +28 -0
- package/dist/shellInputPublication.js +49 -0
- package/dist/shellPointerInput.d.ts +18 -0
- package/dist/shellPointerInput.js +49 -0
- package/dist/structures/BuildingKitBatch.d.ts +3 -1
- package/dist/structures/BuildingKitBatch.js +34 -59
- package/dist/structures/BuildingRenderBudget.d.ts +23 -0
- package/dist/structures/BuildingRenderBudget.js +54 -0
- package/dist/structures/GeneratedBuilding.js +13 -8
- package/dist/structures/buildingKitMaterials.d.ts +20 -0
- package/dist/structures/buildingKitMaterials.js +70 -0
- package/dist/structures/buildingSpatialBatch.d.ts +11 -0
- package/dist/structures/buildingSpatialBatch.js +45 -0
- package/dist/terrain/CarvedTerrain.d.ts +2 -1
- package/dist/terrain/CarvedTerrain.js +2 -2
- package/dist/terrain/GrassField.d.ts +3 -1
- package/dist/terrain/GrassField.js +61 -6
- package/dist/terrain/grassGeometry.d.ts +13 -0
- package/dist/terrain/grassGeometry.js +57 -1
- package/dist/terrain/grassMaterial.js +2 -2
- package/dist/terrain/index.d.ts +1 -0
- package/dist/terrain/index.js +1 -0
- package/dist/terrain/terrainDetailMaterial.d.ts +3 -3
- package/dist/terrain/terrainDetailMaterial.js +7 -7
- package/dist/terrain/terrainMath.d.ts +2 -0
- package/dist/terrain/terrainMath.js +2 -0
- package/dist/terrain/terrainPaintColor.d.ts +11 -0
- package/dist/terrain/terrainPaintColor.js +47 -0
- package/dist/touch/TouchControlsOverlay.js +4 -4
- package/dist/useShellMultiplayerSync.js +4 -0
- package/dist/vfx/ParticleField.d.ts +23 -20
- package/dist/vfx/ParticleField.js +168 -50
- package/dist/visibility/CullingProvider.js +2 -62
- package/dist/visibility/cullingDriver.d.ts +9 -0
- package/dist/visibility/cullingDriver.js +68 -0
- package/dist/weather/AuthoredWeatherLayer.d.ts +16 -0
- package/dist/weather/AuthoredWeatherLayer.js +31 -0
- package/dist/weather/DustField.d.ts +2 -1
- package/dist/weather/DustField.js +17 -3
- package/dist/weather/FireSpreadLayer.d.ts +4 -2
- package/dist/weather/FireSpreadLayer.js +5 -11
- package/dist/weather/LightningStrike.d.ts +4 -1
- package/dist/weather/LightningStrike.js +13 -17
- package/dist/weather/RainField.d.ts +2 -1
- package/dist/weather/RainField.js +17 -3
- package/dist/weather/RainImpactField.d.ts +16 -0
- package/dist/weather/RainImpactField.js +89 -0
- package/dist/weather/SnowField.d.ts +2 -1
- package/dist/weather/SnowField.js +17 -3
- package/dist/weather/WeatherLayer.d.ts +11 -2
- package/dist/weather/WeatherLayer.js +30 -6
- package/dist/weather/index.d.ts +2 -0
- package/dist/weather/index.js +2 -0
- package/dist/weather/weatherMath.d.ts +2 -0
- package/dist/weather/weatherMath.js +8 -0
- package/dist/weather/weatherShelter.d.ts +18 -0
- package/dist/weather/weatherShelter.js +75 -0
- package/dist/weather/weatherUniforms.d.ts +40 -1
- package/dist/weather/weatherUniforms.js +29 -9
- package/dist/world/AuthoredFlocks.d.ts +22 -0
- package/dist/world/AuthoredFlocks.js +87 -0
- package/dist/world/StaticObjectModels.d.ts +8 -0
- package/dist/world/StaticObjectModels.js +75 -0
- package/dist/world/WorldHud.d.ts +14 -6
- package/dist/world/WorldHud.js +25 -12
- package/dist/world/WorldParticles.d.ts +10 -23
- package/dist/world/WorldParticles.js +209 -61
- package/dist/world/WorldScene.d.ts +3 -1
- package/dist/world/WorldScene.js +41 -26
- package/dist/world/staticObjectBatches.d.ts +35 -0
- package/dist/world/staticObjectBatches.js +163 -0
- package/dist/world/worldBarSamples.d.ts +37 -33
- package/dist/world/worldBarSamples.js +117 -75
- package/package.json +4 -4
- package/skills/ce-handoff/SKILL.md +1 -1
- package/skills/game-design/SKILL.md +2 -0
- package/skills/jgengine/SKILL.md +23 -2
- package/skills/jgengine/api.md +28 -17
- package/skills/jgengine/recipes/minimal-game.md +7 -2
- package/skills/jgengine-ui/SKILL.md +3 -1
- package/skills/jgengine-ui/api.md +219 -70
- package/skills/jgengine-ui/capabilities.md +119 -1
- package/skills/jgengine-ui/reference-primitives.md +93 -3
- package/skills/jgengine-ui/reference.md +2 -0
- package/skills/jgengine-world/SKILL.md +12 -3
- package/skills/jgengine-world/api.md +201 -58
- package/skills/jgengine-world/capabilities.md +116 -3
- package/skills/jgengine-world/recipes/character-animation.md +31 -8
- package/skills/jgengine-world/recipes/controls.md +4 -0
- package/skills/jgengine-world/recipes/npc-senses.md +12 -0
- package/skills/jgengine-world/recipes/vehicle-feel.md +3 -1
- package/skills/jgengine-world/reference.md +167 -5
- package/skills/jgengine-world/visibility.md +28 -1
|
@@ -3,12 +3,17 @@ import { dispatchBoundAction, heldActionsFor, shouldFireBoundAction } from "../b
|
|
|
3
3
|
import { advanceBehaviors } from "@jgengine/core/scene/behaviorRuntime";
|
|
4
4
|
import { playControlsActive } from "@jgengine/core/game/controlGate";
|
|
5
5
|
import { devtools } from "@jgengine/core/devtools/devtools";
|
|
6
|
+
import { isServerAuthoritative } from "@jgengine/core/runtime/adapter";
|
|
7
|
+
import { resolveCommandSink } from "../commandSink.js";
|
|
8
|
+
import { inputFramesEqual, resolveInputSink } from "../inputSink.js";
|
|
6
9
|
import { EMPTY_RESERVED, NO_ACTIONS } from "../shellConstants.js";
|
|
7
|
-
export function HudOnlyDriver({ ctx, playable, tracker, pointerAxisRef, gateRef, onRuntimeError, }) {
|
|
10
|
+
export function HudOnlyDriver({ ctx, multiplayer, serverIdRef, playable, tracker, pointerAxisRef, gateRef, onRuntimeError, }) {
|
|
8
11
|
const hasReportedTickError = useRef(false);
|
|
9
12
|
const repeatFiredAtRef = useRef(new Map());
|
|
10
13
|
const lastFrameRef = useRef(null);
|
|
11
14
|
const inputActions = useMemo(() => Object.keys(playable.game.input ?? {}), [playable]);
|
|
15
|
+
const lastSentInputRef = useRef(null);
|
|
16
|
+
const serverAuthoritative = isServerAuthoritative(playable.game.multiplayer) && multiplayer !== null;
|
|
12
17
|
useEffect(() => {
|
|
13
18
|
let frameId;
|
|
14
19
|
const tick = (now) => {
|
|
@@ -17,8 +22,19 @@ export function HudOnlyDriver({ ctx, playable, tracker, pointerAxisRef, gateRef,
|
|
|
17
22
|
lastFrameRef.current = now;
|
|
18
23
|
if (last === null)
|
|
19
24
|
return;
|
|
25
|
+
const sendInput = () => {
|
|
26
|
+
if (!serverAuthoritative || serverIdRef.current === null)
|
|
27
|
+
return;
|
|
28
|
+
const frame = { held: ctx.input.held(), pointer: ctx.input.pointer(), analog: ctx.input.analog() };
|
|
29
|
+
if (lastSentInputRef.current !== null && inputFramesEqual(lastSentInputRef.current, frame))
|
|
30
|
+
return;
|
|
31
|
+
lastSentInputRef.current = frame;
|
|
32
|
+
resolveInputSink({ serverAuthoritative, backend: multiplayer?.backend ?? null, serverId: serverIdRef.current }).send(frame);
|
|
33
|
+
};
|
|
20
34
|
if (gateRef.current || !playControlsActive(ctx)) {
|
|
21
|
-
ctx.input.publish(heldActionsFor(tracker, NO_ACTIONS));
|
|
35
|
+
ctx.input.publish(heldActionsFor(tracker, NO_ACTIONS), { reset: true });
|
|
36
|
+
ctx.input.publishAnalog(null);
|
|
37
|
+
sendInput();
|
|
22
38
|
return;
|
|
23
39
|
}
|
|
24
40
|
const rawDt = (now - last) / 1000;
|
|
@@ -26,26 +42,28 @@ export function HudOnlyDriver({ ctx, playable, tracker, pointerAxisRef, gateRef,
|
|
|
26
42
|
try {
|
|
27
43
|
let endPhase = devtools.profile.begin("time+input");
|
|
28
44
|
const dt = Math.min(rawDt, 0.05);
|
|
29
|
-
ctx.input.publish(heldActionsFor(tracker, tracker.actions()));
|
|
45
|
+
ctx.input.publish(heldActionsFor(tracker, tracker.actions()), {});
|
|
30
46
|
ctx.input.publishPointer(pointerAxisRef.current);
|
|
47
|
+
sendInput();
|
|
31
48
|
endPhase();
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
49
|
+
if (!serverAuthoritative)
|
|
50
|
+
ctx.sim.advance(dt, (stepDt, _tick, gameDt) => {
|
|
51
|
+
ctx.input.beginStep();
|
|
52
|
+
ctx.sim.runStages("beforeMovement", stepDt);
|
|
53
|
+
ctx.sim.runStages("afterMovement", stepDt);
|
|
54
|
+
devtools.profile.measure("onTick", () => {
|
|
55
|
+
playable.loop.onTick(ctx, gameDt);
|
|
56
|
+
});
|
|
57
|
+
advanceBehaviors(ctx, gameDt);
|
|
58
|
+
ctx.sim.runStages("afterTick", stepDt);
|
|
38
59
|
});
|
|
39
|
-
advanceBehaviors(ctx, gameDt);
|
|
40
|
-
ctx.sim.runStages("afterTick", stepDt);
|
|
41
|
-
});
|
|
42
60
|
endPhase = devtools.profile.begin("actions");
|
|
43
61
|
const nowMs = performance.now();
|
|
44
62
|
for (const action of inputActions) {
|
|
45
63
|
if (!shouldFireBoundAction(tracker, action, playable.game.input, repeatFiredAtRef.current, nowMs))
|
|
46
64
|
continue;
|
|
47
65
|
repeatFiredAtRef.current.set(action, nowMs);
|
|
48
|
-
dispatchBoundAction(ctx, action, 0, 0, { yaw: 0, pitch: 0 }, EMPTY_RESERVED);
|
|
66
|
+
dispatchBoundAction(ctx, action, 0, 0, { yaw: 0, pitch: 0 }, EMPTY_RESERVED, resolveCommandSink(ctx, { serverAuthoritative, backend: multiplayer?.backend ?? null, serverId: serverIdRef.current }));
|
|
49
67
|
}
|
|
50
68
|
tracker.endFrame();
|
|
51
69
|
endPhase();
|
|
@@ -60,6 +78,6 @@ export function HudOnlyDriver({ ctx, playable, tracker, pointerAxisRef, gateRef,
|
|
|
60
78
|
};
|
|
61
79
|
frameId = requestAnimationFrame(tick);
|
|
62
80
|
return () => cancelAnimationFrame(frameId);
|
|
63
|
-
}, [ctx, playable, tracker, pointerAxisRef, gateRef, onRuntimeError, inputActions]);
|
|
81
|
+
}, [ctx, playable, tracker, pointerAxisRef, gateRef, onRuntimeError, inputActions, multiplayer, serverIdRef, serverAuthoritative]);
|
|
64
82
|
return null;
|
|
65
83
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useMemo, useRef } from "react";
|
|
3
3
|
import { useFrame } from "@react-three/fiber";
|
|
4
|
-
import {
|
|
4
|
+
import { PresentationAssetBoundary } from "../presentationRecovery.js";
|
|
5
|
+
import { PresentationRecoveredDraw, useRecoverableTexture } from "../presentationRecovery3d.js";
|
|
5
6
|
import * as THREE from "three";
|
|
6
7
|
import { useGameContext } from "@jgengine/react/provider";
|
|
7
8
|
import { resolveBuildingPalette } from "@jgengine/core/world/buildings";
|
|
@@ -9,6 +10,8 @@ import { resolveStructureBuildings } from "@jgengine/core/world/environmentSumma
|
|
|
9
10
|
import { findRoadJunctions } from "@jgengine/core/world/streets";
|
|
10
11
|
import { createTerrainPaletteSampler, resolveEnvironmentField, resolveTerrainDetail, resolveTerrainPalette, } from "@jgengine/core/world/terrain";
|
|
11
12
|
import { useDisposable } from "../render/useDisposable.js";
|
|
13
|
+
import { editableTerrainFromSnapshot } from "@jgengine/core/world/terraform";
|
|
14
|
+
import { createTerrainPaintColorSampler } from "../terrain/terrainPaintColor.js";
|
|
12
15
|
import { GroundPad } from "./GroundPad.js";
|
|
13
16
|
import { RoadJunctions } from "./RoadJunctions.js";
|
|
14
17
|
import { buildAuthoredRoadNetwork, RoadRibbons } from "./RoadRibbons.js";
|
|
@@ -20,14 +23,13 @@ import { createTerrainDetailMaterial } from "../terrain/terrainDetailMaterial.js
|
|
|
20
23
|
import { Ocean } from "../water/Ocean.js";
|
|
21
24
|
import { RainField } from "../weather/RainField.js";
|
|
22
25
|
import { SnowField } from "../weather/SnowField.js";
|
|
23
|
-
import { WeatherUniformProvider } from "../weather/weatherUniforms.js";
|
|
24
26
|
function TexturedTerrainGround({ detail, layout, }) {
|
|
25
27
|
const maps = detail.material.maps;
|
|
26
|
-
const textures =
|
|
28
|
+
const textures = useRecoverableTexture({
|
|
27
29
|
color: maps.color,
|
|
28
30
|
normal: maps.normal,
|
|
29
|
-
roughness: maps.roughness,
|
|
30
|
-
ao: maps.ao,
|
|
31
|
+
...(maps.roughness !== undefined ? { roughness: maps.roughness } : {}),
|
|
32
|
+
...(maps.ao !== undefined ? { ao: maps.ao } : {}),
|
|
31
33
|
});
|
|
32
34
|
useEffect(() => {
|
|
33
35
|
textures.color.colorSpace = THREE.SRGBColorSpace;
|
|
@@ -38,9 +40,10 @@ function TexturedTerrainGround({ detail, layout, }) {
|
|
|
38
40
|
}
|
|
39
41
|
}, [textures]);
|
|
40
42
|
const material = useDisposable(() => createTerrainDetailMaterial(detail, textures).material, [detail, textures]);
|
|
41
|
-
return (_jsx(CarvedTerrain, { field: layout.field, size: layout.size, segments: layout.segments, colors: layout.colors, heightRange: layout.heightRange, paletteAt: layout.paletteAt, center: layout.center, roughness: 0.94, surfaceMaterial: material }));
|
|
43
|
+
return (_jsxs(_Fragment, { children: [_jsx(PresentationRecoveredDraw, {}), _jsx(CarvedTerrain, { field: layout.field, size: layout.size, segments: layout.segments, colors: layout.colors, heightRange: layout.heightRange, paletteAt: layout.paletteAt, paintColorAt: layout.paintColorAt, center: layout.center, roughness: 0.94, surfaceMaterial: material })] }));
|
|
42
44
|
}
|
|
43
|
-
function TerrainGround({ terrain, field, center, }) {
|
|
45
|
+
function TerrainGround({ terrain, field, center, sculpt, }) {
|
|
46
|
+
const paintColorAt = useMemo(() => sculpt === undefined ? undefined : createTerrainPaintColorSampler(editableTerrainFromSnapshot(sculpt)), [sculpt]);
|
|
44
47
|
const palette = useMemo(() => resolveTerrainPalette(terrain), [terrain]);
|
|
45
48
|
const paletteAt = useMemo(() => {
|
|
46
49
|
const hasRegions = terrain.materialRegions !== undefined && terrain.materialRegions.length > 0;
|
|
@@ -69,9 +72,9 @@ function TerrainGround({ terrain, field, center, }) {
|
|
|
69
72
|
: createTerrainDetailMaterial(resolvedDetail).material, [resolvedDetail]);
|
|
70
73
|
useEffect(() => () => proceduralMaterial?.dispose(), [proceduralMaterial]);
|
|
71
74
|
if (resolvedDetail?.material !== undefined) {
|
|
72
|
-
return (_jsx(TexturedTerrainGround, { detail: resolvedDetail, layout: { field, size, segments: terrain.segments, colors, heightRange, paletteAt, center } }));
|
|
75
|
+
return (_jsx(PresentationAssetBoundary, { children: _jsx(TexturedTerrainGround, { detail: resolvedDetail, layout: { field, size, segments: terrain.segments, colors, heightRange, paletteAt, paintColorAt, center } }) }));
|
|
73
76
|
}
|
|
74
|
-
return (_jsx(CarvedTerrain, { field: field, size: size, segments: terrain.segments, colors: colors, heightRange: heightRange, paletteAt: paletteAt, center: center, roughness: 0.94, surfaceMaterial: proceduralMaterial }));
|
|
77
|
+
return (_jsx(CarvedTerrain, { field: field, size: size, segments: terrain.segments, colors: colors, heightRange: heightRange, paletteAt: paletteAt, paintColorAt: paintColorAt, center: center, roughness: 0.94, surfaceMaterial: proceduralMaterial }));
|
|
75
78
|
}
|
|
76
79
|
function areaCenter(area) {
|
|
77
80
|
return area.position ?? [0, 0];
|
|
@@ -88,8 +91,8 @@ function Vegetation({ grass, field, groundColor, }) {
|
|
|
88
91
|
return (_jsx("group", { position: [cx, 0, cz], children: _jsx(GrassField, { area: area, count: Math.max(1500, Math.min(250000, Math.ceil(grass.density * grass.area.w * grass.area.d))), density: grass.density, seed: grass.seed, bladeHeight: grass.bladeHeight, bladeWidth: grass.bladeWidth, bladeBend: grass.bladeBend, tuftRadius: grass.tuftRadius, colorVariation: grass.colorVariation, normalLift: grass.normalLift, heightAt: heightAt, colorBase: colorBase, colorTip: colorTip, colorGround: groundColor, wind: wind, edgeFeather: Math.min(8, Math.max(1.5, Math.min(grass.area.w, grass.area.d) * 0.12)), frustumCulled: true }) }));
|
|
89
92
|
}
|
|
90
93
|
/** Terrain color under a grass patch center — so blade roots blend into the ground they stand on. */
|
|
91
|
-
function vegetationGroundColor(terrain, field, grass) {
|
|
92
|
-
const sampler = terrainGroundColorSampler(terrain, field);
|
|
94
|
+
function vegetationGroundColor(terrain, field, grass, sculpt) {
|
|
95
|
+
const sampler = terrainGroundColorSampler(terrain, field, sculpt);
|
|
93
96
|
if (sampler === undefined)
|
|
94
97
|
return undefined;
|
|
95
98
|
const [cx, cz] = areaCenter(grass.area);
|
|
@@ -99,14 +102,15 @@ function weatherVolume(area) {
|
|
|
99
102
|
return [area.w, area.h ?? 60, area.d];
|
|
100
103
|
}
|
|
101
104
|
function Precipitation({ weather }) {
|
|
105
|
+
const ctx = useGameContext();
|
|
102
106
|
const [cx, cz] = areaCenter(weather.area);
|
|
103
107
|
if (weather.kind === "rain") {
|
|
104
|
-
return (_jsx("group", { position: [cx, 0, cz], children: _jsx(RainField, { density: weather.density, speed: weather.speed, length: weather.dropLength, width: weather.width, opacity: weather.opacity, color: weather.color, wind: [weather.wind[0], 0, weather.wind[1]], volume: weatherVolume(weather.area) }) }));
|
|
108
|
+
return (_jsx("group", { position: [cx, 0, cz], children: _jsx(RainField, { timeSeconds: ctx.time.now, density: weather.density, speed: weather.speed, length: weather.dropLength, width: weather.width, opacity: weather.opacity, color: weather.color, wind: [weather.wind[0], 0, weather.wind[1]], volume: weatherVolume(weather.area) }) }));
|
|
105
109
|
}
|
|
106
|
-
return (_jsx("group", { position: [cx, 0, cz], children: _jsx(SnowField, { density: weather.density, speed: weather.speed, size: weather.flakeSize, sway: weather.drift, opacity: weather.opacity, color: weather.color, wind: [weather.wind[0], 0, weather.wind[1]], volume: weatherVolume(weather.area) }) }));
|
|
110
|
+
return (_jsx("group", { position: [cx, 0, cz], children: _jsx(SnowField, { timeSeconds: ctx.time.now, density: weather.density, speed: weather.speed, size: weather.flakeSize, sway: weather.drift, opacity: weather.opacity, color: weather.color, wind: [weather.wind[0], 0, weather.wind[1]], volume: weatherVolume(weather.area) }) }));
|
|
107
111
|
}
|
|
108
112
|
function Weather({ weather }) {
|
|
109
|
-
return (_jsx(
|
|
113
|
+
return (_jsx(_Fragment, { children: weather.map((entry, index) => (_jsx(Precipitation, { weather: entry, index: index }, `${entry.kind}-${index}`))) }));
|
|
110
114
|
}
|
|
111
115
|
function oceanConfig(ocean) {
|
|
112
116
|
return {
|
|
@@ -159,5 +163,5 @@ export function EnvironmentScene({ feature }) {
|
|
|
159
163
|
const roads = feature.roads ?? [];
|
|
160
164
|
const junctions = useMemo(() => findRoadJunctions(roads), [roads]);
|
|
161
165
|
const roadNetwork = useMemo(() => buildAuthoredRoadNetwork(roads, junctions, (x, z) => field.sampleHeight(x, z)), [roads, junctions, field]);
|
|
162
|
-
return (_jsxs(_Fragment, { children: [feature.terrain !== undefined ? _jsx(TerrainGround, { terrain: feature.terrain, field: field }) : null, islands.map((entry, index) => (_jsx(TerrainGround, { terrain: entry, field: field, center: entry.origin }, `island-${index}`))), water.map((ocean, index) => (_jsx(Water, { ocean: ocean }, `ocean-${index}`))), roads.map((entry, index) => (_jsx(RoadRibbons, { road: entry, field: field, paths: roadNetwork.roadPaths[index] ?? [] }, `road-${index}`))), _jsx(RoadJunctions, { surfaces: roadNetwork.junctionSurfaces, junctions: junctions, field: field }), structures.map((entry, index) => (_jsx(Structures, { structures: entry, field: field }, `structures-${index}`))), pads.map((entry, index) => (_jsx(GroundPad, { pad: entry, field: field }, `pad-${index}`))), vegetation.map((grass, index) => (_jsx(Vegetation, { grass: grass, field: field, groundColor: vegetationGroundColor(feature.terrain, field, grass) }, `grass-${index}`))), feature.weather !== undefined && feature.weather.length > 0 ? (_jsx(Weather, { weather: feature.weather })) : null] }));
|
|
166
|
+
return (_jsxs(_Fragment, { children: [feature.terrain !== undefined ? _jsx(TerrainGround, { terrain: feature.terrain, field: field, sculpt: feature.sculpt }) : null, islands.map((entry, index) => (_jsx(TerrainGround, { terrain: entry, field: field, center: entry.origin, sculpt: feature.sculpt }, `island-${index}`))), water.map((ocean, index) => (_jsx(Water, { ocean: ocean }, `ocean-${index}`))), roads.map((entry, index) => (_jsx(RoadRibbons, { road: entry, field: field, paths: roadNetwork.roadPaths[index] ?? [] }, `road-${index}`))), _jsx(RoadJunctions, { surfaces: roadNetwork.junctionSurfaces, junctions: junctions, field: field }), structures.map((entry, index) => (_jsx(Structures, { structures: entry, field: field }, `structures-${index}`))), pads.map((entry, index) => (_jsx(GroundPad, { pad: entry, field: field }, `pad-${index}`))), vegetation.map((grass, index) => (_jsx(Vegetation, { grass: grass, field: field, groundColor: vegetationGroundColor(feature.terrain, field, grass, feature.sculpt) }, `grass-${index}`))), feature.weather !== undefined && feature.weather.length > 0 ? (_jsx(Weather, { weather: feature.weather })) : null] }));
|
|
163
167
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { TerrainEnvironmentDescriptor } from "@jgengine/core/world/features";
|
|
2
2
|
import { type TerrainField } from "@jgengine/core/world/terrain";
|
|
3
|
+
import { type TerraformSnapshot } from "@jgengine/core/world/terraform";
|
|
3
4
|
/**
|
|
4
|
-
* Terrain
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Terrain base color at a world XZ — authored paint first, otherwise the regional palette lerped
|
|
6
|
+
* by ground height. Grass roots and soil-patch borders share the painted color with ground vertices.
|
|
7
|
+
* Descriptor surface mottling and fragment detail are presentation overlays, not sampled here.
|
|
7
8
|
* @internal
|
|
8
9
|
*/
|
|
9
|
-
export declare function terrainGroundColorSampler(terrain: Omit<TerrainEnvironmentDescriptor, "kind"> | undefined, field: TerrainField): ((x: number, z: number) => string) | undefined;
|
|
10
|
+
export declare function terrainGroundColorSampler(terrain: Omit<TerrainEnvironmentDescriptor, "kind"> | undefined, field: TerrainField, sculpt?: TerraformSnapshot): ((x: number, z: number) => string) | undefined;
|
|
@@ -1,21 +1,32 @@
|
|
|
1
1
|
import * as THREE from "three";
|
|
2
|
-
import { resolveTerrainPalette } from "@jgengine/core/world/terrain";
|
|
2
|
+
import { createTerrainPaletteSampler, resolveTerrainPalette } from "@jgengine/core/world/terrain";
|
|
3
|
+
import { editableTerrainFromSnapshot } from "@jgengine/core/world/terraform";
|
|
4
|
+
import { createTerrainPaintColorSampler } from "../terrain/terrainPaintColor.js";
|
|
3
5
|
/**
|
|
4
|
-
* Terrain
|
|
5
|
-
*
|
|
6
|
-
*
|
|
6
|
+
* Terrain base color at a world XZ — authored paint first, otherwise the regional palette lerped
|
|
7
|
+
* by ground height. Grass roots and soil-patch borders share the painted color with ground vertices.
|
|
8
|
+
* Descriptor surface mottling and fragment detail are presentation overlays, not sampled here.
|
|
7
9
|
* @internal
|
|
8
10
|
*/
|
|
9
|
-
export function terrainGroundColorSampler(terrain, field) {
|
|
10
|
-
if (terrain === undefined)
|
|
11
|
+
export function terrainGroundColorSampler(terrain, field, sculpt) {
|
|
12
|
+
if (terrain === undefined && sculpt === undefined)
|
|
11
13
|
return undefined;
|
|
12
14
|
const palette = resolveTerrainPalette(terrain);
|
|
15
|
+
const paletteAt = terrain === undefined ? undefined : createTerrainPaletteSampler(terrain);
|
|
16
|
+
const paintColorAt = sculpt === undefined ? undefined : createTerrainPaintColorSampler(editableTerrainFromSnapshot(sculpt));
|
|
13
17
|
const low = new THREE.Color(palette.low);
|
|
14
18
|
const high = new THREE.Color(palette.high);
|
|
15
|
-
const base = terrain
|
|
16
|
-
const swing = terrain
|
|
19
|
+
const base = terrain?.baseHeight ?? 0;
|
|
20
|
+
const swing = (terrain?.height ?? 0) * 1.2;
|
|
17
21
|
const scratch = new THREE.Color();
|
|
18
22
|
return (x, z) => {
|
|
23
|
+
if (paintColorAt?.(x, z, scratch))
|
|
24
|
+
return `#${scratch.getHexString()}`;
|
|
25
|
+
const regional = paletteAt?.(x, z);
|
|
26
|
+
if (regional !== undefined) {
|
|
27
|
+
low.set(regional.low);
|
|
28
|
+
high.set(regional.high);
|
|
29
|
+
}
|
|
19
30
|
const t = swing <= 0 ? 0.5 : THREE.MathUtils.clamp((field.sampleHeight(x, z) - (base - swing)) / (swing * 2), 0, 1);
|
|
20
31
|
return `#${scratch.copy(low).lerp(high, t).getHexString()}`;
|
|
21
32
|
};
|
package/dist/inputSink.d.ts
CHANGED
|
@@ -2,17 +2,17 @@ import { type InputFrame } from "@jgengine/core/runtime/hostedGameRunner";
|
|
|
2
2
|
import type { LiveGameBackend } from "@jgengine/core/runtime/transport";
|
|
3
3
|
/** Where the local player's per-frame input goes: discarded in single-player, sent to the authoritative host under `authority: "server"`. */
|
|
4
4
|
export interface InputSink {
|
|
5
|
-
send(frame: InputFrame
|
|
5
|
+
send(frame: InputFrame, options?: {
|
|
6
|
+
urgent?: boolean;
|
|
7
|
+
reset?: boolean;
|
|
8
|
+
}): void;
|
|
6
9
|
}
|
|
7
10
|
/** Discards input — the single-player / client-authoritative default, where the client integrates movement itself.
|
|
8
11
|
* @internal
|
|
9
12
|
*/
|
|
10
13
|
export declare function noopInputSink(): InputSink;
|
|
11
|
-
/**
|
|
12
|
-
*
|
|
13
|
-
* {@link INPUT_COMMAND}. Sends are sequenced one-in-flight-at-a-time per `serverId` and stamped with a monotonic
|
|
14
|
-
* seq: a frame sent while one is already in flight replaces the pending frame rather than racing it, so the
|
|
15
|
-
* latest intent always wins and a stale frame can never resolve after (and overwrite) a newer one.
|
|
14
|
+
/** Coalesces continuous frames. Discrete release/reset intents bypass a pending ACK;
|
|
15
|
+
* the host's monotonic sequence admission rejects any older input arriving afterwards.
|
|
16
16
|
* @internal
|
|
17
17
|
*/
|
|
18
18
|
export declare function remoteInputSink(backend: Pick<LiveGameBackend, "transport">, serverId: string): InputSink;
|
package/dist/inputSink.js
CHANGED
|
@@ -5,56 +5,91 @@ import { INPUT_COMMAND } from "@jgengine/core/runtime/hostedGameRunner";
|
|
|
5
5
|
export function noopInputSink() {
|
|
6
6
|
return { send() { } };
|
|
7
7
|
}
|
|
8
|
-
const remoteInputSources = new
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
const remoteInputSources = new WeakMap();
|
|
9
|
+
let lastInputSeq = 0;
|
|
10
|
+
function remoteInputSourceFor(backend, serverId) {
|
|
11
|
+
let sources = remoteInputSources.get(backend.transport);
|
|
12
|
+
if (sources === undefined) {
|
|
13
|
+
sources = new Map();
|
|
14
|
+
remoteInputSources.set(backend.transport, sources);
|
|
15
|
+
}
|
|
16
|
+
let source = sources.get(serverId);
|
|
11
17
|
if (source === undefined) {
|
|
12
|
-
source = { pending: null, inFlight:
|
|
13
|
-
|
|
18
|
+
source = { pending: null, inFlight: null, latest: null, presses: new Map() };
|
|
19
|
+
sources.set(serverId, source);
|
|
14
20
|
}
|
|
15
21
|
return source;
|
|
16
22
|
}
|
|
23
|
+
// Use the browser time origin so a full reload stays ahead of the previous client.
|
|
24
|
+
// The local high-water mark also preserves release ordering if its clock regresses.
|
|
17
25
|
function monotonicInputSeq() {
|
|
18
|
-
|
|
26
|
+
const now = typeof performance !== "undefined" ? performance.timeOrigin + performance.now() : Date.now();
|
|
27
|
+
return lastInputSeq = Math.max(now, lastInputSeq + 0.001);
|
|
19
28
|
}
|
|
20
29
|
function pumpRemoteInput(backend, serverId, source) {
|
|
21
|
-
if (source.inFlight)
|
|
30
|
+
if (source.inFlight !== null)
|
|
22
31
|
return;
|
|
23
32
|
const frame = source.pending;
|
|
24
33
|
if (frame === null) {
|
|
25
|
-
|
|
34
|
+
if (source.presses.size === 0)
|
|
35
|
+
remoteInputSources.get(backend.transport)?.delete(serverId);
|
|
26
36
|
return;
|
|
27
37
|
}
|
|
28
38
|
source.pending = null;
|
|
29
|
-
|
|
39
|
+
const owner = {};
|
|
40
|
+
source.inFlight = owner;
|
|
41
|
+
const presses = [...source.presses.values()].map(press => ({ ...press }));
|
|
30
42
|
const seq = monotonicInputSeq();
|
|
43
|
+
// Live clients and the joined host have independent simulation clocks. The host
|
|
44
|
+
// stamps receipt on its next tick; explicit ticks remain supported by replay.
|
|
45
|
+
const { tick: _clientTick, presses: _framePresses, ...intent } = frame;
|
|
31
46
|
void backend.transport
|
|
32
|
-
.runCommand({ serverId, command: INPUT_COMMAND, input: { ...
|
|
47
|
+
.runCommand({ serverId, command: INPUT_COMMAND, input: { ...intent, ...(presses.length > 0 ? { presses } : {}), seq } })
|
|
33
48
|
.then((result) => {
|
|
34
|
-
if (
|
|
49
|
+
if (result.ok)
|
|
50
|
+
for (const press of presses)
|
|
51
|
+
source.presses.delete(press.seq);
|
|
52
|
+
if (!result.ok)
|
|
35
53
|
console.warn(`[jgengine:input] frame seq=${seq} to server "${serverId}" rejected: ${result.reason}`);
|
|
36
|
-
}
|
|
37
54
|
})
|
|
38
55
|
.catch((error) => {
|
|
39
56
|
console.warn(`[jgengine:input] frame seq=${seq} to server "${serverId}" failed to send`, error);
|
|
40
57
|
})
|
|
41
58
|
.finally(() => {
|
|
42
|
-
|
|
59
|
+
// An urgent discrete intent supersedes this flight. Its old completion must not
|
|
60
|
+
// release the replacement's ACK gate or replay the superseded pending frame.
|
|
61
|
+
if (source.inFlight !== owner)
|
|
62
|
+
return;
|
|
63
|
+
source.inFlight = null;
|
|
43
64
|
pumpRemoteInput(backend, serverId, source);
|
|
44
65
|
});
|
|
45
66
|
}
|
|
46
|
-
/**
|
|
47
|
-
*
|
|
48
|
-
* {@link INPUT_COMMAND}. Sends are sequenced one-in-flight-at-a-time per `serverId` and stamped with a monotonic
|
|
49
|
-
* seq: a frame sent while one is already in flight replaces the pending frame rather than racing it, so the
|
|
50
|
-
* latest intent always wins and a stale frame can never resolve after (and overwrite) a newer one.
|
|
67
|
+
/** Coalesces continuous frames. Discrete release/reset intents bypass a pending ACK;
|
|
68
|
+
* the host's monotonic sequence admission rejects any older input arriving afterwards.
|
|
51
69
|
* @internal
|
|
52
70
|
*/
|
|
53
71
|
export function remoteInputSink(backend, serverId) {
|
|
54
72
|
return {
|
|
55
|
-
send(frame) {
|
|
56
|
-
const source = remoteInputSourceFor(serverId);
|
|
57
|
-
|
|
73
|
+
send(frame, options) {
|
|
74
|
+
const source = remoteInputSourceFor(backend, serverId);
|
|
75
|
+
if (options?.reset)
|
|
76
|
+
source.presses.clear();
|
|
77
|
+
for (const press of frame.presses ?? [])
|
|
78
|
+
source.presses.set(press.seq, { ...press });
|
|
79
|
+
// A reset must overtake an identical neutral release that still carries an
|
|
80
|
+
// unacknowledged press. Intent equality alone does not describe that flight.
|
|
81
|
+
if (!options?.reset && source.latest !== null && inputFramesEqual(source.latest, frame) && source.presses.size === 0)
|
|
82
|
+
return;
|
|
83
|
+
const snapshot = {
|
|
84
|
+
...frame, held: [...frame.held],
|
|
85
|
+
...(frame.presses === undefined ? {} : { presses: frame.presses.map(press => ({ ...press })) }),
|
|
86
|
+
pointer: frame.pointer === null ? null : { ...frame.pointer },
|
|
87
|
+
...(frame.analog === undefined ? {} : { analog: frame.analog === null ? null : { ...frame.analog } }),
|
|
88
|
+
};
|
|
89
|
+
source.latest = snapshot;
|
|
90
|
+
source.pending = snapshot;
|
|
91
|
+
if (options?.urgent)
|
|
92
|
+
source.inFlight = null;
|
|
58
93
|
pumpRemoteInput(backend, serverId, source);
|
|
59
94
|
},
|
|
60
95
|
};
|
|
@@ -72,6 +107,10 @@ export function resolveInputSink(opts) {
|
|
|
72
107
|
* @internal
|
|
73
108
|
*/
|
|
74
109
|
export function inputFramesEqual(a, b) {
|
|
110
|
+
const ap = a.presses ?? [];
|
|
111
|
+
const bp = b.presses ?? [];
|
|
112
|
+
if (ap.length !== bp.length || ap.some((press, index) => press.seq !== bp[index].seq || press.action !== bp[index].action))
|
|
113
|
+
return false;
|
|
75
114
|
if (a.held.length !== b.held.length)
|
|
76
115
|
return false;
|
|
77
116
|
for (let i = 0; i < a.held.length; i++)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ComponentProps } from "react";
|
|
2
|
+
import type { Shell3dPresentation } from "./Shell3dPresentation.js";
|
|
3
|
+
type Presentation = typeof Shell3dPresentation;
|
|
4
|
+
type Props = ComponentProps<Presentation> & {
|
|
5
|
+
onPresentationReveal?: () => void;
|
|
6
|
+
};
|
|
7
|
+
/** Loads the normal 3D implementation only for a 3D presentation. @internal */
|
|
8
|
+
export declare function LazyShell3dPresentation({ onPresentationReveal, ...props }: Props): import("react").JSX.Element;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { lazy, Suspense, useLayoutEffect, useState } from "react";
|
|
3
|
+
import { suspendPlayControls } from "@jgengine/core/game/controlGate";
|
|
4
|
+
import { markPresentationCodeLoadFailure } from "./presentationRecovery.js";
|
|
5
|
+
let loadedPresentation;
|
|
6
|
+
function getPresentation() {
|
|
7
|
+
return loadedPresentation ??= lazy(() => import("./Shell3dPresentation.js").then((module) => ({ default: module.Shell3dPresentation }), (error) => {
|
|
8
|
+
throw markPresentationCodeLoadFailure(error);
|
|
9
|
+
}));
|
|
10
|
+
}
|
|
11
|
+
function LoadingPresentation({ ctx }) {
|
|
12
|
+
useLayoutEffect(() => suspendPlayControls(ctx), [ctx]);
|
|
13
|
+
return (_jsx("div", { role: "status", "aria-live": "polite", "data-shell-code-loading": true, style: { display: "grid", placeItems: "center", width: "100%", height: "100%", minHeight: 160, background: "#0f172a", color: "#e2e8f0", fontFamily: "ui-sans-serif, system-ui, sans-serif" }, children: "Loading game view\u2026" }));
|
|
14
|
+
}
|
|
15
|
+
function PresentationReveal({ onReveal }) {
|
|
16
|
+
// React repeats layout effects when a suspended tree becomes visible again.
|
|
17
|
+
useLayoutEffect(() => { onReveal?.(); });
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
/** Loads the normal 3D implementation only for a 3D presentation. @internal */
|
|
21
|
+
export function LazyShell3dPresentation({ onPresentationReveal, ...props }) {
|
|
22
|
+
const [Presentation] = useState(getPresentation);
|
|
23
|
+
return (_jsxs(Suspense, { fallback: _jsx(LoadingPresentation, { ctx: props.ctx }), children: [_jsx(Presentation, { ...props }), _jsx(PresentationReveal, { onReveal: onPresentationReveal })] }));
|
|
24
|
+
}
|
|
@@ -4,6 +4,8 @@ export interface MaterialOverrideOptions {
|
|
|
4
4
|
clone?: boolean;
|
|
5
5
|
/** Loaded PBR textures matching `ModelMaterialOverride.maps`' roles, applied onto each standard or physical material alongside the tint override. The caller (a React component, via `useTexture`) owns loading; this function never fetches a URL. */
|
|
6
6
|
textures?: MaterialOverrideTextures;
|
|
7
|
+
/** Records material replacements for the caller's resource owner. */
|
|
8
|
+
ownMaterial?: (material: THREE.Material) => void;
|
|
7
9
|
}
|
|
8
10
|
/** Loaded PBR textures for `applyMaterialOverride`'s `textures` option — matches `ModelMaterialMaps`' roles. */
|
|
9
11
|
export interface MaterialOverrideTextures {
|
|
@@ -14,6 +16,46 @@ export interface MaterialOverrideTextures {
|
|
|
14
16
|
metalness?: THREE.Texture;
|
|
15
17
|
emissive?: THREE.Texture;
|
|
16
18
|
height?: THREE.Texture;
|
|
19
|
+
alpha?: THREE.Texture;
|
|
20
|
+
sheenColor?: THREE.Texture;
|
|
21
|
+
sheenRoughness?: THREE.Texture;
|
|
22
|
+
anisotropy?: THREE.Texture;
|
|
23
|
+
clearcoat?: THREE.Texture;
|
|
24
|
+
clearcoatRoughness?: THREE.Texture;
|
|
25
|
+
clearcoatNormal?: THREE.Texture;
|
|
26
|
+
specularIntensity?: THREE.Texture;
|
|
27
|
+
specularColor?: THREE.Texture;
|
|
28
|
+
transmission?: THREE.Texture;
|
|
29
|
+
thickness?: THREE.Texture;
|
|
30
|
+
iridescence?: THREE.Texture;
|
|
31
|
+
iridescenceThickness?: THREE.Texture;
|
|
17
32
|
}
|
|
33
|
+
/** Physical texture roles mapped to the pinned Three renderer's native properties. @internal */
|
|
34
|
+
export declare const MATERIAL_TEXTURE_PROPERTIES: {
|
|
35
|
+
readonly color: "map";
|
|
36
|
+
readonly normal: "normalMap";
|
|
37
|
+
readonly roughness: "roughnessMap";
|
|
38
|
+
readonly ao: "aoMap";
|
|
39
|
+
readonly metalness: "metalnessMap";
|
|
40
|
+
readonly emissive: "emissiveMap";
|
|
41
|
+
readonly height: "displacementMap";
|
|
42
|
+
readonly alpha: "alphaMap";
|
|
43
|
+
readonly sheenColor: "sheenColorMap";
|
|
44
|
+
readonly sheenRoughness: "sheenRoughnessMap";
|
|
45
|
+
readonly anisotropy: "anisotropyMap";
|
|
46
|
+
readonly clearcoat: "clearcoatMap";
|
|
47
|
+
readonly clearcoatRoughness: "clearcoatRoughnessMap";
|
|
48
|
+
readonly clearcoatNormal: "clearcoatNormalMap";
|
|
49
|
+
readonly specularIntensity: "specularIntensityMap";
|
|
50
|
+
readonly specularColor: "specularColorMap";
|
|
51
|
+
readonly transmission: "transmissionMap";
|
|
52
|
+
readonly thickness: "thicknessMap";
|
|
53
|
+
readonly iridescence: "iridescenceMap";
|
|
54
|
+
readonly iridescenceThickness: "iridescenceThicknessMap";
|
|
55
|
+
};
|
|
56
|
+
/** Whether authored settings require a physical shader; ordinary PBR stays standard. @internal */
|
|
57
|
+
export declare function requiresPhysicalMaterial(override: ModelMaterialOverride, textures?: MaterialOverrideTextures): boolean;
|
|
18
58
|
/** @internal */
|
|
19
59
|
export declare function applyMaterialOverride(root: THREE.Object3D, override: ModelMaterialOverride, options?: MaterialOverrideOptions): void;
|
|
60
|
+
/** Apply the shared override contract to one material. @internal */
|
|
61
|
+
export declare function applyMaterialOverrideToMaterial(material: THREE.Material, override: ModelMaterialOverride, clone: boolean, textures: MaterialOverrideTextures | undefined): THREE.Material;
|