@jgengine/shell 0.8.0 → 0.9.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 +16 -0
- package/README.md +11 -0
- package/dist/GamePlayerShell.d.ts +5 -2
- package/dist/GamePlayerShell.js +358 -98
- package/dist/GameUiPreview.js +2 -1
- package/dist/audio/AudioComponents.js +25 -8
- package/dist/audio/audioEngine.d.ts +1 -0
- package/dist/audio/audioEngine.js +4 -0
- package/dist/behaviour.d.ts +5 -24
- package/dist/behaviour.js +14 -50
- package/dist/behaviourAttach.d.ts +9 -0
- package/dist/behaviourAttach.js +32 -0
- package/dist/behaviourDriver.d.ts +7 -0
- package/dist/behaviourDriver.js +21 -0
- package/dist/camera/GameCameraRig.js +1 -1
- package/dist/camera/GameFirstPersonCamera.js +14 -0
- package/dist/camera/GameOrbitCamera.js +10 -1
- package/dist/camera/PlayerFov.d.ts +18 -0
- package/dist/camera/PlayerFov.js +48 -0
- package/dist/camera/cameraBlendMath.d.ts +13 -0
- package/dist/camera/cameraBlendMath.js +36 -0
- package/dist/camera/cameraRigs.d.ts +3 -0
- package/dist/camera/cameraRigs.js +36 -33
- package/dist/camera/fovPreference.d.ts +25 -0
- package/dist/camera/fovPreference.js +73 -0
- package/dist/camera/index.d.ts +3 -0
- package/dist/camera/index.js +3 -0
- package/dist/camera/rigMath.d.ts +10 -0
- package/dist/camera/rigMath.js +36 -1
- package/dist/camera/shakeChannel.d.ts +3 -17
- package/dist/camera/shakeChannel.js +2 -21
- package/dist/camera/shakeChannelMath.d.ts +8 -0
- package/dist/camera/shakeChannelMath.js +21 -0
- package/dist/cartridge.d.ts +145 -0
- package/dist/cartridge.js +245 -0
- package/dist/defineGame.js +2 -1
- package/dist/devtools/CollisionDebugWorld.d.ts +5 -0
- package/dist/devtools/CollisionDebugWorld.js +180 -0
- package/dist/devtools/DevtoolsOverlay.d.ts +65 -1
- package/dist/devtools/DevtoolsOverlay.js +383 -41
- package/dist/devtools/collisionDebug.d.ts +57 -0
- package/dist/devtools/collisionDebug.js +127 -0
- package/dist/devtools/collisionDebugMath.d.ts +102 -0
- package/dist/devtools/collisionDebugMath.js +128 -0
- package/dist/environment/Daylight.d.ts +13 -7
- package/dist/environment/Daylight.js +12 -11
- package/dist/environment/EnvironmentScene.js +82 -35
- package/dist/environment/groundPadMath.d.ts +2 -2
- package/dist/environment/groundPadMath.js +1 -1
- package/dist/environment/index.d.ts +2 -1
- package/dist/environment/index.js +1 -0
- package/dist/environment/skyLightingPolicy.d.ts +10 -0
- package/dist/environment/skyLightingPolicy.js +6 -0
- package/dist/input/mouseLook.d.ts +27 -0
- package/dist/input/mouseLook.js +35 -0
- package/dist/materialOverride.d.ts +4 -6
- package/dist/materialOverride.js +12 -16
- package/dist/pointer/PointerProbe.js +6 -2
- package/dist/pointer/pointerService.d.ts +3 -0
- package/dist/pointer/pointerService.js +6 -0
- package/dist/render/modelRender.d.ts +10 -1
- package/dist/render/modelRender.js +32 -5
- package/dist/render/resolveModel.d.ts +14 -0
- package/dist/render/resolveModel.js +24 -0
- package/dist/settings/QuickControls.d.ts +4 -0
- package/dist/settings/QuickControls.js +42 -0
- package/dist/settings/SettingsChrome.d.ts +1 -0
- package/dist/settings/SettingsChrome.js +10 -0
- package/dist/settings/SettingsMenu.d.ts +6 -0
- package/dist/settings/SettingsMenu.js +148 -0
- package/dist/settings/SettingsRuntime.d.ts +11 -0
- package/dist/settings/SettingsRuntime.js +19 -0
- package/dist/settings/appliedSettings.d.ts +14 -0
- package/dist/settings/appliedSettings.js +27 -0
- package/dist/settings/settingsController.d.ts +20 -0
- package/dist/settings/settingsController.js +165 -0
- package/dist/structures/GeneratedBuilding.d.ts +10 -0
- package/dist/structures/GeneratedBuilding.js +96 -8
- package/dist/structures/index.d.ts +1 -1
- package/dist/structures/index.js +1 -1
- package/dist/terrain/CarvedTerrain.d.ts +2 -1
- package/dist/terrain/CarvedTerrain.js +3 -3
- package/dist/terrain/GrassField.d.ts +3 -1
- package/dist/terrain/GrassField.js +4 -2
- package/dist/terrain/grassBudget.d.ts +3 -0
- package/dist/terrain/grassBudget.js +6 -0
- package/dist/terrain/grassGeometry.js +1 -1
- package/dist/terrain/terrainMath.d.ts +8 -0
- package/dist/terrain/terrainMath.js +9 -0
- package/dist/touch/OrientationHint.d.ts +3 -0
- package/dist/touch/OrientationHint.js +13 -0
- package/dist/touch/TouchControlsOverlay.d.ts +17 -1
- package/dist/touch/TouchControlsOverlay.js +115 -9
- package/dist/visibility/CullingProvider.d.ts +21 -0
- package/dist/visibility/CullingProvider.js +134 -0
- package/dist/vision/FrustumSensorHud.d.ts +1 -6
- package/dist/vision/FrustumSensorHud.js +25 -27
- package/dist/vision/frustumSampleEqual.d.ts +2 -0
- package/dist/vision/frustumSampleEqual.js +10 -0
- package/dist/water/Ocean.js +1 -1
- package/dist/water/OceanConfig.d.ts +13 -0
- package/dist/water/OceanConfig.js +25 -17
- package/dist/water/index.d.ts +1 -1
- package/dist/water/index.js +1 -1
- package/dist/weather/FireSpreadLayer.js +7 -2
- package/dist/weather/RainField.d.ts +3 -1
- package/dist/weather/RainField.js +4 -4
- package/dist/weather/SnowField.d.ts +3 -1
- package/dist/weather/SnowField.js +4 -4
- package/dist/weather/fireSpreadPose.d.ts +2 -0
- package/dist/weather/fireSpreadPose.js +4 -0
- package/dist/weather/weatherMath.d.ts +5 -1
- package/dist/weather/weatherMath.js +7 -2
- package/dist/world/DataObjects.d.ts +1 -1
- package/dist/world/DataObjects.js +3 -1
- package/dist/world/SpriteBatch.d.ts +44 -0
- package/dist/world/SpriteBatch.js +112 -0
- package/dist/world/WorldHud.d.ts +3 -0
- package/dist/world/WorldHud.js +89 -42
- package/dist/world/entityPose.d.ts +14 -0
- package/dist/world/entityPose.js +10 -0
- package/dist/world/telegraphPulse.d.ts +1 -0
- package/dist/world/telegraphPulse.js +4 -0
- package/dist/world/worldBarSamples.d.ts +30 -0
- package/dist/world/worldBarSamples.js +51 -0
- package/llms.txt +1404 -1143
- package/package.json +4 -4
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
export const COLLISION_DEBUG_LAYERS = [
|
|
2
|
+
"hitboxes",
|
|
3
|
+
"bodies",
|
|
4
|
+
"projectiles",
|
|
5
|
+
"muzzles",
|
|
6
|
+
"aimLaser",
|
|
7
|
+
];
|
|
8
|
+
const DEFAULT_LAYERS = {
|
|
9
|
+
hitboxes: false,
|
|
10
|
+
bodies: false,
|
|
11
|
+
projectiles: false,
|
|
12
|
+
muzzles: false,
|
|
13
|
+
aimLaser: false,
|
|
14
|
+
};
|
|
15
|
+
export function createDefaultCollisionDebugState() {
|
|
16
|
+
return {
|
|
17
|
+
layers: { ...DEFAULT_LAYERS },
|
|
18
|
+
aimProbe: null,
|
|
19
|
+
projectileTraces: [],
|
|
20
|
+
maxProjectileTraces: 32,
|
|
21
|
+
projectileTraceLifeMs: 2500,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export function anyCollisionLayerOn(layers) {
|
|
25
|
+
return (layers.hitboxes ||
|
|
26
|
+
layers.bodies ||
|
|
27
|
+
layers.projectiles ||
|
|
28
|
+
layers.muzzles ||
|
|
29
|
+
layers.aimLaser);
|
|
30
|
+
}
|
|
31
|
+
export function colliderScanNeeded(layers) {
|
|
32
|
+
return layers.hitboxes || layers.bodies;
|
|
33
|
+
}
|
|
34
|
+
export function projectileListenNeeded(layers) {
|
|
35
|
+
return layers.projectiles || layers.muzzles;
|
|
36
|
+
}
|
|
37
|
+
export function aimProbeNeeded(layers) {
|
|
38
|
+
return layers.aimLaser;
|
|
39
|
+
}
|
|
40
|
+
export function createCollisionDebugController(initial = createDefaultCollisionDebugState()) {
|
|
41
|
+
let state = {
|
|
42
|
+
...initial,
|
|
43
|
+
layers: { ...initial.layers },
|
|
44
|
+
projectileTraces: [...initial.projectileTraces],
|
|
45
|
+
};
|
|
46
|
+
const listeners = new Set();
|
|
47
|
+
let nextTraceId = 1;
|
|
48
|
+
function emit() {
|
|
49
|
+
for (const listener of listeners)
|
|
50
|
+
listener();
|
|
51
|
+
}
|
|
52
|
+
function replace(next) {
|
|
53
|
+
state = next;
|
|
54
|
+
emit();
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
getState() {
|
|
58
|
+
return state;
|
|
59
|
+
},
|
|
60
|
+
subscribe(listener) {
|
|
61
|
+
listeners.add(listener);
|
|
62
|
+
return () => {
|
|
63
|
+
listeners.delete(listener);
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
setLayer(layer, on) {
|
|
67
|
+
if (state.layers[layer] === on)
|
|
68
|
+
return;
|
|
69
|
+
replace({ ...state, layers: { ...state.layers, [layer]: on } });
|
|
70
|
+
},
|
|
71
|
+
toggleLayer(layer) {
|
|
72
|
+
replace({ ...state, layers: { ...state.layers, [layer]: !state.layers[layer] } });
|
|
73
|
+
},
|
|
74
|
+
setLayers(partial) {
|
|
75
|
+
const layers = { ...state.layers, ...partial };
|
|
76
|
+
replace({ ...state, layers });
|
|
77
|
+
},
|
|
78
|
+
setAllLayers(on) {
|
|
79
|
+
const layers = {
|
|
80
|
+
hitboxes: on,
|
|
81
|
+
bodies: on,
|
|
82
|
+
projectiles: on,
|
|
83
|
+
muzzles: on,
|
|
84
|
+
aimLaser: on,
|
|
85
|
+
};
|
|
86
|
+
replace({ ...state, layers });
|
|
87
|
+
},
|
|
88
|
+
setAimProbe(probe) {
|
|
89
|
+
state = { ...state, aimProbe: probe };
|
|
90
|
+
},
|
|
91
|
+
getAimProbe() {
|
|
92
|
+
return state.aimProbe;
|
|
93
|
+
},
|
|
94
|
+
pushProjectileTrace(input) {
|
|
95
|
+
if (!projectileListenNeeded(state.layers))
|
|
96
|
+
return;
|
|
97
|
+
const trace = {
|
|
98
|
+
id: nextTraceId++,
|
|
99
|
+
origin: input.origin,
|
|
100
|
+
at: input.at,
|
|
101
|
+
hit: input.hit,
|
|
102
|
+
bornMs: input.nowMs ?? 0,
|
|
103
|
+
};
|
|
104
|
+
const next = [...state.projectileTraces, trace];
|
|
105
|
+
while (next.length > state.maxProjectileTraces)
|
|
106
|
+
next.shift();
|
|
107
|
+
replace({ ...state, projectileTraces: next });
|
|
108
|
+
},
|
|
109
|
+
pruneProjectileTraces(nowMs) {
|
|
110
|
+
if (state.projectileTraces.length === 0)
|
|
111
|
+
return;
|
|
112
|
+
const life = state.projectileTraceLifeMs;
|
|
113
|
+
const next = state.projectileTraces.filter((trace) => nowMs - trace.bornMs <= life);
|
|
114
|
+
if (next.length === state.projectileTraces.length)
|
|
115
|
+
return;
|
|
116
|
+
replace({ ...state, projectileTraces: next });
|
|
117
|
+
},
|
|
118
|
+
reset() {
|
|
119
|
+
nextTraceId = 1;
|
|
120
|
+
replace(createDefaultCollisionDebugState());
|
|
121
|
+
},
|
|
122
|
+
isActive() {
|
|
123
|
+
return anyCollisionLayerOn(state.layers);
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
export const collisionDebug = createCollisionDebugController();
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { EntityPosition } from "@jgengine/core/scene/entityStore";
|
|
2
|
+
import { type ColliderPurpose, type EntityColliderSet, type ResolvedCollider } from "@jgengine/core/scene/colliders";
|
|
3
|
+
import { type SceneRaycastApi, type SceneRaycastHit } from "@jgengine/core/scene/sceneRaycast";
|
|
4
|
+
import { type ShotOriginPolicy } from "@jgengine/core/combat/shotOrigin";
|
|
5
|
+
import type { Aim } from "@jgengine/core/scene/spatial";
|
|
6
|
+
import type { CollisionDebugLayers } from "./collisionDebug.js";
|
|
7
|
+
export type AimEndpointKind = "damage" | "solid" | "miss";
|
|
8
|
+
export interface DebugShapeEntry {
|
|
9
|
+
key: string;
|
|
10
|
+
targetKind: "entity" | "object";
|
|
11
|
+
instanceId: string;
|
|
12
|
+
catalogId?: string;
|
|
13
|
+
name: string;
|
|
14
|
+
purpose: ColliderPurpose;
|
|
15
|
+
damageEligible: boolean;
|
|
16
|
+
blocks: boolean;
|
|
17
|
+
position: EntityPosition;
|
|
18
|
+
rotationY: number;
|
|
19
|
+
shape: {
|
|
20
|
+
kind: "sphere";
|
|
21
|
+
radius: number;
|
|
22
|
+
center: EntityPosition;
|
|
23
|
+
} | {
|
|
24
|
+
kind: "aabb";
|
|
25
|
+
halfExtents: EntityPosition;
|
|
26
|
+
center: EntityPosition;
|
|
27
|
+
};
|
|
28
|
+
label: string;
|
|
29
|
+
style: "hitbox" | "body";
|
|
30
|
+
}
|
|
31
|
+
export interface AimLaserDebug {
|
|
32
|
+
origin: EntityPosition;
|
|
33
|
+
direction: EntityPosition;
|
|
34
|
+
end: EntityPosition;
|
|
35
|
+
maxDistance: number;
|
|
36
|
+
kind: AimEndpointKind;
|
|
37
|
+
nearest: SceneRaycastHit | null;
|
|
38
|
+
firstImpact: SceneRaycastHit | null;
|
|
39
|
+
/** Counts scene raycast calls performed (0 when skipped). */
|
|
40
|
+
queryCount: number;
|
|
41
|
+
}
|
|
42
|
+
export interface CollectDebugShapesInput {
|
|
43
|
+
layers: CollisionDebugLayers;
|
|
44
|
+
entities: readonly {
|
|
45
|
+
id: string;
|
|
46
|
+
position: EntityPosition;
|
|
47
|
+
rotationY: number;
|
|
48
|
+
name?: string;
|
|
49
|
+
}[];
|
|
50
|
+
objects?: readonly {
|
|
51
|
+
instanceId: string;
|
|
52
|
+
catalogId: string;
|
|
53
|
+
position: EntityPosition;
|
|
54
|
+
rotationY: number;
|
|
55
|
+
}[];
|
|
56
|
+
entityCollidersOf?(instanceId: string): EntityColliderSet | null | undefined;
|
|
57
|
+
objectCollidersOf?(instanceId: string): EntityColliderSet | null | undefined;
|
|
58
|
+
objectHalfExtentsOf?(catalogId: string): EntityPosition | null | undefined;
|
|
59
|
+
/** Mutated when a scene scan runs — tests assert 0 when hidden. */
|
|
60
|
+
counters?: {
|
|
61
|
+
scans: number;
|
|
62
|
+
shapes: number;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export declare function classifyAimEndpoint(hit: Pick<SceneRaycastHit, "damageEligible"> | null | undefined): AimEndpointKind;
|
|
66
|
+
export declare function pointAlongRay(origin: EntityPosition, direction: EntityPosition, distance: number): EntityPosition;
|
|
67
|
+
export declare function shapeWorldCenter(collider: ResolvedCollider, position: EntityPosition, rotationY: number): EntityPosition;
|
|
68
|
+
/**
|
|
69
|
+
* Collects world-space collider wireframe entries for enabled layers.
|
|
70
|
+
* Returns [] and performs no entity/object iteration when both hitbox/body layers are off.
|
|
71
|
+
*/
|
|
72
|
+
export declare function collectDebugShapes(input: CollectDebugShapesInput): DebugShapeEntry[];
|
|
73
|
+
export interface ComputeAimLaserInput {
|
|
74
|
+
layers: CollisionDebugLayers;
|
|
75
|
+
sceneRaycast: SceneRaycastApi;
|
|
76
|
+
positionOf(instanceId: string): EntityPosition | undefined;
|
|
77
|
+
rotationYOf?(instanceId: string): number | undefined;
|
|
78
|
+
from: string;
|
|
79
|
+
aim: Aim;
|
|
80
|
+
originPolicy?: ShotOriginPolicy;
|
|
81
|
+
maxDistance: number;
|
|
82
|
+
counters?: {
|
|
83
|
+
queries: number;
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Builds the authoritative aim-laser segment using resolveShot + scene raycast
|
|
88
|
+
* (same seam as projectile prediction/settlement). Zero queries when aimLaser is off.
|
|
89
|
+
*/
|
|
90
|
+
export declare function computeAimLaser(input: ComputeAimLaserInput): AimLaserDebug | null;
|
|
91
|
+
export declare function muzzleMarkerFromOrigin(origin: EntityPosition): {
|
|
92
|
+
center: EntityPosition;
|
|
93
|
+
radius: number;
|
|
94
|
+
color: string;
|
|
95
|
+
};
|
|
96
|
+
export declare const HITBOX_WIRE_COLOR = "#f472b6";
|
|
97
|
+
export declare const BODY_WIRE_COLOR = "#38bdf8";
|
|
98
|
+
export declare const PROJECTILE_PATH_COLOR = "#fde68a";
|
|
99
|
+
export declare const AIM_LASER_COLOR = "#a3e635";
|
|
100
|
+
export declare const AIM_DAMAGE_COLOR = "#f87171";
|
|
101
|
+
export declare const AIM_SOLID_COLOR = "#fbbf24";
|
|
102
|
+
export declare const AIM_MISS_COLOR = "#94a3b8";
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { colliderWorldCenter, defaultEntityColliders, defaultObjectColliders, resolveColliders, } from "@jgengine/core/scene/colliders";
|
|
2
|
+
import { firstImpact, hitsUntilBlocked, } from "@jgengine/core/scene/sceneRaycast";
|
|
3
|
+
import { resolveShot } from "@jgengine/core/combat/shotOrigin";
|
|
4
|
+
import { aimProbeNeeded, colliderScanNeeded } from "./collisionDebug.js";
|
|
5
|
+
export function classifyAimEndpoint(hit) {
|
|
6
|
+
if (hit === null || hit === undefined)
|
|
7
|
+
return "miss";
|
|
8
|
+
return hit.damageEligible ? "damage" : "solid";
|
|
9
|
+
}
|
|
10
|
+
export function pointAlongRay(origin, direction, distance) {
|
|
11
|
+
return [
|
|
12
|
+
origin[0] + direction[0] * distance,
|
|
13
|
+
origin[1] + direction[1] * distance,
|
|
14
|
+
origin[2] + direction[2] * distance,
|
|
15
|
+
];
|
|
16
|
+
}
|
|
17
|
+
export function shapeWorldCenter(collider, position, rotationY) {
|
|
18
|
+
return colliderWorldCenter(collider, position, rotationY);
|
|
19
|
+
}
|
|
20
|
+
function styleFor(purpose) {
|
|
21
|
+
return purpose === "damage" ? "hitbox" : "body";
|
|
22
|
+
}
|
|
23
|
+
function labelFor(collider) {
|
|
24
|
+
return `${collider.purpose}:${collider.name}`;
|
|
25
|
+
}
|
|
26
|
+
function pushResolved(out, targetKind, instanceId, catalogId, position, rotationY, set, layers) {
|
|
27
|
+
for (const collider of resolveColliders(set)) {
|
|
28
|
+
const style = styleFor(collider.purpose);
|
|
29
|
+
if (style === "hitbox" && !layers.hitboxes)
|
|
30
|
+
continue;
|
|
31
|
+
if (style === "body" && !layers.bodies)
|
|
32
|
+
continue;
|
|
33
|
+
const center = shapeWorldCenter(collider, position, rotationY);
|
|
34
|
+
const shape = collider.shape.kind === "sphere"
|
|
35
|
+
? { kind: "sphere", radius: collider.shape.radius, center }
|
|
36
|
+
: {
|
|
37
|
+
kind: "aabb",
|
|
38
|
+
halfExtents: collider.shape.halfExtents,
|
|
39
|
+
center,
|
|
40
|
+
};
|
|
41
|
+
out.push({
|
|
42
|
+
key: `${targetKind}:${instanceId}:${collider.purpose}:${collider.name}`,
|
|
43
|
+
targetKind,
|
|
44
|
+
instanceId,
|
|
45
|
+
...(catalogId !== undefined ? { catalogId } : {}),
|
|
46
|
+
name: collider.name,
|
|
47
|
+
purpose: collider.purpose,
|
|
48
|
+
damageEligible: collider.damageEligible,
|
|
49
|
+
blocks: collider.blocks,
|
|
50
|
+
position,
|
|
51
|
+
rotationY,
|
|
52
|
+
shape,
|
|
53
|
+
label: labelFor(collider),
|
|
54
|
+
style,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Collects world-space collider wireframe entries for enabled layers.
|
|
60
|
+
* Returns [] and performs no entity/object iteration when both hitbox/body layers are off.
|
|
61
|
+
*/
|
|
62
|
+
export function collectDebugShapes(input) {
|
|
63
|
+
if (!colliderScanNeeded(input.layers))
|
|
64
|
+
return [];
|
|
65
|
+
input.counters !== undefined && (input.counters.scans += 1);
|
|
66
|
+
const out = [];
|
|
67
|
+
for (const entity of input.entities) {
|
|
68
|
+
const set = input.entityCollidersOf?.(entity.id) ?? defaultEntityColliders();
|
|
69
|
+
pushResolved(out, "entity", entity.id, entity.name, entity.position, entity.rotationY, set, input.layers);
|
|
70
|
+
}
|
|
71
|
+
if (input.objects !== undefined) {
|
|
72
|
+
for (const object of input.objects) {
|
|
73
|
+
const half = input.objectHalfExtentsOf?.(object.catalogId) ?? undefined;
|
|
74
|
+
const set = input.objectCollidersOf?.(object.instanceId) ??
|
|
75
|
+
defaultObjectColliders(half === null || half === undefined ? undefined : half);
|
|
76
|
+
pushResolved(out, "object", object.instanceId, object.catalogId, object.position, object.rotationY, set, input.layers);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (input.counters !== undefined)
|
|
80
|
+
input.counters.shapes += out.length;
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Builds the authoritative aim-laser segment using resolveShot + scene raycast
|
|
85
|
+
* (same seam as projectile prediction/settlement). Zero queries when aimLaser is off.
|
|
86
|
+
*/
|
|
87
|
+
export function computeAimLaser(input) {
|
|
88
|
+
if (!aimProbeNeeded(input.layers)) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
const resolved = resolveShot({
|
|
92
|
+
positionOf: input.positionOf,
|
|
93
|
+
rotationYOf: input.rotationYOf,
|
|
94
|
+
}, input.from, input.aim, input.originPolicy ?? { kind: "legacy" });
|
|
95
|
+
if (resolved === null)
|
|
96
|
+
return null;
|
|
97
|
+
input.counters !== undefined && (input.counters.queries += 1);
|
|
98
|
+
const all = input.sceneRaycast.raycastAll({
|
|
99
|
+
origin: resolved.origin,
|
|
100
|
+
direction: resolved.direction,
|
|
101
|
+
maxDistance: input.maxDistance,
|
|
102
|
+
excludeInstanceIds: [input.from],
|
|
103
|
+
});
|
|
104
|
+
const until = hitsUntilBlocked(all);
|
|
105
|
+
const nearest = until[0] ?? null;
|
|
106
|
+
const impact = firstImpact(until);
|
|
107
|
+
const end = nearest !== null ? nearest.point : pointAlongRay(resolved.origin, resolved.direction, input.maxDistance);
|
|
108
|
+
return {
|
|
109
|
+
origin: resolved.origin,
|
|
110
|
+
direction: resolved.direction,
|
|
111
|
+
end,
|
|
112
|
+
maxDistance: input.maxDistance,
|
|
113
|
+
kind: classifyAimEndpoint(nearest),
|
|
114
|
+
nearest,
|
|
115
|
+
firstImpact: impact,
|
|
116
|
+
queryCount: 1,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
export function muzzleMarkerFromOrigin(origin) {
|
|
120
|
+
return { center: origin, radius: 0.08, color: "#ef4444" };
|
|
121
|
+
}
|
|
122
|
+
export const HITBOX_WIRE_COLOR = "#f472b6";
|
|
123
|
+
export const BODY_WIRE_COLOR = "#38bdf8";
|
|
124
|
+
export const PROJECTILE_PATH_COLOR = "#fde68a";
|
|
125
|
+
export const AIM_LASER_COLOR = "#a3e635";
|
|
126
|
+
export const AIM_DAMAGE_COLOR = "#f87171";
|
|
127
|
+
export const AIM_SOLID_COLOR = "#fbbf24";
|
|
128
|
+
export const AIM_MISS_COLOR = "#94a3b8";
|
|
@@ -28,12 +28,16 @@ export interface DaylightProps {
|
|
|
28
28
|
groundColor?: string;
|
|
29
29
|
intensity?: number;
|
|
30
30
|
};
|
|
31
|
+
/** When false, only the sky dome and fog mount — use with authored `PlayableGame.lighting`. Default true. */
|
|
32
|
+
lights?: boolean;
|
|
31
33
|
}
|
|
32
|
-
export declare function Daylight({ sky, fog, sun, ambient }?: DaylightProps): import("react").JSX.Element;
|
|
33
|
-
|
|
34
|
-
export declare function SkyDaylight({ sky }: {
|
|
34
|
+
export declare function Daylight({ sky, fog, sun, ambient, lights }?: DaylightProps): import("react").JSX.Element;
|
|
35
|
+
export interface SkyDaylightProps {
|
|
35
36
|
sky: SkyEnvironmentDescriptor;
|
|
36
|
-
|
|
37
|
+
lights?: boolean;
|
|
38
|
+
}
|
|
39
|
+
/** Renders a fixed sky/sun/fog look sampled from `sky`'s preset (or, when `timeOfDay` is on but no clock drives it, its noon look). No per-frame updates. */
|
|
40
|
+
export declare function SkyDaylight({ sky, lights }: SkyDaylightProps): import("react").JSX.Element;
|
|
37
41
|
export interface TimeOfDayDaylightProps {
|
|
38
42
|
sky: SkyEnvironmentDescriptor;
|
|
39
43
|
/** The world's `SimClock` (or a stub exposing `calendar().dayFraction`). Absent means static rendering. */
|
|
@@ -42,9 +46,11 @@ export interface TimeOfDayDaylightProps {
|
|
|
42
46
|
dayFraction: number;
|
|
43
47
|
};
|
|
44
48
|
};
|
|
49
|
+
lights?: boolean;
|
|
45
50
|
}
|
|
46
51
|
/**
|
|
47
|
-
* Drives
|
|
48
|
-
*
|
|
52
|
+
* Drives sky/fog (and optional default lights) from the world clock when `sky.timeOfDay` and `clock`
|
|
53
|
+
* are both present. Authored `PlayableGame.lighting` is never rewritten — pass `lights={false}` so
|
|
54
|
+
* only dome colors and fog track the day fraction.
|
|
49
55
|
*/
|
|
50
|
-
export declare function TimeOfDayDaylight({ sky, clock }: TimeOfDayDaylightProps): import("react").JSX.Element;
|
|
56
|
+
export declare function TimeOfDayDaylight({ sky, clock, lights }: TimeOfDayDaylightProps): import("react").JSX.Element;
|
|
@@ -53,25 +53,26 @@ export function SkyDome({ topColor = SKY_TOP, horizonColor = SKY_HORIZON, radius
|
|
|
53
53
|
}, [material, materialRef]);
|
|
54
54
|
return (_jsx("mesh", { material: material, renderOrder: -1, children: _jsx("sphereGeometry", { args: [radius, 32, 16] }) }));
|
|
55
55
|
}
|
|
56
|
-
export function Daylight({ sky, fog, sun, ambient } = {}) {
|
|
56
|
+
export function Daylight({ sky, fog, sun, ambient, lights = true } = {}) {
|
|
57
57
|
const sunPosition = sun?.position ?? [120, 160, 70];
|
|
58
|
-
return (_jsxs(_Fragment, { children: [sky === false ? null : _jsx(SkyDome, { ...(sky ?? {}) }), fog === false ? null : (_jsx("fog", { attach: "fog", args: [fog?.color ?? FOG_COLOR, fog?.near ?? 70, fog?.far ?? 260] })), _jsx("hemisphereLight", { args: [ambient?.skyColor ?? HEMI_SKY, ambient?.groundColor ?? HEMI_GROUND, ambient?.intensity ?? 0.55] }), _jsx("directionalLight", { position: [sunPosition[0], sunPosition[1], sunPosition[2]], intensity: sun?.intensity ?? 0.85, color: sun?.color ?? SUN_COLOR, castShadow: true })] }));
|
|
58
|
+
return (_jsxs(_Fragment, { children: [sky === false ? null : _jsx(SkyDome, { ...(sky ?? {}) }), fog === false ? null : (_jsx("fog", { attach: "fog", args: [fog?.color ?? FOG_COLOR, fog?.near ?? 70, fog?.far ?? 260] })), lights ? (_jsxs(_Fragment, { children: [_jsx("hemisphereLight", { args: [ambient?.skyColor ?? HEMI_SKY, ambient?.groundColor ?? HEMI_GROUND, ambient?.intensity ?? 0.55] }), _jsx("directionalLight", { position: [sunPosition[0], sunPosition[1], sunPosition[2]], intensity: sun?.intensity ?? 0.85, color: sun?.color ?? SUN_COLOR, castShadow: true })] })) : null] }));
|
|
59
59
|
}
|
|
60
60
|
/** Renders a fixed sky/sun/fog look sampled from `sky`'s preset (or, when `timeOfDay` is on but no clock drives it, its noon look). No per-frame updates. */
|
|
61
|
-
export function SkyDaylight({ sky }) {
|
|
61
|
+
export function SkyDaylight({ sky, lights = true }) {
|
|
62
62
|
const state = useMemo(() => daylightStateAt(SKY_PRESET_DAY_FRACTION[sky.preset], sky), [sky]);
|
|
63
|
-
return (_jsx(Daylight, { sky: { topColor: state.skyTop, horizonColor: state.skyBottom }, fog: { color: sky.fog?.color ?? state.background, near: sky.fog?.near, far: sky.fog?.far }, sun: { position: state.sunPosition, intensity: state.sunIntensity }, ambient: { intensity: state.ambientIntensity } }));
|
|
63
|
+
return (_jsx(Daylight, { sky: { topColor: state.skyTop, horizonColor: state.skyBottom }, fog: { color: sky.fog?.color ?? state.background, near: sky.fog?.near, far: sky.fog?.far }, sun: { position: state.sunPosition, intensity: state.sunIntensity }, ambient: { intensity: state.ambientIntensity }, lights: lights }));
|
|
64
64
|
}
|
|
65
65
|
/**
|
|
66
|
-
* Drives
|
|
67
|
-
*
|
|
66
|
+
* Drives sky/fog (and optional default lights) from the world clock when `sky.timeOfDay` and `clock`
|
|
67
|
+
* are both present. Authored `PlayableGame.lighting` is never rewritten — pass `lights={false}` so
|
|
68
|
+
* only dome colors and fog track the day fraction.
|
|
68
69
|
*/
|
|
69
|
-
export function TimeOfDayDaylight({ sky, clock }) {
|
|
70
|
+
export function TimeOfDayDaylight({ sky, clock, lights = true }) {
|
|
70
71
|
if (!sky.timeOfDay || clock === undefined)
|
|
71
|
-
return _jsx(SkyDaylight, { sky: sky });
|
|
72
|
-
return _jsx(DrivenDaylight, { sky: sky, clock: clock });
|
|
72
|
+
return _jsx(SkyDaylight, { sky: sky, lights: lights });
|
|
73
|
+
return _jsx(DrivenDaylight, { sky: sky, clock: clock, lights: lights });
|
|
73
74
|
}
|
|
74
|
-
function DrivenDaylight({ sky, clock, }) {
|
|
75
|
+
function DrivenDaylight({ sky, clock, lights, }) {
|
|
75
76
|
const initial = useMemo(() => daylightStateAt(clock.calendar().dayFraction, sky), [clock, sky]);
|
|
76
77
|
const sunRef = useRef(null);
|
|
77
78
|
const hemiRef = useRef(null);
|
|
@@ -96,5 +97,5 @@ function DrivenDaylight({ sky, clock, }) {
|
|
|
96
97
|
skyMaterial.uniforms.bottomColor.value.set(state.skyBottom);
|
|
97
98
|
}
|
|
98
99
|
});
|
|
99
|
-
return (_jsxs(_Fragment, { children: [_jsx(SkyDome, { topColor: initial.skyTop, horizonColor: initial.skyBottom, materialRef: skyMaterialRef }), _jsx("fog", { attach: "fog", ref: fogRef, args: [sky.fog?.color ?? initial.background, sky.fog?.near ?? 70, sky.fog?.far ?? 260] }), _jsx("hemisphereLight", { ref: hemiRef, args: [HEMI_SKY, HEMI_GROUND, initial.ambientIntensity] }), _jsx("directionalLight", { ref: sunRef, position: initial.sunPosition, intensity: initial.sunIntensity, color: SUN_COLOR, castShadow: true })] }));
|
|
100
|
+
return (_jsxs(_Fragment, { children: [_jsx(SkyDome, { topColor: initial.skyTop, horizonColor: initial.skyBottom, materialRef: skyMaterialRef }), _jsx("fog", { attach: "fog", ref: fogRef, args: [sky.fog?.color ?? initial.background, sky.fog?.near ?? 70, sky.fog?.far ?? 260] }), lights ? (_jsxs(_Fragment, { children: [_jsx("hemisphereLight", { ref: hemiRef, args: [HEMI_SKY, HEMI_GROUND, initial.ambientIntensity] }), _jsx("directionalLight", { ref: sunRef, position: initial.sunPosition, intensity: initial.sunIntensity, color: SUN_COLOR, castShadow: true })] })) : null] }));
|
|
100
101
|
}
|
|
@@ -1,67 +1,114 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useMemo } from "react";
|
|
2
|
+
import { useMemo, useRef } from "react";
|
|
3
|
+
import { useFrame } from "@react-three/fiber";
|
|
4
|
+
import { useGameContext } from "@jgengine/react/provider";
|
|
5
|
+
import { resolveBuildingPalette } from "@jgengine/core/world/buildings";
|
|
3
6
|
import { resolveStructureBuildings } from "@jgengine/core/world/environmentSummary";
|
|
4
|
-
import {
|
|
5
|
-
import { SkyDaylight } from "./Daylight.js";
|
|
7
|
+
import { createTerrainPaletteSampler, resolveEnvironmentField, resolveTerrainPalette, } from "@jgengine/core/world/terrain";
|
|
6
8
|
import { GroundPad } from "./GroundPad.js";
|
|
7
|
-
import {
|
|
9
|
+
import { InstancedBuildings } from "../structures/GeneratedBuilding.js";
|
|
8
10
|
import { GrassField } from "../terrain/GrassField.js";
|
|
9
|
-
import {
|
|
11
|
+
import { CarvedTerrain } from "../terrain/CarvedTerrain.js";
|
|
10
12
|
import { Ocean } from "../water/Ocean.js";
|
|
11
13
|
import { RainField } from "../weather/RainField.js";
|
|
12
14
|
import { SnowField } from "../weather/SnowField.js";
|
|
13
15
|
import { WeatherUniformProvider } from "../weather/weatherUniforms.js";
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
16
|
+
function TerrainGround({ terrain, field, center, }) {
|
|
17
|
+
const palette = useMemo(() => resolveTerrainPalette(terrain), [terrain]);
|
|
18
|
+
const paletteAt = useMemo(() => {
|
|
19
|
+
if (terrain.materialRegions === undefined || terrain.materialRegions.length === 0)
|
|
20
|
+
return undefined;
|
|
21
|
+
const sampler = createTerrainPaletteSampler(terrain);
|
|
22
|
+
if (center === undefined)
|
|
23
|
+
return sampler;
|
|
24
|
+
return (x, z) => sampler(x - center[0], z - center[1]);
|
|
25
|
+
}, [terrain, center]);
|
|
26
|
+
const colors = useMemo(() => ({
|
|
27
|
+
low: palette.low,
|
|
28
|
+
high: palette.high,
|
|
29
|
+
...(terrain.waterLevel === undefined ? {} : { waterline: palette.waterline, waterlineHeight: terrain.waterLevel }),
|
|
30
|
+
}), [palette, terrain.waterLevel]);
|
|
31
|
+
const size = useMemo(() => [terrain.bounds.w, terrain.bounds.d], [terrain.bounds]);
|
|
32
|
+
const heightRange = useMemo(() => {
|
|
33
|
+
const base = terrain.baseHeight ?? 0;
|
|
34
|
+
const swing = terrain.height * 1.2;
|
|
35
|
+
return [base - swing, base + swing];
|
|
36
|
+
}, [terrain.baseHeight, terrain.height]);
|
|
37
|
+
return (_jsx(CarvedTerrain, { field: field, size: size, segments: terrain.segments, colors: colors, heightRange: heightRange, paletteAt: paletteAt, center: center, roughness: 0.94 }));
|
|
38
|
+
}
|
|
39
|
+
function areaCenter(area) {
|
|
40
|
+
return area.position ?? [0, 0];
|
|
31
41
|
}
|
|
32
42
|
function Vegetation({ grass, field }) {
|
|
33
|
-
|
|
43
|
+
const [cx, cz] = areaCenter(grass.area);
|
|
44
|
+
const heightAt = useMemo(() => cx === 0 && cz === 0
|
|
45
|
+
? field.sampleHeight
|
|
46
|
+
: (x, z) => field.sampleHeight(x + cx, z + cz), [cx, cz, field]);
|
|
47
|
+
const area = useMemo(() => [grass.area.w, grass.area.d], [grass.area.w, grass.area.d]);
|
|
48
|
+
const wind = useMemo(() => ({ strength: grass.windStrength }), [grass.windStrength]);
|
|
49
|
+
const colorBase = grass.colors[0];
|
|
50
|
+
const colorTip = grass.colors[grass.colors.length - 1];
|
|
51
|
+
return (_jsx("group", { position: [cx, 0, cz], children: _jsx(GrassField, { area: area, density: grass.density, seed: grass.seed, bladeHeight: grass.bladeHeight, bladeWidth: grass.bladeWidth, heightAt: heightAt, colorBase: colorBase, colorTip: colorTip, wind: wind, frustumCulled: true }) }));
|
|
34
52
|
}
|
|
35
53
|
function weatherVolume(area) {
|
|
36
54
|
return [area.w, area.h ?? 60, area.d];
|
|
37
55
|
}
|
|
38
56
|
function Precipitation({ weather }) {
|
|
57
|
+
const [cx, cz] = areaCenter(weather.area);
|
|
39
58
|
if (weather.kind === "rain") {
|
|
40
|
-
return (_jsx(RainField, { density: weather.density, speed: weather.speed, length: weather.dropLength, color: weather.color, wind: [weather.wind[0], 0, weather.wind[1]], volume: weatherVolume(weather.area) }));
|
|
59
|
+
return (_jsx("group", { position: [cx, 0, cz], children: _jsx(RainField, { density: weather.density, speed: weather.speed, length: weather.dropLength, color: weather.color, wind: [weather.wind[0], 0, weather.wind[1]], volume: weatherVolume(weather.area) }) }));
|
|
41
60
|
}
|
|
42
|
-
return (_jsx(SnowField, { density: weather.density, speed: weather.speed, size: weather.flakeSize, sway: weather.drift, color: weather.color, wind: [weather.wind[0], 0, weather.wind[1]], volume: weatherVolume(weather.area) }));
|
|
61
|
+
return (_jsx("group", { position: [cx, 0, cz], children: _jsx(SnowField, { density: weather.density, speed: weather.speed, size: weather.flakeSize, sway: weather.drift, color: weather.color, wind: [weather.wind[0], 0, weather.wind[1]], volume: weatherVolume(weather.area) }) }));
|
|
43
62
|
}
|
|
44
63
|
function Weather({ weather }) {
|
|
45
64
|
return (_jsx(WeatherUniformProvider, { children: weather.map((entry, index) => (_jsx(Precipitation, { weather: entry, index: index }, `${entry.kind}-${index}`))) }));
|
|
46
65
|
}
|
|
66
|
+
function oceanConfig(ocean) {
|
|
67
|
+
return {
|
|
68
|
+
size: ocean.bounds.w,
|
|
69
|
+
depth: ocean.bounds.d,
|
|
70
|
+
amplitude: ocean.waveHeight,
|
|
71
|
+
speed: ocean.waveSpeed,
|
|
72
|
+
waveScale: ocean.waveScale,
|
|
73
|
+
color: { shallow: ocean.color },
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function useOceanConfig(ocean) {
|
|
77
|
+
return useMemo(() => oceanConfig(ocean), [ocean.bounds.w, ocean.bounds.d, ocean.waveHeight, ocean.waveSpeed, ocean.color]);
|
|
78
|
+
}
|
|
79
|
+
function DynamicWater({ ocean }) {
|
|
80
|
+
const ctx = useGameContext();
|
|
81
|
+
const groupRef = useRef(null);
|
|
82
|
+
const [x, z] = ocean.position ?? [0, 0];
|
|
83
|
+
const config = useOceanConfig(ocean);
|
|
84
|
+
useFrame(() => {
|
|
85
|
+
if (groupRef.current !== null)
|
|
86
|
+
groupRef.current.position.y = ocean.levelAt(ctx.time.now());
|
|
87
|
+
});
|
|
88
|
+
return (_jsx("group", { ref: groupRef, position: [0, ocean.level, 0], children: _jsx(Ocean, { position: [x, 0, z], config: config }) }));
|
|
89
|
+
}
|
|
47
90
|
function Water({ ocean }) {
|
|
48
91
|
const [x, z] = ocean.position ?? [0, 0];
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
} }));
|
|
92
|
+
const config = useOceanConfig(ocean);
|
|
93
|
+
if (ocean.levelAt !== undefined) {
|
|
94
|
+
return _jsx(DynamicWater, { ocean: ocean });
|
|
95
|
+
}
|
|
96
|
+
return _jsx(Ocean, { position: [x, ocean.level, z], config: config });
|
|
55
97
|
}
|
|
56
98
|
function Structures({ structures, field }) {
|
|
57
|
-
const
|
|
58
|
-
|
|
99
|
+
const placements = useMemo(() => resolveStructureBuildings(structures).map((building) => ({
|
|
100
|
+
building,
|
|
101
|
+
position: [0, field.sampleHeight(building.center[0], building.center[1]), 0],
|
|
102
|
+
})), [structures, field]);
|
|
103
|
+
const palette = useMemo(() => resolveBuildingPalette(structures.style, structures.palette), [structures.style, structures.palette]);
|
|
104
|
+
return _jsx(InstancedBuildings, { buildings: placements, palette: palette });
|
|
59
105
|
}
|
|
60
106
|
export function EnvironmentScene({ feature }) {
|
|
61
|
-
const field = useMemo(() =>
|
|
107
|
+
const field = useMemo(() => resolveEnvironmentField(feature), [feature]);
|
|
62
108
|
const vegetation = feature.vegetation ?? [];
|
|
63
109
|
const water = feature.water ?? [];
|
|
64
110
|
const structures = feature.structures ?? [];
|
|
65
111
|
const pads = feature.pads ?? [];
|
|
66
|
-
|
|
112
|
+
const islands = feature.islands ?? [];
|
|
113
|
+
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}`))), 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 }, `grass-${index}`))), feature.weather !== undefined && feature.weather.length > 0 ? (_jsx(Weather, { weather: feature.weather })) : null] }));
|
|
67
114
|
}
|
|
@@ -9,5 +9,5 @@ export type PadShape = {
|
|
|
9
9
|
depth: number;
|
|
10
10
|
};
|
|
11
11
|
export declare function resolvePadShape(size: PadSize): PadShape;
|
|
12
|
-
export declare function resolvePadSurfaceY(groundHeight: number, pad: Pick<PadEnvironmentDescriptor, "height">): number;
|
|
13
|
-
export declare function resolvePadMeshY(groundHeight: number, pad: Pick<PadEnvironmentDescriptor, "height">): number;
|
|
12
|
+
export declare function resolvePadSurfaceY(groundHeight: number, pad: Pick<PadEnvironmentDescriptor, "height" | "elevation">): number;
|
|
13
|
+
export declare function resolvePadMeshY(groundHeight: number, pad: Pick<PadEnvironmentDescriptor, "height" | "elevation">): number;
|
|
@@ -3,7 +3,7 @@ export function resolvePadShape(size) {
|
|
|
3
3
|
return "radius" in size ? { circular: true, radius: size.radius } : { circular: false, width: size[0], depth: size[1] };
|
|
4
4
|
}
|
|
5
5
|
export function resolvePadSurfaceY(groundHeight, pad) {
|
|
6
|
-
return groundHeight + pad.height;
|
|
6
|
+
return pad.elevation ?? groundHeight + pad.height;
|
|
7
7
|
}
|
|
8
8
|
export function resolvePadMeshY(groundHeight, pad) {
|
|
9
9
|
return resolvePadSurfaceY(groundHeight, pad) - PAD_THICKNESS / 2;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
export { Daylight, SkyDaylight, SkyDome, TimeOfDayDaylight, type DaylightProps, type SkyDomeProps, type TimeOfDayDaylightProps, } from "./Daylight.js";
|
|
1
|
+
export { Daylight, SkyDaylight, SkyDome, TimeOfDayDaylight, type DaylightProps, type SkyDaylightProps, type SkyDomeProps, type TimeOfDayDaylightProps, } from "./Daylight.js";
|
|
2
2
|
export { EnvironmentScene, type EnvironmentSceneProps } from "./EnvironmentScene.js";
|
|
3
3
|
export { daylightStateAt, lerpHexColor, SKY_PRESET_DAY_FRACTION, type DaylightCycleConfig, type DaylightState, } from "./daylightCycle.js";
|
|
4
|
+
export { resolveSkyLightOwnership, skyEmitsLights, type SkyLightOwnership, } from "./skyLightingPolicy.js";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { Daylight, SkyDaylight, SkyDome, TimeOfDayDaylight, } from "./Daylight.js";
|
|
2
2
|
export { EnvironmentScene } from "./EnvironmentScene.js";
|
|
3
3
|
export { daylightStateAt, lerpHexColor, SKY_PRESET_DAY_FRACTION, } from "./daylightCycle.js";
|
|
4
|
+
export { resolveSkyLightOwnership, skyEmitsLights, } from "./skyLightingPolicy.js";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Policy for composing sky backdrops with `PlayableGame.lighting`:
|
|
3
|
+
* - authored lighting present → sky renders dome + fog only; lights stay game-owned
|
|
4
|
+
* - no authored lighting → sky may emit its default sun/hemisphere with the dome
|
|
5
|
+
* Time-of-day never rewrites configured lights; it only drives sky colors/fog (and
|
|
6
|
+
* sky-owned lights when the game did not author lighting).
|
|
7
|
+
*/
|
|
8
|
+
export type SkyLightOwnership = "authored" | "sky-default";
|
|
9
|
+
export declare function resolveSkyLightOwnership(hasAuthoredLighting: boolean): SkyLightOwnership;
|
|
10
|
+
export declare function skyEmitsLights(ownership: SkyLightOwnership): boolean;
|