@jgengine/core 0.18.0 → 0.19.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 +265 -2
- package/dist/ai/decisionGraph.d.ts +96 -0
- package/dist/ai/decisionGraph.js +212 -0
- package/dist/ai/groupAssist.d.ts +10 -0
- package/dist/ai/groupAssist.js +11 -0
- package/dist/ai/mobBrainGraph.d.ts +3 -0
- package/dist/ai/mobBrainGraph.js +25 -0
- package/dist/ai/pursuit.d.ts +1 -1
- package/dist/ai/pursuit.js +1 -1
- package/dist/ai/tacticalQueries.d.ts +30 -0
- package/dist/ai/tacticalQueries.js +45 -0
- package/dist/ai/threat.d.ts +8 -0
- package/dist/ai/threat.js +10 -0
- package/dist/anim/animGraph.d.ts +155 -0
- package/dist/anim/animGraph.js +414 -0
- package/dist/anim/boneTexture.d.ts +55 -0
- package/dist/anim/boneTexture.js +43 -0
- package/dist/anim/footPlacement.d.ts +57 -0
- package/dist/anim/footPlacement.js +67 -0
- package/dist/anim/ikSolver.d.ts +51 -0
- package/dist/anim/ikSolver.js +124 -0
- package/dist/anim/locomotionGraph.d.ts +35 -0
- package/dist/anim/locomotionGraph.js +62 -0
- package/dist/assets/spriteAtlas.d.ts +21 -0
- package/dist/assets/spriteAtlas.js +103 -0
- package/dist/audio/audioFalloff.d.ts +12 -0
- package/dist/audio/doppler.d.ts +21 -0
- package/dist/audio/doppler.js +32 -0
- package/dist/audio/engineLayers.d.ts +83 -0
- package/dist/audio/engineLayers.js +115 -0
- package/dist/combat/abilityKit.d.ts +1 -0
- package/dist/combat/abilityKit.js +1 -0
- package/dist/combat/death.d.ts +7 -0
- package/dist/combat/shotOrigin.d.ts +6 -0
- package/dist/combat/shotOrigin.js +6 -0
- package/dist/combat/weaponFeedback.d.ts +36 -0
- package/dist/combat/weaponFeedback.js +42 -0
- package/dist/combat/weaponHandling.d.ts +161 -0
- package/dist/combat/weaponHandling.js +202 -0
- package/dist/combat/weaponPresentation.d.ts +115 -0
- package/dist/combat/weaponPresentation.js +96 -0
- package/dist/devtools/warnOnce.d.ts +8 -0
- package/dist/devtools/warnOnce.js +18 -0
- package/dist/economy/currency.d.ts +16 -2
- package/dist/economy/currency.js +55 -14
- package/dist/economy/wallet.d.ts +5 -4
- package/dist/economy/wallet.js +8 -4
- package/dist/editor/commandApply.js +1 -0
- package/dist/editor/commands.d.ts +4 -1
- package/dist/editor/document.d.ts +2 -1
- package/dist/editor/document.js +92 -1
- package/dist/editor/environment.d.ts +3 -0
- package/dist/editor/environment.js +24 -0
- package/dist/editor/index.d.ts +4 -2
- package/dist/editor/index.js +1 -1
- package/dist/editor/types.d.ts +20 -0
- package/dist/faction/reputation.d.ts +6 -0
- package/dist/faction/reputation.js +11 -0
- package/dist/game/cameraConfig.d.ts +59 -1
- package/dist/game/chat.d.ts +22 -1
- package/dist/game/chat.js +48 -19
- package/dist/game/controlGate.d.ts +6 -0
- package/dist/game/controlGate.js +30 -1
- package/dist/game/defineGame.d.ts +10 -1
- package/dist/game/events.d.ts +14 -1
- package/dist/game/lootTable.d.ts +4 -0
- package/dist/game/lootTable.js +16 -0
- package/dist/game/pixelPerfect.d.ts +25 -0
- package/dist/game/pixelPerfect.js +29 -0
- package/dist/game/playableGame.d.ts +167 -6
- package/dist/game/systemRuntime.js +6 -1
- package/dist/game/toasts.d.ts +8 -0
- package/dist/game/toasts.js +7 -0
- package/dist/game/vfxInstance.d.ts +9 -0
- package/dist/game/vfxInstance.js +12 -1
- package/dist/game/viewports.d.ts +43 -0
- package/dist/game/viewports.js +46 -0
- package/dist/game/worldItem.d.ts +5 -1
- package/dist/game/worldItem.js +0 -1
- package/dist/input/actionBindings.d.ts +17 -2
- package/dist/input/actionBindings.js +24 -11
- package/dist/input/actionContexts.d.ts +45 -0
- package/dist/input/actionContexts.js +113 -0
- package/dist/input/axisInput.d.ts +4 -0
- package/dist/input/axisInput.js +4 -0
- package/dist/input/axisShaper.d.ts +73 -0
- package/dist/input/axisShaper.js +105 -0
- package/dist/input/gamepadModel.d.ts +79 -0
- package/dist/input/gamepadModel.js +108 -0
- package/dist/input/haptics.d.ts +50 -0
- package/dist/input/haptics.js +74 -0
- package/dist/input/inputBuffer.d.ts +40 -0
- package/dist/input/inputBuffer.js +59 -0
- package/dist/input/lookChannel.d.ts +10 -0
- package/dist/input/lookChannel.js +6 -0
- package/dist/input/pointer.d.ts +2 -0
- package/dist/input/pointer.js +4 -0
- package/dist/item/affix.d.ts +6 -0
- package/dist/item/affix.js +6 -0
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +196 -1
- package/dist/movement/characterController.d.ts +76 -0
- package/dist/movement/characterController.js +216 -0
- package/dist/movement/freeFlight.d.ts +119 -0
- package/dist/movement/freeFlight.js +294 -0
- package/dist/movement/glideModel.d.ts +3 -0
- package/dist/movement/glideModel.js +6 -0
- package/dist/movement/grappleSwing.d.ts +2 -0
- package/dist/movement/grappleSwing.js +5 -1
- package/dist/movement/movementModel.d.ts +39 -0
- package/dist/movement/movementModel.js +62 -19
- package/dist/movement/movementProbe.d.ts +51 -0
- package/dist/movement/movementProbe.js +119 -0
- package/dist/movement/playerMovement.d.ts +30 -3
- package/dist/movement/playerMovement.js +304 -7
- package/dist/movement/serverStep.d.ts +5 -0
- package/dist/movement/serverStep.js +6 -0
- package/dist/movement/solidObstacles.d.ts +39 -8
- package/dist/movement/solidObstacles.js +71 -6
- package/dist/movement/voxelController.js +7 -7
- package/dist/multiplayer/poseSyncGate.d.ts +3 -1
- package/dist/multiplayer/poseSyncGate.js +9 -1
- package/dist/multiplayer/presenceContract.d.ts +6 -0
- package/dist/multiplayer/presenceModel.d.ts +2 -0
- package/dist/multiplayer/presenceModel.js +11 -0
- package/dist/nav/navFromEnvironment.d.ts +9 -3
- package/dist/nav/navFromEnvironment.js +18 -12
- package/dist/nav/navMesh.d.ts +74 -0
- package/dist/nav/navMesh.js +570 -0
- package/dist/physics/aircraftDynamics.d.ts +390 -0
- package/dist/physics/aircraftDynamics.js +704 -0
- package/dist/physics/boatDynamics.d.ts +116 -0
- package/dist/physics/boatDynamics.js +160 -0
- package/dist/physics/drivableVehicle.d.ts +22 -8
- package/dist/physics/drivableVehicle.js +4 -2
- package/dist/physics/flightDynamics.d.ts +29 -4
- package/dist/physics/flightDynamics.js +28 -5
- package/dist/physics/handlingProbe.d.ts +249 -0
- package/dist/physics/handlingProbe.js +606 -0
- package/dist/physics/kinematicVehicle.d.ts +37 -2
- package/dist/physics/kinematicVehicle.js +15 -0
- package/dist/physics/physicsBackend.d.ts +203 -0
- package/dist/physics/physicsBackend.js +49 -0
- package/dist/physics/physicsBackendConformance.d.ts +27 -0
- package/dist/physics/physicsBackendConformance.js +151 -0
- package/dist/physics/physicsWorldBackend.d.ts +45 -0
- package/dist/physics/physicsWorldBackend.js +518 -0
- package/dist/physics/vehicleBackendLink.d.ts +49 -0
- package/dist/physics/vehicleBackendLink.js +102 -0
- package/dist/physics/vehicleDynamics.d.ts +374 -0
- package/dist/physics/vehicleDynamics.js +737 -0
- package/dist/physics/worldColliders.d.ts +13 -0
- package/dist/physics/worldColliders.js +128 -0
- package/dist/puzzle/cellGrid.d.ts +15 -9
- package/dist/puzzle/cellGrid.js +13 -9
- package/dist/random/rng.d.ts +12 -1
- package/dist/random/rng.js +20 -3
- package/dist/render/environment.d.ts +23 -0
- package/dist/render/environment.js +1 -0
- package/dist/render/lookPreset.d.ts +19 -6
- package/dist/render/lookPreset.js +56 -4
- package/dist/render/postProcessing.d.ts +40 -1
- package/dist/render/postProcessing.js +1 -1
- package/dist/render/sprite2d.d.ts +38 -0
- package/dist/render/sprite2d.js +40 -0
- package/dist/runtime/adapter.d.ts +20 -1
- package/dist/runtime/adapter.js +8 -1
- package/dist/runtime/cameraDirector.d.ts +17 -3
- package/dist/runtime/cameraDirector.js +25 -0
- package/dist/runtime/commandInput.d.ts +52 -0
- package/dist/runtime/commandInput.js +71 -0
- package/dist/runtime/commandRunner.d.ts +39 -7
- package/dist/runtime/commandRunner.js +35 -5
- package/dist/runtime/context/combat.d.ts +1 -0
- package/dist/runtime/context/combat.js +2 -1
- package/dist/runtime/context/deathLoot.d.ts +2 -0
- package/dist/runtime/context/deathLoot.js +1 -0
- package/dist/runtime/gameContext.js +18 -1
- package/dist/runtime/gameContextTypes.d.ts +33 -7
- package/dist/runtime/gameRuntime.d.ts +23 -5
- package/dist/runtime/gameRuntime.js +21 -4
- package/dist/runtime/headlessRunner.d.ts +10 -0
- package/dist/runtime/headlessRunner.js +26 -8
- package/dist/runtime/hostPolicy.d.ts +3 -2
- package/dist/runtime/hostPolicy.js +8 -10
- package/dist/runtime/hostedGameRunner.js +34 -6
- package/dist/runtime/hostedWorldSession.d.ts +12 -16
- package/dist/runtime/hostedWorldSession.js +29 -6
- package/dist/runtime/hostedWorldStore.d.ts +20 -0
- package/dist/runtime/hostedWorldStore.js +1 -0
- package/dist/runtime/inputRecorder.d.ts +30 -0
- package/dist/runtime/inputRecorder.js +59 -0
- package/dist/runtime/inputSnapshot.d.ts +14 -0
- package/dist/runtime/inputSnapshot.js +11 -0
- package/dist/runtime/localPlayers.d.ts +73 -0
- package/dist/runtime/localPlayers.js +119 -0
- package/dist/runtime/poseInterpolation.d.ts +36 -0
- package/dist/runtime/poseInterpolation.js +91 -0
- package/dist/runtime/prediction.d.ts +45 -0
- package/dist/runtime/prediction.js +62 -0
- package/dist/runtime/simContext.d.ts +45 -0
- package/dist/runtime/simContext.js +65 -0
- package/dist/runtime/simLoop.d.ts +52 -0
- package/dist/runtime/simLoop.js +72 -0
- package/dist/runtime/simSnapshot.d.ts +32 -0
- package/dist/runtime/simSnapshot.js +60 -0
- package/dist/runtime/snapshot.d.ts +8 -0
- package/dist/runtime/snapshotBuffer.d.ts +33 -0
- package/dist/runtime/snapshotBuffer.js +58 -0
- package/dist/runtime/transport.d.ts +41 -2
- package/dist/runtime/transport.js +7 -1
- package/dist/runtime/worldChunks.d.ts +2 -0
- package/dist/runtime/worldChunks.js +14 -0
- package/dist/runtime/worldProjection.d.ts +9 -0
- package/dist/runtime/worldProjection.js +17 -1
- package/dist/runtime/worldSnapshot.d.ts +6 -0
- package/dist/runtime/worldSnapshot.js +3 -0
- package/dist/scene/behaviorRuntime.d.ts +29 -1
- package/dist/scene/behaviorRuntime.js +71 -4
- package/dist/scene/behaviors.d.ts +12 -1
- package/dist/scene/builtinSceneKinds.js +10 -0
- package/dist/scene/sceneKinds.d.ts +9 -0
- package/dist/scene/vehicleSeat.d.ts +5 -1
- package/dist/scene/vehicleSeat.js +5 -1
- package/dist/sensor/concealment.d.ts +6 -0
- package/dist/sensor/concealment.js +8 -0
- package/dist/sensor/freezeMonitor.d.ts +6 -0
- package/dist/sensor/freezeMonitor.js +8 -0
- package/dist/sensor/perception.d.ts +70 -0
- package/dist/sensor/perception.js +189 -0
- package/dist/sensor/recordingBuffer.d.ts +3 -0
- package/dist/sensor/recordingBuffer.js +8 -0
- package/dist/session/roundState.d.ts +4 -0
- package/dist/session/roundState.js +11 -0
- package/dist/settings/frameRateLimit.d.ts +16 -0
- package/dist/settings/frameRateLimit.js +37 -0
- package/dist/settings/graphicsProfile.d.ts +21 -0
- package/dist/settings/graphicsProfile.js +48 -0
- package/dist/settings/graphicsSettings.d.ts +40 -0
- package/dist/settings/graphicsSettings.js +81 -0
- package/dist/settings/settingsModel.d.ts +9 -1
- package/dist/settings/settingsModel.js +10 -0
- package/dist/stats/eventMeter.d.ts +1 -1
- package/dist/stats/eventMeter.js +1 -1
- package/dist/time/accrueSince.d.ts +10 -0
- package/dist/time/accrueSince.js +9 -0
- package/dist/time/rateWindow.d.ts +16 -0
- package/dist/time/rateWindow.js +14 -0
- package/dist/time/serverTick.d.ts +21 -3
- package/dist/time/serverTick.js +14 -13
- package/dist/vfx/feedbackMixer.d.ts +61 -0
- package/dist/vfx/feedbackMixer.js +106 -0
- package/dist/vfx/particleDirector.d.ts +68 -0
- package/dist/vfx/particleDirector.js +81 -0
- package/dist/visibility/simulationCulling.d.ts +8 -1
- package/dist/visibility/simulationCulling.js +7 -0
- package/dist/visibility/spatialIndex.d.ts +21 -0
- package/dist/visibility/spatialIndex.js +16 -0
- package/dist/world/authoredSolids.d.ts +46 -0
- package/dist/world/authoredSolids.js +75 -0
- package/dist/world/buildingGenerator.js +2 -2
- package/dist/world/buildingKit.d.ts +3 -1
- package/dist/world/buildings.d.ts +46 -2
- package/dist/world/buildings.js +53 -3
- package/dist/world/cityContent.js +103 -25
- package/dist/world/cityKind.d.ts +139 -0
- package/dist/world/cityKind.js +367 -16
- package/dist/world/features.d.ts +56 -3
- package/dist/world/features.js +13 -2
- package/dist/world/footprintGrid.d.ts +7 -0
- package/dist/world/footprintGrid.js +16 -0
- package/dist/world/interiors.d.ts +1 -0
- package/dist/world/interiors.js +5 -3
- package/dist/world/lod.d.ts +6 -0
- package/dist/world/lod.js +8 -0
- package/dist/world/placement.d.ts +7 -0
- package/dist/world/placement.js +2 -0
- package/dist/world/placementController.d.ts +1 -1
- package/dist/world/placementController.js +7 -0
- package/dist/world/raceCircuit.d.ts +168 -0
- package/dist/world/raceCircuit.js +797 -0
- package/dist/world/terraform.d.ts +8 -0
- package/dist/world/terraform.js +7 -0
- package/dist/world/terrain.d.ts +5 -1
- package/dist/world/terrain.js +5 -0
- package/dist/world/territory.d.ts +97 -0
- package/dist/world/territory.js +182 -0
- package/dist/world/walls.d.ts +7 -0
- package/dist/world/walls.js +7 -0
- package/dist/world/worldSolids.d.ts +84 -0
- package/dist/world/worldSolids.js +240 -0
- package/dist/world.d.ts +5 -3
- package/dist/world.js +4 -2
- package/package.json +1 -1
- package/skills/ce-handoff/SKILL.md +2 -2
- package/skills/game-design/SKILL.md +11 -1
- package/skills/jgengine/SKILL.md +9 -5
- package/skills/jgengine/api.md +205 -101
- package/skills/jgengine/capabilities.md +56 -0
- package/skills/jgengine/recipes/minimal-game.md +3 -0
- package/skills/jgengine-combat/SKILL.md +5 -0
- package/skills/jgengine-combat/api.md +138 -103
- package/skills/jgengine-combat/capabilities.md +29 -1
- package/skills/jgengine-combat/recipes/weapon-feel.md +50 -0
- package/skills/jgengine-gameplay/SKILL.md +10 -0
- package/skills/jgengine-gameplay/api.md +501 -453
- package/skills/jgengine-gameplay/capabilities.md +36 -0
- package/skills/jgengine-gameplay/reference-systems.md +4 -0
- package/skills/jgengine-world/SKILL.md +21 -3
- package/skills/jgengine-world/api.md +1172 -625
- package/skills/jgengine-world/capabilities.md +229 -3
- package/skills/jgengine-world/recipes/character-animation.md +39 -0
- package/skills/jgengine-world/recipes/controls.md +44 -0
- package/skills/jgengine-world/recipes/flight-feel.md +76 -0
- package/skills/jgengine-world/recipes/npc-senses.md +43 -0
- package/skills/jgengine-world/recipes/vehicle-feel.md +85 -0
- package/skills/jgengine-world/reference.md +31 -2
- package/skills/jgengine-world/visibility.md +112 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create the particle intent registry a `GameContext` exposes as `ctx.particles`.
|
|
3
|
+
* State is data-only and serializable: standing emitters survive `snapshot`/`restore`,
|
|
4
|
+
* queued bursts are transient. The renderer subscribes, drains bursts, and mirrors
|
|
5
|
+
* the emitter list — the director never allocates particle pools itself.
|
|
6
|
+
*
|
|
7
|
+
* @capability particle-director game-reachable VFX seam: queue one-shot particle bursts and keyed follow-capable standing emitters as serializable data for the shell to render
|
|
8
|
+
*/
|
|
9
|
+
export function createParticleDirector() {
|
|
10
|
+
let nextSeq = 1;
|
|
11
|
+
let bursts = [];
|
|
12
|
+
const emitters = new Map();
|
|
13
|
+
const listeners = new Set();
|
|
14
|
+
function notify() {
|
|
15
|
+
for (const listener of listeners)
|
|
16
|
+
listener();
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
burst(config, count, blending) {
|
|
20
|
+
const wanted = Math.floor(count);
|
|
21
|
+
if (wanted <= 0)
|
|
22
|
+
return;
|
|
23
|
+
bursts.push({ seq: nextSeq++, config, count: wanted, ...(blending === undefined ? {} : { blending }) });
|
|
24
|
+
notify();
|
|
25
|
+
},
|
|
26
|
+
attach(id, config, options) {
|
|
27
|
+
emitters.set(id, {
|
|
28
|
+
id,
|
|
29
|
+
config,
|
|
30
|
+
...(options?.follow === undefined ? {} : { follow: options.follow }),
|
|
31
|
+
...(options?.offset === undefined ? {} : { offset: options.offset }),
|
|
32
|
+
...(options?.blending === undefined ? {} : { blending: options.blending }),
|
|
33
|
+
});
|
|
34
|
+
notify();
|
|
35
|
+
},
|
|
36
|
+
retune(id, patch) {
|
|
37
|
+
const current = emitters.get(id);
|
|
38
|
+
if (current === undefined)
|
|
39
|
+
return;
|
|
40
|
+
emitters.set(id, { ...current, config: { ...current.config, ...patch } });
|
|
41
|
+
notify();
|
|
42
|
+
},
|
|
43
|
+
detach(id) {
|
|
44
|
+
if (!emitters.delete(id))
|
|
45
|
+
return;
|
|
46
|
+
notify();
|
|
47
|
+
},
|
|
48
|
+
clear() {
|
|
49
|
+
if (emitters.size === 0 && bursts.length === 0)
|
|
50
|
+
return;
|
|
51
|
+
emitters.clear();
|
|
52
|
+
bursts = [];
|
|
53
|
+
notify();
|
|
54
|
+
},
|
|
55
|
+
emitters() {
|
|
56
|
+
return Array.from(emitters.values());
|
|
57
|
+
},
|
|
58
|
+
drainBursts() {
|
|
59
|
+
if (bursts.length === 0)
|
|
60
|
+
return [];
|
|
61
|
+
const drained = bursts;
|
|
62
|
+
bursts = [];
|
|
63
|
+
return drained;
|
|
64
|
+
},
|
|
65
|
+
subscribe(listener) {
|
|
66
|
+
listeners.add(listener);
|
|
67
|
+
return () => listeners.delete(listener);
|
|
68
|
+
},
|
|
69
|
+
snapshot() {
|
|
70
|
+
return { nextSeq, emitters: Array.from(emitters.values()) };
|
|
71
|
+
},
|
|
72
|
+
restore(state) {
|
|
73
|
+
nextSeq = state.nextSeq;
|
|
74
|
+
bursts = [];
|
|
75
|
+
emitters.clear();
|
|
76
|
+
for (const spec of state.emitters)
|
|
77
|
+
emitters.set(spec.id, spec);
|
|
78
|
+
notify();
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { LodBand } from "../world/lod.js";
|
|
1
|
+
import type { LodBand, LodSchedulerState } from "../world/lod.js";
|
|
2
2
|
/**
|
|
3
3
|
* Simulation culling is a SEPARATE, opt-in system from render culling. Render culling only
|
|
4
4
|
* decides what is drawn; this decides whether a low-priority off-screen entity updates this
|
|
@@ -23,6 +23,11 @@ export interface SimulationDecision {
|
|
|
23
23
|
/** Seconds of accumulated time delivered when `update` is true (for catch-up integration). */
|
|
24
24
|
elapsed: number;
|
|
25
25
|
}
|
|
26
|
+
/** Plain JSON state of a {@link SimulationCuller}: the enabled flag and each id's throttle accumulator. */
|
|
27
|
+
export interface SimulationCullerState {
|
|
28
|
+
enabled: boolean;
|
|
29
|
+
scheduler: LodSchedulerState;
|
|
30
|
+
}
|
|
26
31
|
export interface SimulationCuller {
|
|
27
32
|
enabled(): boolean;
|
|
28
33
|
setEnabled(value: boolean): void;
|
|
@@ -30,5 +35,7 @@ export interface SimulationCuller {
|
|
|
30
35
|
step(id: string, distance: number, dt: number): SimulationDecision;
|
|
31
36
|
forget(id: string): void;
|
|
32
37
|
clear(): void;
|
|
38
|
+
snapshot(): SimulationCullerState;
|
|
39
|
+
restore(next: SimulationCullerState): void;
|
|
33
40
|
}
|
|
34
41
|
export declare function createSimulationCuller(options?: SimulationCullingOptions): SimulationCuller;
|
|
@@ -32,5 +32,12 @@ export function createSimulationCuller(options = {}) {
|
|
|
32
32
|
clear() {
|
|
33
33
|
scheduler.clear();
|
|
34
34
|
},
|
|
35
|
+
snapshot() {
|
|
36
|
+
return { enabled, scheduler: scheduler.snapshot() };
|
|
37
|
+
},
|
|
38
|
+
restore(next) {
|
|
39
|
+
enabled = next.enabled;
|
|
40
|
+
scheduler.restore(next.scheduler);
|
|
41
|
+
},
|
|
35
42
|
};
|
|
36
43
|
}
|
|
@@ -10,6 +10,25 @@ export interface SpatialIndexOptions {
|
|
|
10
10
|
*/
|
|
11
11
|
readonly maxCellSpan?: number;
|
|
12
12
|
}
|
|
13
|
+
/** Integer cell bounds an object covers, inclusive on both ends. */
|
|
14
|
+
export interface SpatialCellRange {
|
|
15
|
+
x0: number;
|
|
16
|
+
y0: number;
|
|
17
|
+
z0: number;
|
|
18
|
+
x1: number;
|
|
19
|
+
y1: number;
|
|
20
|
+
z1: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Plain JSON state of a {@link SpatialIndex}. Cells and their ids keep insertion order, so a restored
|
|
24
|
+
* index returns query results in the same order.
|
|
25
|
+
*/
|
|
26
|
+
export interface SpatialIndexState {
|
|
27
|
+
/** Per object: its cell range (`null` when oversized) and whether it was inserted as dynamic. */
|
|
28
|
+
entries: [id: string, range: SpatialCellRange | null, dynamic: boolean][];
|
|
29
|
+
cells: [x: number, y: number, z: number, ids: string[]][];
|
|
30
|
+
oversized: string[];
|
|
31
|
+
}
|
|
13
32
|
/**
|
|
14
33
|
* A uniform 3D spatial hash the renderer and streaming system query for potentially-visible
|
|
15
34
|
* objects instead of scanning the whole scene. Objects are keyed by their world AABB into
|
|
@@ -39,6 +58,8 @@ export interface SpatialIndex {
|
|
|
39
58
|
}[];
|
|
40
59
|
cellSize(): number;
|
|
41
60
|
clear(): void;
|
|
61
|
+
snapshot(): SpatialIndexState;
|
|
62
|
+
restore(next: SpatialIndexState): void;
|
|
42
63
|
}
|
|
43
64
|
export declare function createSpatialIndex(options?: SpatialIndexOptions): SpatialIndex;
|
|
44
65
|
/** Precise AABB-vs-AABB test re-exported for callers refining a broad-phase result. */
|
|
@@ -206,6 +206,22 @@ export function createSpatialIndex(options = {}) {
|
|
|
206
206
|
entries.clear();
|
|
207
207
|
stamps.clear();
|
|
208
208
|
},
|
|
209
|
+
snapshot() {
|
|
210
|
+
return {
|
|
211
|
+
entries: [...entries].map(([id, entry]) => [id, entry.range === null ? null : { ...entry.range }, entry.dynamic]),
|
|
212
|
+
cells: [...cells.values()].map((bucket) => [bucket.x, bucket.y, bucket.z, [...bucket.ids]]),
|
|
213
|
+
oversized: [...oversized],
|
|
214
|
+
};
|
|
215
|
+
},
|
|
216
|
+
restore(next) {
|
|
217
|
+
this.clear();
|
|
218
|
+
for (const [id, range, dynamic] of next.entries)
|
|
219
|
+
entries.set(id, { range: range === null ? null : { ...range }, dynamic });
|
|
220
|
+
for (const [x, y, z, ids] of next.cells)
|
|
221
|
+
cells.set(packCell(x, y, z), { x, y, z, ids: new Set(ids) });
|
|
222
|
+
for (const id of next.oversized)
|
|
223
|
+
oversized.add(id);
|
|
224
|
+
},
|
|
209
225
|
};
|
|
210
226
|
}
|
|
211
227
|
/** Precise AABB-vs-AABB test re-exported for callers refining a broad-phase result. */
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { WorldSolid, WorldSolids } from "./worldSolids.js";
|
|
2
|
+
type Vec3Like = {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
z: number;
|
|
6
|
+
};
|
|
7
|
+
/** Minimal editor document shape {@link resolveAuthoredSolids} walks; any `EditorDocument` satisfies it. */
|
|
8
|
+
export interface AuthoredSolidsDocumentLike {
|
|
9
|
+
markers: readonly {
|
|
10
|
+
id: string;
|
|
11
|
+
kind: string;
|
|
12
|
+
position: Vec3Like;
|
|
13
|
+
rotationY?: number;
|
|
14
|
+
meta?: Record<string, unknown>;
|
|
15
|
+
}[];
|
|
16
|
+
volumes: readonly {
|
|
17
|
+
id: string;
|
|
18
|
+
kind: string;
|
|
19
|
+
center: Vec3Like;
|
|
20
|
+
halfExtents?: Vec3Like;
|
|
21
|
+
radius?: number;
|
|
22
|
+
meta?: Record<string, unknown>;
|
|
23
|
+
}[];
|
|
24
|
+
paths: readonly {
|
|
25
|
+
id: string;
|
|
26
|
+
kind: string;
|
|
27
|
+
points: readonly Vec3Like[];
|
|
28
|
+
meta?: Record<string, unknown>;
|
|
29
|
+
}[];
|
|
30
|
+
}
|
|
31
|
+
/** Layer prefix `syncAuthoredSolids` owns in `ctx.world.solids`; one layer per document object. */
|
|
32
|
+
export declare const AUTHORED_SOLID_LAYER_PREFIX = "authored:";
|
|
33
|
+
/**
|
|
34
|
+
* Solids for every document object whose scene kind declares `solids`, keyed by object id. Objects
|
|
35
|
+
* whose kind has no `solids` hook, or whose hook returns none, are left out.
|
|
36
|
+
* @capability authored-solids collision for studio-authored world content such as city volumes
|
|
37
|
+
*/
|
|
38
|
+
export declare function resolveAuthoredSolids(document: AuthoredSolidsDocumentLike, sampleHeight?: (x: number, z: number) => number): Map<string, readonly WorldSolid[]>;
|
|
39
|
+
/**
|
|
40
|
+
* Write a document's studio solids into `solids` (usually `ctx.world.solids`), one
|
|
41
|
+
* `authored:<objectId>` layer per object, dropping authored layers the document no longer has.
|
|
42
|
+
* Call again whenever the document changes.
|
|
43
|
+
* @capability authored-solids collision for studio-authored world content such as city volumes
|
|
44
|
+
*/
|
|
45
|
+
export declare function syncAuthoredSolids(solids: WorldSolids, document: AuthoredSolidsDocumentLike, sampleHeight?: (x: number, z: number) => number): void;
|
|
46
|
+
export {};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { registerBuiltinSceneKinds } from "../scene/builtinSceneKinds.js";
|
|
2
|
+
import { getSceneKind, parseParams } from "../scene/sceneKinds.js";
|
|
3
|
+
/** Layer prefix `syncAuthoredSolids` owns in `ctx.world.solids`; one layer per document object. */
|
|
4
|
+
export const AUTHORED_SOLID_LAYER_PREFIX = "authored:";
|
|
5
|
+
function kindObjects(document) {
|
|
6
|
+
const objects = [];
|
|
7
|
+
for (const marker of document.markers) {
|
|
8
|
+
objects.push({
|
|
9
|
+
id: marker.id,
|
|
10
|
+
kind: marker.kind,
|
|
11
|
+
position: marker.position,
|
|
12
|
+
...(marker.rotationY === undefined ? {} : { rotationY: marker.rotationY }),
|
|
13
|
+
...(marker.meta === undefined ? {} : { meta: marker.meta }),
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
for (const volume of document.volumes) {
|
|
17
|
+
objects.push({
|
|
18
|
+
id: volume.id,
|
|
19
|
+
kind: volume.kind,
|
|
20
|
+
center: volume.center,
|
|
21
|
+
...(volume.halfExtents === undefined ? {} : { halfExtents: volume.halfExtents }),
|
|
22
|
+
...(volume.radius === undefined ? {} : { radius: volume.radius }),
|
|
23
|
+
...(volume.meta === undefined ? {} : { meta: volume.meta }),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
for (const path of document.paths) {
|
|
27
|
+
objects.push({
|
|
28
|
+
id: path.id,
|
|
29
|
+
kind: path.kind,
|
|
30
|
+
points: path.points,
|
|
31
|
+
...(path.meta === undefined ? {} : { meta: path.meta }),
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return objects;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Solids for every document object whose scene kind declares `solids`, keyed by object id. Objects
|
|
38
|
+
* whose kind has no `solids` hook, or whose hook returns none, are left out.
|
|
39
|
+
* @capability authored-solids collision for studio-authored world content such as city volumes
|
|
40
|
+
*/
|
|
41
|
+
export function resolveAuthoredSolids(document, sampleHeight) {
|
|
42
|
+
registerBuiltinSceneKinds();
|
|
43
|
+
const out = new Map();
|
|
44
|
+
const objects = kindObjects(document);
|
|
45
|
+
const context = { ...(sampleHeight === undefined ? {} : { sampleHeight }), objects };
|
|
46
|
+
for (const object of objects) {
|
|
47
|
+
const definition = getSceneKind(object.kind);
|
|
48
|
+
if (definition?.solids === undefined || definition.resolve === undefined)
|
|
49
|
+
continue;
|
|
50
|
+
const params = parseParams(definition.schema, object.meta);
|
|
51
|
+
const solids = definition.solids(definition.resolve(object, params, context), object, params, context);
|
|
52
|
+
if (solids.length > 0)
|
|
53
|
+
out.set(object.id, solids);
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Write a document's studio solids into `solids` (usually `ctx.world.solids`), one
|
|
59
|
+
* `authored:<objectId>` layer per object, dropping authored layers the document no longer has.
|
|
60
|
+
* Call again whenever the document changes.
|
|
61
|
+
* @capability authored-solids collision for studio-authored world content such as city volumes
|
|
62
|
+
*/
|
|
63
|
+
export function syncAuthoredSolids(solids, document, sampleHeight) {
|
|
64
|
+
const resolved = resolveAuthoredSolids(document, sampleHeight);
|
|
65
|
+
solids.batch(() => {
|
|
66
|
+
for (const layer of solids.layers()) {
|
|
67
|
+
if (!layer.startsWith(AUTHORED_SOLID_LAYER_PREFIX))
|
|
68
|
+
continue;
|
|
69
|
+
if (!resolved.has(layer.slice(AUTHORED_SOLID_LAYER_PREFIX.length)))
|
|
70
|
+
solids.remove(layer);
|
|
71
|
+
}
|
|
72
|
+
for (const [id, set] of resolved)
|
|
73
|
+
solids.set(`${AUTHORED_SOLID_LAYER_PREFIX}${id}`, set);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* @capability building-generator seeded building as a placeable parametric asset
|
|
8
8
|
*/
|
|
9
9
|
import { registerAssetGenerator, partsBounds } from "../scene/assetGenerator.js";
|
|
10
|
-
import { BUILDING_STYLE_PALETTES, DEFAULT_BUILDING_CONFIG, generateBuilding, resolveBuildingPalette } from "./buildings.js";
|
|
10
|
+
import { BUILDING_STYLE_PALETTES, DEFAULT_BUILDING_CONFIG, buildingSurfaceColor, generateBuilding, resolveBuildingPalette } from "./buildings.js";
|
|
11
11
|
/** The generator id a catalog entry / placed marker references. */
|
|
12
12
|
export const BUILDING_GENERATOR_ID = "building";
|
|
13
13
|
/** The building generator's slider schema — drives the inspector via #809. */
|
|
@@ -47,7 +47,7 @@ export function generateBuildingAsset(params, seed) {
|
|
|
47
47
|
position: part.position,
|
|
48
48
|
size: part.scale,
|
|
49
49
|
rotationY: part.rotationY,
|
|
50
|
-
color: palette[part.kind],
|
|
50
|
+
color: buildingSurfaceColor(palette[part.kind]),
|
|
51
51
|
}));
|
|
52
52
|
return { parts, bounds: partsBounds(parts) };
|
|
53
53
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* as an instanced asset kit instead of untextured blocks. Pure serializable data — core resolves
|
|
4
4
|
* bindings, the renderer loads them.
|
|
5
5
|
*/
|
|
6
|
-
import type { BuildingKitSlot, BuildingPartKind, BuildingVariantCounts, Vec3 } from "./buildings.js";
|
|
6
|
+
import type { BuildingKitSlot, BuildingPartKind, BuildingSurfaceMaterial, BuildingVariantCounts, Vec3 } from "./buildings.js";
|
|
7
7
|
/**
|
|
8
8
|
* How a kit model's native bounds are mapped onto the slot box `BuildingPartPlacement.scale`
|
|
9
9
|
* describes.
|
|
@@ -32,6 +32,8 @@ export interface BuildingKitPart {
|
|
|
32
32
|
/** Multiplier applied after `fit`. */
|
|
33
33
|
scale?: Vec3;
|
|
34
34
|
tint?: string;
|
|
35
|
+
/** Tiled PBR maps applied over the model's own materials; `color` tints, `repeat` sets the tiling. */
|
|
36
|
+
material?: BuildingSurfaceMaterial;
|
|
35
37
|
/**
|
|
36
38
|
* Quarter-turn the model when its long horizontal axis disagrees with the slot's, so a panel
|
|
37
39
|
* authored running along Z still tiles a bay that runs along X. Default true — modular kits do not
|
|
@@ -103,11 +103,55 @@ export interface BuildingGridConfig {
|
|
|
103
103
|
floorRange?: readonly [number, number];
|
|
104
104
|
}
|
|
105
105
|
export declare const DEFAULT_BUILDING_CONFIG: BuildingConfig;
|
|
106
|
-
|
|
106
|
+
/** URLs of the PBR maps a building surface tiles; `buildMaterialCatalog(...).resolve(id)!.maps` fits directly. */
|
|
107
|
+
export interface BuildingSurfaceMaps {
|
|
108
|
+
color?: string;
|
|
109
|
+
normal?: string;
|
|
110
|
+
roughness?: string;
|
|
111
|
+
ao?: string;
|
|
112
|
+
metalness?: string;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* A textured building surface: a base colour plus optional tiled PBR maps. With `maps`, `color`
|
|
116
|
+
* multiplies the albedo (leave it light to show the map as authored); without, it is the flat
|
|
117
|
+
* colour the part has always had.
|
|
118
|
+
*/
|
|
119
|
+
export interface BuildingSurfaceMaterial {
|
|
120
|
+
/** Base colour; the style's colour for that part kind when unset. */
|
|
121
|
+
color?: string;
|
|
122
|
+
maps?: BuildingSurfaceMaps;
|
|
123
|
+
/**
|
|
124
|
+
* Texture tiles per part slot along [x, y]; a number tiles both. Slots within one building share
|
|
125
|
+
* a size, so one repeat per kind is uniform there; it drifts across buildings with a different
|
|
126
|
+
* `bayWidth`. Default 1.
|
|
127
|
+
*/
|
|
128
|
+
repeat?: number | readonly [number, number];
|
|
129
|
+
roughness?: number;
|
|
130
|
+
metalness?: number;
|
|
131
|
+
}
|
|
132
|
+
/** What a building part kind is painted with: a hex colour, or a colour plus tiled PBR maps. */
|
|
133
|
+
export type BuildingSurface = string | BuildingSurfaceMaterial;
|
|
134
|
+
export type BuildingPalette = Record<BuildingPartKind, BuildingSurface>;
|
|
107
135
|
export type BuildingPaletteOverrides = Partial<BuildingPalette>;
|
|
136
|
+
/** The flat colour of a surface: the hex itself, or a material's `color`, else `fallback`. */
|
|
137
|
+
export declare function buildingSurfaceColor(surface: BuildingSurface | undefined, fallback?: string): string;
|
|
138
|
+
/** Normalises a surface to its material form so a renderer reads one shape. */
|
|
139
|
+
export declare function resolveBuildingSurface(surface: BuildingSurface): BuildingSurfaceMaterial;
|
|
108
140
|
export type BuildingStyle = "generic" | "capital" | "village" | "desert" | "industrial" | "coastal" | "neon" | "ruin" | "frontier" | "aerial";
|
|
109
141
|
export declare const DEFAULT_BUILDING_STYLE: BuildingStyle;
|
|
110
|
-
export declare const BUILDING_STYLE_PALETTES: Record<BuildingStyle,
|
|
142
|
+
export declare const BUILDING_STYLE_PALETTES: Record<BuildingStyle, Record<BuildingPartKind, string>>;
|
|
143
|
+
/**
|
|
144
|
+
* Per-style facade-tone family: the wall colours a district spreads across its buildings so
|
|
145
|
+
* neighbours differ in hue and value while the block stays one palette. Index 0 is the palette's
|
|
146
|
+
* own `wall`; the rest fan warmer/cooler and lighter/darker around it.
|
|
147
|
+
*/
|
|
148
|
+
export declare const BUILDING_STYLE_WALL_TONES: Record<BuildingStyle, readonly string[]>;
|
|
149
|
+
/**
|
|
150
|
+
* The facade-tone family for a style — the ramp {@link BUILDING_STYLE_WALL_TONES} keeps, falling
|
|
151
|
+
* back to the style's single palette wall when a style has no ramp.
|
|
152
|
+
* @internal
|
|
153
|
+
*/
|
|
154
|
+
export declare function resolveBuildingWallTones(style?: BuildingStyle): readonly string[];
|
|
111
155
|
/** @internal */
|
|
112
156
|
export declare function resolveBuildingPalette(style?: BuildingStyle, overrides?: BuildingPaletteOverrides): BuildingPalette;
|
|
113
157
|
/**
|
package/dist/world/buildings.js
CHANGED
|
@@ -38,6 +38,16 @@ export const DEFAULT_BUILDING_CONFIG = {
|
|
|
38
38
|
probabilities: DEFAULT_PROBABILITIES,
|
|
39
39
|
variants: DEFAULT_VARIANTS,
|
|
40
40
|
};
|
|
41
|
+
/** The flat colour of a surface: the hex itself, or a material's `color`, else `fallback`. */
|
|
42
|
+
export function buildingSurfaceColor(surface, fallback = "#808080") {
|
|
43
|
+
if (surface === undefined)
|
|
44
|
+
return fallback;
|
|
45
|
+
return typeof surface === "string" ? surface : (surface.color ?? fallback);
|
|
46
|
+
}
|
|
47
|
+
/** Normalises a surface to its material form so a renderer reads one shape. */
|
|
48
|
+
export function resolveBuildingSurface(surface) {
|
|
49
|
+
return typeof surface === "string" ? { color: surface } : surface;
|
|
50
|
+
}
|
|
41
51
|
export const DEFAULT_BUILDING_STYLE = "generic";
|
|
42
52
|
export const BUILDING_STYLE_PALETTES = {
|
|
43
53
|
generic: {
|
|
@@ -181,6 +191,34 @@ export const BUILDING_STYLE_PALETTES = {
|
|
|
181
191
|
corner: "#c3d0d7",
|
|
182
192
|
},
|
|
183
193
|
};
|
|
194
|
+
/**
|
|
195
|
+
* Per-style facade-tone family: the wall colours a district spreads across its buildings so
|
|
196
|
+
* neighbours differ in hue and value while the block stays one palette. Index 0 is the palette's
|
|
197
|
+
* own `wall`; the rest fan warmer/cooler and lighter/darker around it.
|
|
198
|
+
*/
|
|
199
|
+
export const BUILDING_STYLE_WALL_TONES = {
|
|
200
|
+
generic: ["#9c8f80", "#736a60", "#b7a894", "#8b8a80", "#c3ac90", "#635c54", "#a1978c"],
|
|
201
|
+
capital: ["#cfc5ad", "#ac9878", "#e6e0cf", "#a8a698", "#c8b48d", "#847b68", "#ded2b3"],
|
|
202
|
+
village: ["#b59e7a", "#9c8360", "#c9b895", "#8d7a5f", "#c2a173", "#a8977f", "#d6c3a1"],
|
|
203
|
+
desert: ["#c9a877", "#b08f61", "#ddc396", "#a4855c", "#d3b98d", "#93764f", "#c6b189"],
|
|
204
|
+
industrial: ["#7c838f", "#565d69", "#96a0ac", "#68737a", "#8a8173", "#4a5058", "#a0a6ac"],
|
|
205
|
+
coastal: ["#e8e2d2", "#cfd6d3", "#f1ece0", "#c9c0ac", "#dbe0dc", "#b7b3a3", "#e5d9c4"],
|
|
206
|
+
neon: ["#1b1b26", "#262233", "#141a24", "#2c2030", "#181f2b", "#221d1f", "#101018"],
|
|
207
|
+
ruin: ["#6b6156", "#57503f", "#7d7266", "#4f4a44", "#736450", "#8a8072", "#5d5b53"],
|
|
208
|
+
frontier: ["#7f5539", "#6a462d", "#96684a", "#5c4331", "#8a6540", "#a07a55", "#6f523c"],
|
|
209
|
+
aerial: ["#dfe7ec", "#c6d2da", "#eef3f6", "#b6c3cb", "#d3dde2", "#a7b7c0", "#e7eef1"],
|
|
210
|
+
};
|
|
211
|
+
/**
|
|
212
|
+
* The facade-tone family for a style — the ramp {@link BUILDING_STYLE_WALL_TONES} keeps, falling
|
|
213
|
+
* back to the style's single palette wall when a style has no ramp.
|
|
214
|
+
* @internal
|
|
215
|
+
*/
|
|
216
|
+
export function resolveBuildingWallTones(style = DEFAULT_BUILDING_STYLE) {
|
|
217
|
+
const tones = BUILDING_STYLE_WALL_TONES[style];
|
|
218
|
+
if (tones !== undefined && tones.length > 0)
|
|
219
|
+
return tones;
|
|
220
|
+
return [buildingSurfaceColor(resolveBuildingPalette(style).wall)];
|
|
221
|
+
}
|
|
184
222
|
/** @internal */
|
|
185
223
|
export function resolveBuildingPalette(style = DEFAULT_BUILDING_STYLE, overrides) {
|
|
186
224
|
const preset = BUILDING_STYLE_PALETTES[style];
|
|
@@ -413,9 +451,21 @@ function pushRoof(parts, config, width, depth) {
|
|
|
413
451
|
for (let index = 0; index < roofCells; index += 1) {
|
|
414
452
|
if (!chance(config.seed, `roof:prop:${index}`, config.probabilities.roofProp))
|
|
415
453
|
continue;
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
454
|
+
// Plant sized and nudged off its cell centre: props locked to the bay grid at one size read as
|
|
455
|
+
// a checkerboard of cubes rather than a roof of mechanical units.
|
|
456
|
+
const wide = hash01(config.seed, `roof:prop:${index}:w`);
|
|
457
|
+
const tall = hash01(config.seed, `roof:prop:${index}:h`);
|
|
458
|
+
const propW = config.bayWidth * (0.2 + wide * 0.4);
|
|
459
|
+
const propD = config.bayWidth * (0.18 + (1 - wide) * 0.36);
|
|
460
|
+
const propH = config.floorHeight * (0.14 + tall * 0.52);
|
|
461
|
+
const x = config.center[0] -
|
|
462
|
+
width / 2 +
|
|
463
|
+
config.bayWidth * ((index % config.baysWide) + 0.5 + (hash01(config.seed, `roof:prop:${index}:x`) - 0.5) * 0.5);
|
|
464
|
+
const z = config.center[1] -
|
|
465
|
+
depth / 2 +
|
|
466
|
+
config.bayWidth *
|
|
467
|
+
(Math.floor(index / config.baysWide) + 0.5 + (hash01(config.seed, `roof:prop:${index}:z`) - 0.5) * 0.5);
|
|
468
|
+
parts.push(part(config, "roofProp", "roof", [x, height + config.facadeDepth + propH / 2, z], [propW, propH, propD], `roof:prop:${index}`, { facade: "roof", level: config.floors, bay: index }, ["decor"]));
|
|
419
469
|
}
|
|
420
470
|
}
|
|
421
471
|
function pushCorners(parts, config, width, depth) {
|