@jgengine/core 0.6.0 → 0.8.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 +214 -2
- package/README.md +2 -0
- package/dist/ai/crowd.d.ts +36 -0
- package/dist/ai/crowd.js +213 -0
- package/dist/ai/jobBoard.d.ts +49 -0
- package/dist/ai/jobBoard.js +155 -0
- package/dist/ai/spawnDirector.d.ts +61 -0
- package/dist/ai/spawnDirector.js +162 -0
- package/dist/ai/threat.d.ts +25 -0
- package/dist/ai/threat.js +67 -0
- package/dist/anim/easing.d.ts +14 -0
- package/dist/anim/easing.js +55 -0
- package/dist/audio/audioFalloff.d.ts +38 -0
- package/dist/audio/audioFalloff.js +32 -0
- package/dist/behaviour/behaviour.d.ts +136 -0
- package/dist/behaviour/behaviour.js +388 -0
- package/dist/board/laneBoard.d.ts +63 -0
- package/dist/board/laneBoard.js +114 -0
- package/dist/board/timelineBoard.d.ts +36 -0
- package/dist/board/timelineBoard.js +70 -0
- package/dist/cards/cardPile.d.ts +64 -0
- package/dist/cards/cardPile.js +175 -0
- package/dist/cards/modifierPipeline.d.ts +33 -0
- package/dist/cards/modifierPipeline.js +48 -0
- package/dist/combat/abilityKit.d.ts +47 -0
- package/dist/combat/abilityKit.js +139 -0
- package/dist/combat/animationState.d.ts +47 -0
- package/dist/combat/animationState.js +101 -0
- package/dist/combat/attackTags.d.ts +14 -0
- package/dist/combat/attackTags.js +26 -0
- package/dist/combat/breakMeters.d.ts +32 -0
- package/dist/combat/breakMeters.js +51 -0
- package/dist/combat/comboString.d.ts +35 -0
- package/dist/combat/comboString.js +59 -0
- package/dist/combat/death.d.ts +8 -0
- package/dist/combat/death.js +5 -2
- package/dist/combat/defensiveWindow.d.ts +34 -0
- package/dist/combat/defensiveWindow.js +66 -0
- package/dist/combat/downed.d.ts +35 -0
- package/dist/combat/downed.js +104 -0
- package/dist/combat/effects.d.ts +12 -1
- package/dist/combat/effects.js +31 -19
- package/dist/combat/hitReaction.d.ts +23 -0
- package/dist/combat/hitReaction.js +20 -0
- package/dist/combat/projectiles.d.ts +42 -5
- package/dist/combat/projectiles.js +98 -12
- package/dist/combat/resistance.d.ts +13 -0
- package/dist/combat/resistance.js +46 -0
- package/dist/combat/telegraph.d.ts +31 -0
- package/dist/combat/telegraph.js +51 -0
- package/dist/commands/commandRegistry.d.ts +1 -1
- package/dist/commands/commandRegistry.js +2 -1
- package/dist/crafting/crop.d.ts +59 -0
- package/dist/crafting/crop.js +181 -0
- package/dist/crafting/production.d.ts +64 -0
- package/dist/crafting/production.js +130 -0
- package/dist/crafting/recipe.d.ts +62 -0
- package/dist/crafting/recipe.js +101 -0
- package/dist/data/dataSource.d.ts +26 -0
- package/dist/data/dataSource.js +93 -0
- package/dist/data/devProxy.d.ts +11 -0
- package/dist/data/devProxy.js +38 -0
- package/dist/data/fetchJson.d.ts +20 -0
- package/dist/data/fetchJson.js +43 -0
- package/dist/data/jsonDataSource.d.ts +4 -0
- package/dist/data/jsonDataSource.js +6 -0
- package/dist/devtools/devtools.d.ts +157 -0
- package/dist/devtools/devtools.js +484 -0
- package/dist/devtools/transformTunables.d.ts +14 -0
- package/dist/devtools/transformTunables.js +38 -0
- package/dist/economy/sharedWallet.d.ts +28 -0
- package/dist/economy/sharedWallet.js +62 -0
- package/dist/economy/techTree.d.ts +40 -0
- package/dist/economy/techTree.js +66 -0
- package/dist/game/chat.d.ts +66 -0
- package/dist/game/chat.js +165 -0
- package/dist/game/cosmetics.d.ts +25 -0
- package/dist/game/cosmetics.js +51 -0
- package/dist/game/defineGame.d.ts +10 -2
- package/dist/game/defineGame.js +2 -1
- package/dist/game/events.d.ts +87 -0
- package/dist/game/levelSequence.d.ts +44 -0
- package/dist/game/levelSequence.js +78 -0
- package/dist/game/lootFilter.d.ts +33 -0
- package/dist/game/lootFilter.js +48 -0
- package/dist/game/ping.d.ts +67 -0
- package/dist/game/ping.js +86 -0
- package/dist/game/playableGame.d.ts +516 -8
- package/dist/game/playableGame.js +6 -1
- package/dist/game/progression.d.ts +1 -0
- package/dist/game/progression.js +23 -2
- package/dist/game/race.d.ts +100 -0
- package/dist/game/race.js +236 -0
- package/dist/game/runDraft.d.ts +34 -0
- package/dist/game/runDraft.js +97 -0
- package/dist/game/social.d.ts +69 -0
- package/dist/game/social.js +138 -0
- package/dist/game/spawnPoints.d.ts +19 -0
- package/dist/game/spawnPoints.js +23 -0
- package/dist/game/worldItem.d.ts +95 -0
- package/dist/game/worldItem.js +138 -0
- package/dist/input/actionBindings.d.ts +10 -0
- package/dist/input/actionBindings.js +14 -0
- package/dist/input/axisInput.d.ts +43 -0
- package/dist/input/axisInput.js +70 -0
- package/dist/input/gestureSurface.d.ts +31 -0
- package/dist/input/gestureSurface.js +120 -0
- package/dist/input/pointer.d.ts +69 -0
- package/dist/input/pointer.js +65 -0
- package/dist/input/touchScheme.d.ts +83 -0
- package/dist/input/touchScheme.js +115 -0
- package/dist/interaction/contextMenu.d.ts +30 -0
- package/dist/interaction/contextMenu.js +22 -0
- package/dist/interaction/qte.d.ts +20 -0
- package/dist/interaction/qte.js +24 -0
- package/dist/interaction/skillCheck.d.ts +20 -0
- package/dist/interaction/skillCheck.js +26 -0
- package/dist/inventory/shapedGrid.d.ts +56 -0
- package/dist/inventory/shapedGrid.js +176 -0
- package/dist/inventory/storageTier.d.ts +52 -0
- package/dist/inventory/storageTier.js +77 -0
- package/dist/item/affix.d.ts +54 -0
- package/dist/item/affix.js +115 -0
- package/dist/item/durability.d.ts +51 -0
- package/dist/item/durability.js +78 -0
- package/dist/item/modularItem.d.ts +46 -0
- package/dist/item/modularItem.js +75 -0
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +71 -1
- package/dist/movement/dash.d.ts +37 -0
- package/dist/movement/dash.js +65 -0
- package/dist/movement/movementModel.d.ts +36 -3
- package/dist/movement/movementModel.js +81 -5
- package/dist/movement/voxelController.d.ts +61 -0
- package/dist/movement/voxelController.js +229 -0
- package/dist/multiplayer/chatContract.d.ts +42 -0
- package/dist/multiplayer/chatContract.js +57 -0
- package/dist/multiplayer/combatSnapshot.d.ts +45 -0
- package/dist/multiplayer/combatSnapshot.js +89 -0
- package/dist/multiplayer/identity.d.ts +13 -0
- package/dist/multiplayer/identity.js +20 -0
- package/dist/multiplayer/lagCompensation.d.ts +43 -0
- package/dist/multiplayer/lagCompensation.js +154 -0
- package/dist/multiplayer/matchmaking.d.ts +33 -0
- package/dist/multiplayer/matchmaking.js +60 -0
- package/dist/multiplayer/poseSyncGate.d.ts +1 -0
- package/dist/multiplayer/poseSyncGate.js +13 -1
- package/dist/multiplayer/presenceModel.d.ts +3 -0
- package/dist/multiplayer/presenceModel.js +15 -1
- package/dist/multiplayer/simultaneousCommit.d.ts +34 -0
- package/dist/multiplayer/simultaneousCommit.js +53 -0
- package/dist/multiplayer/voiceContract.d.ts +44 -0
- package/dist/multiplayer/voiceContract.js +108 -0
- package/dist/nav/navConstrain.d.ts +14 -0
- package/dist/nav/navConstrain.js +26 -0
- package/dist/nav/navFromEnvironment.d.ts +11 -0
- package/dist/nav/navFromEnvironment.js +16 -0
- package/dist/nav/navGrid.d.ts +56 -0
- package/dist/nav/navGrid.js +265 -0
- package/dist/nav/pathFollow.d.ts +31 -0
- package/dist/nav/pathFollow.js +69 -0
- package/dist/physics/ballisticSweep.d.ts +18 -0
- package/dist/physics/ballisticSweep.js +49 -0
- package/dist/physics/buoyancy.d.ts +49 -0
- package/dist/physics/buoyancy.js +112 -0
- package/dist/physics/carryable.d.ts +46 -0
- package/dist/physics/carryable.js +110 -0
- package/dist/physics/damageZones.d.ts +56 -0
- package/dist/physics/damageZones.js +107 -0
- package/dist/physics/forceVolume.d.ts +48 -0
- package/dist/physics/forceVolume.js +119 -0
- package/dist/physics/physicsWorld.d.ts +124 -2
- package/dist/physics/physicsWorld.js +493 -41
- package/dist/physics/ragdoll.d.ts +49 -0
- package/dist/physics/ragdoll.js +103 -0
- package/dist/physics/spatialGrid.d.ts +41 -0
- package/dist/physics/spatialGrid.js +143 -0
- package/dist/physics/structure.d.ts +83 -0
- package/dist/physics/structure.js +0 -0
- package/dist/physics/traversal.d.ts +75 -0
- package/dist/physics/traversal.js +138 -0
- package/dist/physics/vehicleBody.d.ts +99 -0
- package/dist/physics/vehicleBody.js +233 -0
- package/dist/puzzle/cellGrid.d.ts +26 -0
- package/dist/puzzle/cellGrid.js +110 -0
- package/dist/puzzle/fallingPiece.d.ts +24 -0
- package/dist/puzzle/fallingPiece.js +51 -0
- package/dist/random/nameGen.d.ts +18 -0
- package/dist/random/nameGen.js +35 -0
- package/dist/random/rng.d.ts +2 -0
- package/dist/random/rng.js +23 -0
- package/dist/runtime/adapter.d.ts +35 -0
- package/dist/runtime/adapter.js +37 -1
- package/dist/runtime/cameraDirector.d.ts +10 -0
- package/dist/runtime/cameraDirector.js +25 -0
- package/dist/runtime/gameContext.d.ts +107 -3
- package/dist/runtime/gameContext.js +253 -7
- package/dist/runtime/hostPersistence.d.ts +39 -0
- package/dist/runtime/hostPersistence.js +54 -0
- package/dist/runtime/inputSnapshot.d.ts +7 -0
- package/dist/runtime/inputSnapshot.js +12 -0
- package/dist/runtime/motionIntents.d.ts +25 -0
- package/dist/runtime/motionIntents.js +25 -0
- package/dist/runtime/persistenceScope.d.ts +32 -0
- package/dist/runtime/persistenceScope.js +64 -0
- package/dist/runtime/transport.d.ts +32 -0
- package/dist/scene/autoTarget.d.ts +15 -0
- package/dist/scene/autoTarget.js +54 -0
- package/dist/scene/behaviors.d.ts +13 -1
- package/dist/scene/behaviors.js +3 -0
- package/dist/scene/captureCheck.d.ts +7 -0
- package/dist/scene/captureCheck.js +11 -0
- package/dist/scene/entityStore.d.ts +9 -1
- package/dist/scene/entityStore.js +25 -2
- package/dist/scene/form.d.ts +40 -0
- package/dist/scene/form.js +56 -0
- package/dist/scene/mount.d.ts +62 -0
- package/dist/scene/mount.js +107 -0
- package/dist/scene/movementSpeed.d.ts +35 -0
- package/dist/scene/movementSpeed.js +20 -0
- package/dist/scene/objectQuery.d.ts +18 -0
- package/dist/scene/objectQuery.js +108 -0
- package/dist/scene/objectStore.d.ts +11 -0
- package/dist/scene/objectStore.js +109 -4
- package/dist/scene/paintLayer.d.ts +15 -0
- package/dist/scene/paintLayer.js +53 -0
- package/dist/scene/possession.d.ts +30 -0
- package/dist/scene/possession.js +51 -0
- package/dist/scene/roster.d.ts +25 -0
- package/dist/scene/roster.js +57 -0
- package/dist/scene/selection.d.ts +30 -0
- package/dist/scene/selection.js +38 -0
- package/dist/scene/spatial.d.ts +21 -0
- package/dist/scene/spatial.js +63 -1
- package/dist/scene/stationClaim.d.ts +43 -0
- package/dist/scene/stationClaim.js +109 -0
- package/dist/sensor/concealment.d.ts +21 -0
- package/dist/sensor/concealment.js +63 -0
- package/dist/sensor/freezeMonitor.d.ts +17 -0
- package/dist/sensor/freezeMonitor.js +35 -0
- package/dist/sensor/frustumSensor.d.ts +60 -0
- package/dist/sensor/frustumSensor.js +104 -0
- package/dist/sensor/hiddenStateProbe.d.ts +31 -0
- package/dist/sensor/hiddenStateProbe.js +40 -0
- package/dist/sensor/recordingBuffer.d.ts +29 -0
- package/dist/sensor/recordingBuffer.js +61 -0
- package/dist/sensor/revealQuery.d.ts +22 -0
- package/dist/sensor/revealQuery.js +36 -0
- package/dist/session/contestedChannel.d.ts +36 -0
- package/dist/session/contestedChannel.js +100 -0
- package/dist/session/extraction.d.ts +54 -0
- package/dist/session/extraction.js +108 -0
- package/dist/session/ring.d.ts +36 -0
- package/dist/session/ring.js +88 -0
- package/dist/session/roles.d.ts +6 -0
- package/dist/session/roles.js +32 -0
- package/dist/session/roundState.d.ts +79 -0
- package/dist/session/roundState.js +158 -0
- package/dist/stats/accumulatorMeter.d.ts +31 -0
- package/dist/stats/accumulatorMeter.js +81 -0
- package/dist/stats/eventMeter.d.ts +34 -0
- package/dist/stats/eventMeter.js +76 -0
- package/dist/stats/rollCheck.d.ts +15 -0
- package/dist/stats/rollCheck.js +15 -0
- package/dist/survival/decayMeter.d.ts +59 -0
- package/dist/survival/decayMeter.js +95 -0
- package/dist/survival/moodle.d.ts +48 -0
- package/dist/survival/moodle.js +91 -0
- package/dist/survival/regionHealth.d.ts +72 -0
- package/dist/survival/regionHealth.js +129 -0
- package/dist/tactics/fallingGrid.d.ts +62 -0
- package/dist/tactics/fallingGrid.js +158 -0
- package/dist/tactics/predictiveQuery.d.ts +34 -0
- package/dist/tactics/predictiveQuery.js +44 -0
- package/dist/tactics/snapshot.d.ts +18 -0
- package/dist/tactics/snapshot.js +65 -0
- package/dist/tactics/surface.d.ts +52 -0
- package/dist/tactics/surface.js +135 -0
- package/dist/tactics/tacticalGrid.d.ts +68 -0
- package/dist/tactics/tacticalGrid.js +261 -0
- package/dist/time/beatClock.d.ts +40 -0
- package/dist/time/beatClock.js +73 -0
- package/dist/time/simClock.d.ts +25 -1
- package/dist/time/simClock.js +28 -3
- package/dist/turn/commit.d.ts +27 -0
- package/dist/turn/commit.js +72 -0
- package/dist/turn/intent.d.ts +14 -0
- package/dist/turn/intent.js +29 -0
- package/dist/turn/turnLoop.d.ts +60 -0
- package/dist/turn/turnLoop.js +223 -0
- package/dist/world/buildPermissions.d.ts +47 -0
- package/dist/world/buildPermissions.js +134 -0
- package/dist/world/carve.d.ts +105 -0
- package/dist/world/carve.js +175 -0
- package/dist/world/connectors.d.ts +40 -0
- package/dist/world/connectors.js +67 -0
- package/dist/world/envField.d.ts +71 -0
- package/dist/world/envField.js +96 -0
- package/dist/world/environmentSummary.d.ts +53 -0
- package/dist/world/environmentSummary.js +129 -0
- package/dist/world/features.d.ts +87 -6
- package/dist/world/features.js +52 -2
- package/dist/world/fog.d.ts +40 -0
- package/dist/world/fog.js +137 -0
- package/dist/world/gridInstances.d.ts +13 -0
- package/dist/world/gridInstances.js +22 -0
- package/dist/world/markers.d.ts +52 -0
- package/dist/world/markers.js +96 -0
- package/dist/world/minimap.d.ts +42 -0
- package/dist/world/minimap.js +72 -0
- package/dist/world/placedStructureStore.d.ts +39 -0
- package/dist/world/placedStructureStore.js +95 -0
- package/dist/world/placementController.d.ts +69 -0
- package/dist/world/placementController.js +153 -0
- package/dist/world/realm.d.ts +61 -0
- package/dist/world/realm.js +66 -0
- package/dist/world/support.d.ts +27 -0
- package/dist/world/support.js +75 -0
- package/dist/world/terraform.d.ts +54 -0
- package/dist/world/terraform.js +186 -0
- package/dist/world/terrain.d.ts +23 -1
- package/dist/world/terrain.js +64 -1
- package/dist/world/voxelField.d.ts +54 -0
- package/dist/world/voxelField.js +241 -0
- package/dist/world/walls.d.ts +61 -0
- package/dist/world/walls.js +221 -0
- package/dist/world/weather.d.ts +92 -0
- package/dist/world/weather.js +169 -0
- package/llms.txt +4102 -0
- package/package.json +3 -2
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { createEnvironmentField } from "./envField.js";
|
|
2
|
+
export const DEFAULT_REALM_ENVIRONMENT = {
|
|
3
|
+
baseTemperature: 20,
|
|
4
|
+
nightDrop: 12,
|
|
5
|
+
altitudeLapse: 0,
|
|
6
|
+
dayLength: 24 * 3600,
|
|
7
|
+
rain: 0,
|
|
8
|
+
ambientFloor: 0.05,
|
|
9
|
+
};
|
|
10
|
+
function applySpawn(table, override) {
|
|
11
|
+
if (override === undefined)
|
|
12
|
+
return;
|
|
13
|
+
if (override.set !== undefined)
|
|
14
|
+
for (const [id, weight] of Object.entries(override.set))
|
|
15
|
+
table[id] = weight;
|
|
16
|
+
if (override.add !== undefined)
|
|
17
|
+
for (const [id, weight] of Object.entries(override.add))
|
|
18
|
+
table[id] = (table[id] ?? 0) + weight;
|
|
19
|
+
if (override.scale !== undefined)
|
|
20
|
+
for (const [id, factor] of Object.entries(override.scale))
|
|
21
|
+
table[id] = (table[id] ?? 0) * factor;
|
|
22
|
+
if (override.remove !== undefined)
|
|
23
|
+
for (const id of override.remove)
|
|
24
|
+
delete table[id];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Assemble a played realm instance at runtime from a deck of modifier cards — the
|
|
28
|
+
* Nightingale "realm card" model. A major card is the biome base; minor cards layer
|
|
29
|
+
* weather, day length, and spawn edits. The result recomposes both the environment
|
|
30
|
+
* (into a sampleable field via `environmentField()`) and the spawn table, and it depends
|
|
31
|
+
* on the weather hooks in this group (#92) to turn its `weather` into gameplay modifiers.
|
|
32
|
+
* Cards apply in array order; sort your deck (majors first) before composing.
|
|
33
|
+
*/
|
|
34
|
+
export function composeRealm(base, cards) {
|
|
35
|
+
const params = { ...DEFAULT_REALM_ENVIRONMENT, ...base.environment };
|
|
36
|
+
let weather = base.weather ?? { kind: "clear", intensity: 0 };
|
|
37
|
+
const spawnTable = { ...base.spawn };
|
|
38
|
+
const aesthetics = [];
|
|
39
|
+
for (const card of cards) {
|
|
40
|
+
if (card.environment !== undefined)
|
|
41
|
+
Object.assign(params, card.environment);
|
|
42
|
+
if (card.weather !== undefined)
|
|
43
|
+
weather = { ...weather, ...card.weather };
|
|
44
|
+
applySpawn(spawnTable, card.spawn);
|
|
45
|
+
if (card.aesthetic !== undefined)
|
|
46
|
+
aesthetics.push(card.aesthetic);
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
params,
|
|
50
|
+
weather,
|
|
51
|
+
spawnTable,
|
|
52
|
+
aesthetics,
|
|
53
|
+
cards,
|
|
54
|
+
environmentField(extra) {
|
|
55
|
+
return createEnvironmentField({
|
|
56
|
+
...extra,
|
|
57
|
+
baseTemperature: params.baseTemperature,
|
|
58
|
+
nightDrop: params.nightDrop,
|
|
59
|
+
altitudeLapse: params.altitudeLapse,
|
|
60
|
+
dayLength: params.dayLength,
|
|
61
|
+
rain: weather.kind === "clear" ? 0 : params.rain,
|
|
62
|
+
ambientFloor: params.ambientFloor,
|
|
63
|
+
});
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { AddBodyOptions } from "../physics/physicsWorld.js";
|
|
2
|
+
import type { ConnectorVec3 } from "./connectors.js";
|
|
3
|
+
export interface SupportPiece {
|
|
4
|
+
id: string;
|
|
5
|
+
grounded?: boolean;
|
|
6
|
+
position?: ConnectorVec3;
|
|
7
|
+
halfExtents?: ConnectorVec3;
|
|
8
|
+
mass?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface SupportLink {
|
|
11
|
+
a: string;
|
|
12
|
+
b: string;
|
|
13
|
+
}
|
|
14
|
+
export interface SupportConfig {
|
|
15
|
+
maxDistance?: number;
|
|
16
|
+
}
|
|
17
|
+
export interface SupportResult {
|
|
18
|
+
supported: readonly string[];
|
|
19
|
+
unsupported: readonly string[];
|
|
20
|
+
distance: Readonly<Record<string, number>>;
|
|
21
|
+
}
|
|
22
|
+
export declare function solveSupport(pieces: readonly SupportPiece[], links: readonly SupportLink[], config?: SupportConfig): SupportResult;
|
|
23
|
+
export interface DebrisOptions {
|
|
24
|
+
scatter?: number;
|
|
25
|
+
seed?: number;
|
|
26
|
+
}
|
|
27
|
+
export declare function toDebrisBodies(pieces: readonly SupportPiece[], collapsedIds: readonly string[], options?: DebrisOptions): AddBodyOptions[];
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
export function solveSupport(pieces, links, config = {}) {
|
|
2
|
+
const maxDistance = config.maxDistance ?? Infinity;
|
|
3
|
+
const ids = new Set(pieces.map((piece) => piece.id));
|
|
4
|
+
const adjacency = new Map();
|
|
5
|
+
for (const id of ids)
|
|
6
|
+
adjacency.set(id, []);
|
|
7
|
+
for (const link of links) {
|
|
8
|
+
if (!ids.has(link.a) || !ids.has(link.b))
|
|
9
|
+
continue;
|
|
10
|
+
adjacency.get(link.a).push(link.b);
|
|
11
|
+
adjacency.get(link.b).push(link.a);
|
|
12
|
+
}
|
|
13
|
+
const distance = {};
|
|
14
|
+
const queue = [];
|
|
15
|
+
for (const piece of pieces) {
|
|
16
|
+
if (piece.grounded === true) {
|
|
17
|
+
distance[piece.id] = 0;
|
|
18
|
+
queue.push(piece.id);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
let head = 0;
|
|
22
|
+
while (head < queue.length) {
|
|
23
|
+
const current = queue[head];
|
|
24
|
+
head += 1;
|
|
25
|
+
const currentDistance = distance[current];
|
|
26
|
+
if (currentDistance >= maxDistance)
|
|
27
|
+
continue;
|
|
28
|
+
for (const neighbor of adjacency.get(current) ?? []) {
|
|
29
|
+
const next = currentDistance + 1;
|
|
30
|
+
if (distance[neighbor] === undefined || next < distance[neighbor]) {
|
|
31
|
+
distance[neighbor] = next;
|
|
32
|
+
queue.push(neighbor);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const supported = [];
|
|
37
|
+
const unsupported = [];
|
|
38
|
+
for (const piece of pieces) {
|
|
39
|
+
const d = distance[piece.id];
|
|
40
|
+
if (d !== undefined && d <= maxDistance)
|
|
41
|
+
supported.push(piece.id);
|
|
42
|
+
else
|
|
43
|
+
unsupported.push(piece.id);
|
|
44
|
+
}
|
|
45
|
+
return { supported, unsupported, distance };
|
|
46
|
+
}
|
|
47
|
+
function scatterHash(seed, index) {
|
|
48
|
+
let h = (seed ^ Math.imul(index + 1, 0x9e3779b1)) >>> 0;
|
|
49
|
+
h = Math.imul(h ^ (h >>> 15), 0x85ebca6b);
|
|
50
|
+
h ^= h >>> 13;
|
|
51
|
+
return ((h >>> 0) / 4294967295) * 2 - 1;
|
|
52
|
+
}
|
|
53
|
+
export function toDebrisBodies(pieces, collapsedIds, options = {}) {
|
|
54
|
+
const scatter = options.scatter ?? 1.5;
|
|
55
|
+
const seed = options.seed ?? 1;
|
|
56
|
+
const collapse = new Set(collapsedIds);
|
|
57
|
+
const bodies = [];
|
|
58
|
+
let index = 0;
|
|
59
|
+
for (const piece of pieces) {
|
|
60
|
+
if (!collapse.has(piece.id))
|
|
61
|
+
continue;
|
|
62
|
+
const position = piece.position ?? [0, 0, 0];
|
|
63
|
+
const halfExtents = piece.halfExtents ?? [0.5, 0.5, 0.5];
|
|
64
|
+
const vx = scatterHash(seed, index) * scatter;
|
|
65
|
+
const vz = scatterHash(seed, index + 977) * scatter;
|
|
66
|
+
bodies.push({
|
|
67
|
+
position,
|
|
68
|
+
halfExtents,
|
|
69
|
+
velocity: [vx, 0, vz],
|
|
70
|
+
...(piece.mass === undefined ? {} : { mass: piece.mass }),
|
|
71
|
+
});
|
|
72
|
+
index += 1;
|
|
73
|
+
}
|
|
74
|
+
return bodies;
|
|
75
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { Aabb, Vec2 } from "./geometry.js";
|
|
2
|
+
import { type TerrainField } from "./terrain.js";
|
|
3
|
+
export type TerraformMode = "raise" | "lower" | "flatten" | "paint";
|
|
4
|
+
export type TerraformFalloff = "smooth" | "linear" | "none";
|
|
5
|
+
export interface TerraformEdit {
|
|
6
|
+
mode: TerraformMode;
|
|
7
|
+
center: Vec2;
|
|
8
|
+
radius: number;
|
|
9
|
+
strength?: number;
|
|
10
|
+
target?: number;
|
|
11
|
+
surface?: string;
|
|
12
|
+
falloff?: TerraformFalloff;
|
|
13
|
+
}
|
|
14
|
+
export interface EditableTerrainConfig {
|
|
15
|
+
bounds: Aabb;
|
|
16
|
+
base?: TerrainField;
|
|
17
|
+
cellSize?: number;
|
|
18
|
+
}
|
|
19
|
+
export interface TerraformSnapshot {
|
|
20
|
+
bounds: Aabb;
|
|
21
|
+
cellSize: number;
|
|
22
|
+
cols: number;
|
|
23
|
+
rows: number;
|
|
24
|
+
offsets: readonly number[];
|
|
25
|
+
surfaces: readonly (string | null)[];
|
|
26
|
+
}
|
|
27
|
+
export interface EditableTerrain extends TerrainField {
|
|
28
|
+
apply(edit: TerraformEdit): number;
|
|
29
|
+
heightOffsetAt(x: number, z: number): number;
|
|
30
|
+
surfaceAt(x: number, z: number): string | null;
|
|
31
|
+
reset(): void;
|
|
32
|
+
snapshot(): TerraformSnapshot;
|
|
33
|
+
restore(snapshot: TerraformSnapshot): void;
|
|
34
|
+
}
|
|
35
|
+
export declare function brushWeight(distance: number, radius: number, falloff: TerraformFalloff): number;
|
|
36
|
+
export declare function createEditableTerrain(config: EditableTerrainConfig): EditableTerrain;
|
|
37
|
+
export interface TerraformBrushConfig {
|
|
38
|
+
radius?: number;
|
|
39
|
+
strength?: number;
|
|
40
|
+
falloff?: TerraformFalloff;
|
|
41
|
+
surface?: string;
|
|
42
|
+
}
|
|
43
|
+
export interface TerraformBrush {
|
|
44
|
+
raise(center: Vec2): number;
|
|
45
|
+
lower(center: Vec2): number;
|
|
46
|
+
flatten(center: Vec2, target?: number): number;
|
|
47
|
+
paint(center: Vec2, surface?: string): number;
|
|
48
|
+
setRadius(radius: number): void;
|
|
49
|
+
setStrength(strength: number): void;
|
|
50
|
+
config(): Required<Omit<TerraformBrushConfig, "surface">> & {
|
|
51
|
+
surface: string | undefined;
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export declare function createTerraformBrush(terrain: Pick<EditableTerrain, "apply">, config?: TerraformBrushConfig): TerraformBrush;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { FLAT_FIELD, withNormal } from "./terrain.js";
|
|
2
|
+
export function brushWeight(distance, radius, falloff) {
|
|
3
|
+
if (radius <= 0)
|
|
4
|
+
return distance <= 0 ? 1 : 0;
|
|
5
|
+
const t = distance / radius;
|
|
6
|
+
if (t >= 1)
|
|
7
|
+
return 0;
|
|
8
|
+
if (falloff === "none")
|
|
9
|
+
return 1;
|
|
10
|
+
if (falloff === "linear")
|
|
11
|
+
return 1 - t;
|
|
12
|
+
const s = 1 - t;
|
|
13
|
+
return s * s * (3 - 2 * s);
|
|
14
|
+
}
|
|
15
|
+
function clampIndex(value, max) {
|
|
16
|
+
return value < 0 ? 0 : value > max ? max : value;
|
|
17
|
+
}
|
|
18
|
+
export function createEditableTerrain(config) {
|
|
19
|
+
const base = config.base ?? FLAT_FIELD;
|
|
20
|
+
const bounds = config.bounds;
|
|
21
|
+
const cellSize = config.cellSize && config.cellSize > 0 ? config.cellSize : 1;
|
|
22
|
+
const spanX = Math.max(bounds.maxX - bounds.minX, cellSize);
|
|
23
|
+
const spanZ = Math.max(bounds.maxZ - bounds.minZ, cellSize);
|
|
24
|
+
const cols = Math.max(1, Math.round(spanX / cellSize));
|
|
25
|
+
const rows = Math.max(1, Math.round(spanZ / cellSize));
|
|
26
|
+
const vertsX = cols + 1;
|
|
27
|
+
const vertsZ = rows + 1;
|
|
28
|
+
const offsets = new Float32Array(vertsX * vertsZ);
|
|
29
|
+
const surfaces = new Array(cols * rows).fill(null);
|
|
30
|
+
function gridX(x) {
|
|
31
|
+
return ((x - bounds.minX) / spanX) * cols;
|
|
32
|
+
}
|
|
33
|
+
function gridZ(z) {
|
|
34
|
+
return ((z - bounds.minZ) / spanZ) * rows;
|
|
35
|
+
}
|
|
36
|
+
function heightOffsetAt(x, z) {
|
|
37
|
+
const fx = gridX(x);
|
|
38
|
+
const fz = gridZ(z);
|
|
39
|
+
const x0 = clampIndex(Math.floor(fx), cols);
|
|
40
|
+
const z0 = clampIndex(Math.floor(fz), rows);
|
|
41
|
+
const x1 = clampIndex(x0 + 1, cols);
|
|
42
|
+
const z1 = clampIndex(z0 + 1, rows);
|
|
43
|
+
const tx = fx - x0 < 0 ? 0 : fx - x0 > 1 ? 1 : fx - x0;
|
|
44
|
+
const tz = fz - z0 < 0 ? 0 : fz - z0 > 1 ? 1 : fz - z0;
|
|
45
|
+
const v00 = offsets[z0 * vertsX + x0];
|
|
46
|
+
const v10 = offsets[z0 * vertsX + x1];
|
|
47
|
+
const v01 = offsets[z1 * vertsX + x0];
|
|
48
|
+
const v11 = offsets[z1 * vertsX + x1];
|
|
49
|
+
const top = v00 + (v10 - v00) * tx;
|
|
50
|
+
const bottom = v01 + (v11 - v01) * tx;
|
|
51
|
+
return top + (bottom - top) * tz;
|
|
52
|
+
}
|
|
53
|
+
const sampleHeight = (x, z) => base.sampleHeight(x, z) + heightOffsetAt(x, z);
|
|
54
|
+
const sampleNormal = withNormal(sampleHeight);
|
|
55
|
+
function editHeight(edit) {
|
|
56
|
+
const falloff = edit.falloff ?? "smooth";
|
|
57
|
+
const strength = edit.strength ?? 1;
|
|
58
|
+
const target = edit.mode === "flatten" ? edit.target ?? sampleHeight(edit.center[0], edit.center[1]) : 0;
|
|
59
|
+
const rCells = Math.ceil(edit.radius / cellSize) + 1;
|
|
60
|
+
const cx = gridX(edit.center[0]);
|
|
61
|
+
const cz = gridZ(edit.center[1]);
|
|
62
|
+
let changed = 0;
|
|
63
|
+
for (let iz = -rCells; iz <= rCells; iz += 1) {
|
|
64
|
+
const gz = Math.round(cz) + iz;
|
|
65
|
+
if (gz < 0 || gz >= vertsZ)
|
|
66
|
+
continue;
|
|
67
|
+
for (let ix = -rCells; ix <= rCells; ix += 1) {
|
|
68
|
+
const gx = Math.round(cx) + ix;
|
|
69
|
+
if (gx < 0 || gx >= vertsX)
|
|
70
|
+
continue;
|
|
71
|
+
const wx = bounds.minX + (gx / cols) * spanX;
|
|
72
|
+
const wz = bounds.minZ + (gz / rows) * spanZ;
|
|
73
|
+
const dist = Math.hypot(wx - edit.center[0], wz - edit.center[1]);
|
|
74
|
+
const weight = brushWeight(dist, edit.radius, falloff);
|
|
75
|
+
if (weight <= 0)
|
|
76
|
+
continue;
|
|
77
|
+
const index = gz * vertsX + gx;
|
|
78
|
+
if (edit.mode === "raise")
|
|
79
|
+
offsets[index] += strength * weight;
|
|
80
|
+
else if (edit.mode === "lower")
|
|
81
|
+
offsets[index] -= strength * weight;
|
|
82
|
+
else {
|
|
83
|
+
const current = base.sampleHeight(wx, wz) + offsets[index];
|
|
84
|
+
offsets[index] += (target - current) * weight * strength;
|
|
85
|
+
}
|
|
86
|
+
changed += 1;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return changed;
|
|
90
|
+
}
|
|
91
|
+
function paintSurface(edit) {
|
|
92
|
+
const surface = edit.surface ?? null;
|
|
93
|
+
const rCells = Math.ceil(edit.radius / cellSize) + 1;
|
|
94
|
+
const cx = gridX(edit.center[0]);
|
|
95
|
+
const cz = gridZ(edit.center[1]);
|
|
96
|
+
let painted = 0;
|
|
97
|
+
for (let iz = -rCells; iz <= rCells; iz += 1) {
|
|
98
|
+
const gz = Math.floor(cz) + iz;
|
|
99
|
+
if (gz < 0 || gz >= rows)
|
|
100
|
+
continue;
|
|
101
|
+
for (let ix = -rCells; ix <= rCells; ix += 1) {
|
|
102
|
+
const gx = Math.floor(cx) + ix;
|
|
103
|
+
if (gx < 0 || gx >= cols)
|
|
104
|
+
continue;
|
|
105
|
+
const wx = bounds.minX + ((gx + 0.5) / cols) * spanX;
|
|
106
|
+
const wz = bounds.minZ + ((gz + 0.5) / rows) * spanZ;
|
|
107
|
+
if (Math.hypot(wx - edit.center[0], wz - edit.center[1]) > edit.radius)
|
|
108
|
+
continue;
|
|
109
|
+
surfaces[gz * cols + gx] = surface;
|
|
110
|
+
painted += 1;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return painted;
|
|
114
|
+
}
|
|
115
|
+
function surfaceAt(x, z) {
|
|
116
|
+
const gx = Math.floor(gridX(x));
|
|
117
|
+
const gz = Math.floor(gridZ(z));
|
|
118
|
+
if (gx < 0 || gx >= cols || gz < 0 || gz >= rows)
|
|
119
|
+
return null;
|
|
120
|
+
return surfaces[gz * cols + gx] ?? null;
|
|
121
|
+
}
|
|
122
|
+
const field = {
|
|
123
|
+
sampleHeight,
|
|
124
|
+
sampleNormal,
|
|
125
|
+
...(base.bounds === undefined ? {} : { bounds: base.bounds }),
|
|
126
|
+
...(base.waterLevel === undefined ? {} : { waterLevel: base.waterLevel }),
|
|
127
|
+
apply(edit) {
|
|
128
|
+
return edit.mode === "paint" ? paintSurface(edit) : editHeight(edit);
|
|
129
|
+
},
|
|
130
|
+
heightOffsetAt,
|
|
131
|
+
surfaceAt,
|
|
132
|
+
reset() {
|
|
133
|
+
offsets.fill(0);
|
|
134
|
+
surfaces.fill(null);
|
|
135
|
+
},
|
|
136
|
+
snapshot() {
|
|
137
|
+
return {
|
|
138
|
+
bounds: { ...bounds },
|
|
139
|
+
cellSize,
|
|
140
|
+
cols,
|
|
141
|
+
rows,
|
|
142
|
+
offsets: Array.from(offsets),
|
|
143
|
+
surfaces: surfaces.slice(),
|
|
144
|
+
};
|
|
145
|
+
},
|
|
146
|
+
restore(snapshot) {
|
|
147
|
+
const count = Math.min(offsets.length, snapshot.offsets.length);
|
|
148
|
+
for (let index = 0; index < count; index += 1)
|
|
149
|
+
offsets[index] = snapshot.offsets[index];
|
|
150
|
+
const surfaceCount = Math.min(surfaces.length, snapshot.surfaces.length);
|
|
151
|
+
for (let index = 0; index < surfaceCount; index += 1)
|
|
152
|
+
surfaces[index] = snapshot.surfaces[index] ?? null;
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
return field;
|
|
156
|
+
}
|
|
157
|
+
export function createTerraformBrush(terrain, config = {}) {
|
|
158
|
+
let radius = config.radius ?? 3;
|
|
159
|
+
let strength = config.strength ?? 0.5;
|
|
160
|
+
const falloff = config.falloff ?? "smooth";
|
|
161
|
+
const surface = config.surface;
|
|
162
|
+
return {
|
|
163
|
+
raise(center) {
|
|
164
|
+
return terrain.apply({ mode: "raise", center, radius, strength, falloff });
|
|
165
|
+
},
|
|
166
|
+
lower(center) {
|
|
167
|
+
return terrain.apply({ mode: "lower", center, radius, strength, falloff });
|
|
168
|
+
},
|
|
169
|
+
flatten(center, target) {
|
|
170
|
+
return terrain.apply({ mode: "flatten", center, radius, strength, falloff, ...(target === undefined ? {} : { target }) });
|
|
171
|
+
},
|
|
172
|
+
paint(center, paintSurface) {
|
|
173
|
+
const chosen = paintSurface ?? surface;
|
|
174
|
+
return terrain.apply({ mode: "paint", center, radius, ...(chosen === undefined ? {} : { surface: chosen }) });
|
|
175
|
+
},
|
|
176
|
+
setRadius(next) {
|
|
177
|
+
radius = next;
|
|
178
|
+
},
|
|
179
|
+
setStrength(next) {
|
|
180
|
+
strength = next;
|
|
181
|
+
},
|
|
182
|
+
config() {
|
|
183
|
+
return { radius, strength, falloff, surface };
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
package/dist/world/terrain.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { TerrainEnvironmentDescriptor, WorldBounds } from "./features.js";
|
|
1
|
+
import type { TerrainEnvironmentConfig, TerrainEnvironmentDescriptor, WorldBounds, WorldFeature } from "./features.js";
|
|
2
2
|
export type TerrainNormal = readonly [number, number, number];
|
|
3
3
|
export interface TerrainField {
|
|
4
4
|
sampleHeight(x: number, z: number): number;
|
|
@@ -46,7 +46,29 @@ export interface ArenaFieldConfig {
|
|
|
46
46
|
}
|
|
47
47
|
export declare const ARENA_WATER_LEVEL = -0.9;
|
|
48
48
|
export declare function arenaField(config?: ArenaFieldConfig): TerrainField;
|
|
49
|
+
export declare function groundFieldFor(world?: WorldFeature): TerrainField;
|
|
50
|
+
export interface TerrainPalette {
|
|
51
|
+
low: string;
|
|
52
|
+
high: string;
|
|
53
|
+
waterline: string;
|
|
54
|
+
}
|
|
55
|
+
export declare const DEFAULT_TERRAIN_MATERIAL = "grass";
|
|
56
|
+
export declare const TERRAIN_MATERIAL_PALETTES: Record<string, TerrainPalette>;
|
|
57
|
+
export declare function resolveTerrainPalette(descriptor?: Pick<TerrainEnvironmentConfig, "material" | "colors">): TerrainPalette;
|
|
49
58
|
export declare function resolveTerrainField(descriptor?: TerrainEnvironmentDescriptor): TerrainField;
|
|
59
|
+
/** Returns `position` with `y` replaced by the field's ground height (plus `offset`) at its `x`/`z`. */
|
|
60
|
+
export declare function snapToGround(field: TerrainField, position: readonly [number, number, number], offset?: number): [number, number, number];
|
|
61
|
+
export interface GroundSnapTarget {
|
|
62
|
+
position: readonly [number, number, number];
|
|
63
|
+
}
|
|
64
|
+
export interface GroundSnapEntityStore {
|
|
65
|
+
get(id: string): GroundSnapTarget | null;
|
|
66
|
+
setPose(id: string, pose: {
|
|
67
|
+
position?: readonly [number, number, number];
|
|
68
|
+
}): boolean;
|
|
69
|
+
}
|
|
70
|
+
/** Ground-snaps an already-spawned entity in place; returns false when `id` is unknown. */
|
|
71
|
+
export declare function snapEntityToGround(entities: GroundSnapEntityStore, id: string, field: TerrainField, offset?: number): boolean;
|
|
50
72
|
export declare const DEFAULT_MAX_WALK_SLOPE = 0.6;
|
|
51
73
|
export declare function resolveGroundStep(field: TerrainField, x: number, z: number, stepX: number, stepZ: number, maxSlope?: number): {
|
|
52
74
|
stepX: number;
|
package/dist/world/terrain.js
CHANGED
|
@@ -148,10 +148,55 @@ export function arenaField(config = {}) {
|
|
|
148
148
|
};
|
|
149
149
|
return fieldFromHeight(sampleHeight, { bounds: config.bounds, waterLevel: ARENA_WATER_LEVEL });
|
|
150
150
|
}
|
|
151
|
+
export function groundFieldFor(world) {
|
|
152
|
+
if (world !== undefined && world.kind === "environment")
|
|
153
|
+
return resolveTerrainField(world.terrain);
|
|
154
|
+
return flatField();
|
|
155
|
+
}
|
|
156
|
+
export const DEFAULT_TERRAIN_MATERIAL = "grass";
|
|
157
|
+
export const TERRAIN_MATERIAL_PALETTES = {
|
|
158
|
+
grass: { low: "#30402c", high: "#7f8b50", waterline: "#1d4c6e" },
|
|
159
|
+
sand: { low: "#9c8354", high: "#e0c98a", waterline: "#2f6f8f" },
|
|
160
|
+
snow: { low: "#c7d3dc", high: "#ffffff", waterline: "#3a6ea5" },
|
|
161
|
+
rock: { low: "#3a3a3d", high: "#8a8a8d", waterline: "#1d4c6e" },
|
|
162
|
+
ash: { low: "#2b2622", high: "#5c534a", waterline: "#3a3630" },
|
|
163
|
+
};
|
|
164
|
+
export function resolveTerrainPalette(descriptor = {}) {
|
|
165
|
+
const preset = TERRAIN_MATERIAL_PALETTES[descriptor.material ?? DEFAULT_TERRAIN_MATERIAL] ?? TERRAIN_MATERIAL_PALETTES[DEFAULT_TERRAIN_MATERIAL];
|
|
166
|
+
const colors = descriptor.colors ?? {};
|
|
167
|
+
return {
|
|
168
|
+
low: colors.low ?? preset.low,
|
|
169
|
+
high: colors.high ?? preset.high,
|
|
170
|
+
waterline: colors.waterline ?? preset.waterline,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
function withFlattenMasks(sampleHeight, masks) {
|
|
174
|
+
const resolved = masks.map((mask) => ({
|
|
175
|
+
center: mask.center,
|
|
176
|
+
radius: mask.radius,
|
|
177
|
+
target: mask.height ?? sampleHeight(mask.center[0], mask.center[1]),
|
|
178
|
+
falloff: mask.falloff ?? mask.radius * 0.5,
|
|
179
|
+
}));
|
|
180
|
+
return (x, z) => {
|
|
181
|
+
const base = sampleHeight(x, z);
|
|
182
|
+
let height = base;
|
|
183
|
+
for (const mask of resolved) {
|
|
184
|
+
const distance = Math.hypot(x - mask.center[0], z - mask.center[1]);
|
|
185
|
+
if (distance <= mask.radius) {
|
|
186
|
+
height = mask.target;
|
|
187
|
+
}
|
|
188
|
+
else if (distance <= mask.radius + mask.falloff) {
|
|
189
|
+
const t = smoothstep(mask.radius, mask.radius + mask.falloff, distance);
|
|
190
|
+
height = lerp(mask.target, base, t);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return height;
|
|
194
|
+
};
|
|
195
|
+
}
|
|
151
196
|
export function resolveTerrainField(descriptor) {
|
|
152
197
|
if (descriptor === undefined)
|
|
153
198
|
return flatField();
|
|
154
|
-
|
|
199
|
+
const noise = noiseField({
|
|
155
200
|
seed: descriptor.seed,
|
|
156
201
|
amplitude: descriptor.height,
|
|
157
202
|
frequency: descriptor.frequency,
|
|
@@ -161,6 +206,24 @@ export function resolveTerrainField(descriptor) {
|
|
|
161
206
|
waterLevel: descriptor.waterLevel,
|
|
162
207
|
bounds: descriptor.bounds,
|
|
163
208
|
});
|
|
209
|
+
if (descriptor.flatten === undefined || descriptor.flatten.length === 0)
|
|
210
|
+
return noise;
|
|
211
|
+
return fieldFromHeight(withFlattenMasks(noise.sampleHeight, descriptor.flatten), {
|
|
212
|
+
bounds: noise.bounds,
|
|
213
|
+
waterLevel: noise.waterLevel,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
/** Returns `position` with `y` replaced by the field's ground height (plus `offset`) at its `x`/`z`. */
|
|
217
|
+
export function snapToGround(field, position, offset = 0) {
|
|
218
|
+
const [x, , z] = position;
|
|
219
|
+
return [x, field.sampleHeight(x, z) + offset, z];
|
|
220
|
+
}
|
|
221
|
+
/** Ground-snaps an already-spawned entity in place; returns false when `id` is unknown. */
|
|
222
|
+
export function snapEntityToGround(entities, id, field, offset = 0) {
|
|
223
|
+
const entity = entities.get(id);
|
|
224
|
+
if (entity === null)
|
|
225
|
+
return false;
|
|
226
|
+
return entities.setPose(id, { position: snapToGround(field, entity.position, offset) });
|
|
164
227
|
}
|
|
165
228
|
export const DEFAULT_MAX_WALK_SLOPE = 0.6;
|
|
166
229
|
export function resolveGroundStep(field, x, z, stepX, stepZ, maxSlope = DEFAULT_MAX_WALK_SLOPE) {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export type VoxelCoord = readonly [number, number, number];
|
|
2
|
+
export type VoxelFace = "px" | "nx" | "py" | "ny" | "pz" | "nz";
|
|
3
|
+
export interface VoxelFieldConfig {
|
|
4
|
+
chunkSize?: number;
|
|
5
|
+
}
|
|
6
|
+
export interface VoxelCell<T extends string = string> {
|
|
7
|
+
x: number;
|
|
8
|
+
y: number;
|
|
9
|
+
z: number;
|
|
10
|
+
type: T;
|
|
11
|
+
}
|
|
12
|
+
export interface VoxelHit<T extends string = string> {
|
|
13
|
+
x: number;
|
|
14
|
+
y: number;
|
|
15
|
+
z: number;
|
|
16
|
+
type: T;
|
|
17
|
+
face: VoxelFace;
|
|
18
|
+
adjacent: VoxelCoord;
|
|
19
|
+
distance: number;
|
|
20
|
+
}
|
|
21
|
+
export interface VoxelBounds {
|
|
22
|
+
min: VoxelCoord;
|
|
23
|
+
max: VoxelCoord;
|
|
24
|
+
}
|
|
25
|
+
export interface VoxelFieldSummary<T extends string = string> {
|
|
26
|
+
blocks: number;
|
|
27
|
+
types: Record<T, number>;
|
|
28
|
+
bounds: VoxelBounds | null;
|
|
29
|
+
}
|
|
30
|
+
export interface VoxelField<T extends string = string> {
|
|
31
|
+
set(x: number, y: number, z: number, type: T): boolean;
|
|
32
|
+
remove(x: number, y: number, z: number): boolean;
|
|
33
|
+
get(x: number, y: number, z: number): T | null;
|
|
34
|
+
has(x: number, y: number, z: number): boolean;
|
|
35
|
+
fill(min: VoxelCoord, max: VoxelCoord, type: T): number;
|
|
36
|
+
clear(): void;
|
|
37
|
+
count(): number;
|
|
38
|
+
cells(): IterableIterator<VoxelCell<T>>;
|
|
39
|
+
bounds(): VoxelBounds | null;
|
|
40
|
+
neighbors(x: number, y: number, z: number): VoxelCell<T>[];
|
|
41
|
+
exposedFaces(x: number, y: number, z: number): readonly VoxelFace[];
|
|
42
|
+
raycast(origin: VoxelCoord, direction: VoxelCoord, maxDistance: number): VoxelHit<T> | null;
|
|
43
|
+
chunkOf(x: number, y: number, z: number): {
|
|
44
|
+
cx: number;
|
|
45
|
+
cy: number;
|
|
46
|
+
cz: number;
|
|
47
|
+
};
|
|
48
|
+
chunkVersion(cx: number, cy: number, cz: number): number;
|
|
49
|
+
subscribe(listener: () => void): () => void;
|
|
50
|
+
summary(): VoxelFieldSummary<T>;
|
|
51
|
+
}
|
|
52
|
+
export declare const VOXEL_FACES: readonly VoxelFace[];
|
|
53
|
+
export declare const VOXEL_FACE_NORMALS: Record<VoxelFace, VoxelCoord>;
|
|
54
|
+
export declare function createVoxelField<T extends string = string>(config?: VoxelFieldConfig): VoxelField<T>;
|