@jgengine/core 0.7.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 +47 -0
- package/README.md +2 -0
- package/dist/ai/spawnDirector.d.ts +5 -0
- package/dist/ai/spawnDirector.js +13 -1
- package/dist/anim/easing.d.ts +14 -0
- package/dist/anim/easing.js +55 -0
- package/dist/behaviour/behaviour.d.ts +136 -0
- package/dist/behaviour/behaviour.js +388 -0
- package/dist/cards/cardPile.d.ts +1 -0
- package/dist/cards/cardPile.js +12 -3
- package/dist/combat/abilityKit.d.ts +5 -0
- package/dist/combat/abilityKit.js +10 -0
- package/dist/combat/effects.d.ts +1 -1
- package/dist/combat/effects.js +9 -6
- package/dist/combat/projectiles.d.ts +42 -5
- package/dist/combat/projectiles.js +98 -12
- package/dist/combat/telegraph.d.ts +5 -1
- package/dist/combat/telegraph.js +14 -0
- package/dist/commands/commandRegistry.d.ts +1 -1
- package/dist/commands/commandRegistry.js +2 -1
- 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/game/chat.d.ts +66 -0
- package/dist/game/chat.js +165 -0
- package/dist/game/defineGame.d.ts +8 -2
- package/dist/game/defineGame.js +2 -1
- package/dist/game/events.d.ts +25 -0
- package/dist/game/levelSequence.d.ts +44 -0
- package/dist/game/levelSequence.js +78 -0
- package/dist/game/playableGame.d.ts +237 -6
- 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 +5 -0
- package/dist/game/race.js +20 -0
- package/dist/game/social.d.ts +42 -0
- package/dist/game/social.js +122 -0
- package/dist/game/spawnPoints.d.ts +19 -0
- package/dist/game/spawnPoints.js +23 -0
- package/dist/game/worldItem.d.ts +3 -0
- package/dist/game/worldItem.js +12 -6
- package/dist/input/actionBindings.d.ts +10 -0
- package/dist/input/actionBindings.js +14 -0
- package/dist/input/gestureSurface.d.ts +31 -0
- package/dist/input/gestureSurface.js +120 -0
- package/dist/input/pointer.d.ts +45 -0
- package/dist/input/pointer.js +48 -0
- package/dist/input/touchScheme.d.ts +83 -0
- package/dist/input/touchScheme.js +115 -0
- package/dist/item/affix.d.ts +1 -1
- package/dist/item/affix.js +1 -19
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +37 -1
- 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/identity.d.ts +13 -0
- package/dist/multiplayer/identity.js +20 -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/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 +10 -0
- package/dist/nav/navGrid.js +18 -1
- package/dist/nav/pathFollow.d.ts +4 -2
- package/dist/nav/pathFollow.js +5 -3
- package/dist/physics/ballisticSweep.d.ts +18 -0
- package/dist/physics/ballisticSweep.js +49 -0
- package/dist/physics/forceVolume.js +5 -5
- package/dist/physics/physicsWorld.d.ts +41 -2
- package/dist/physics/physicsWorld.js +232 -40
- package/dist/physics/spatialGrid.d.ts +1 -1
- package/dist/physics/spatialGrid.js +5 -0
- package/dist/physics/vehicleBody.d.ts +6 -0
- package/dist/physics/vehicleBody.js +16 -17
- 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 +44 -2
- package/dist/runtime/gameContext.js +100 -4
- package/dist/runtime/hostPersistence.d.ts +21 -0
- package/dist/runtime/hostPersistence.js +50 -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/transport.d.ts +32 -0
- package/dist/scene/entityStore.d.ts +8 -0
- package/dist/scene/entityStore.js +25 -2
- 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/spatial.d.ts +21 -0
- package/dist/scene/spatial.js +63 -1
- 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/session/roles.d.ts +6 -0
- package/dist/session/roles.js +32 -0
- package/dist/session/roundState.d.ts +41 -17
- package/dist/session/roundState.js +67 -38
- package/dist/tactics/fallingGrid.d.ts +62 -0
- package/dist/tactics/fallingGrid.js +158 -0
- package/dist/tactics/tacticalGrid.d.ts +11 -0
- package/dist/tactics/tacticalGrid.js +17 -0
- package/dist/time/simClock.d.ts +25 -1
- package/dist/time/simClock.js +28 -3
- package/dist/turn/intent.d.ts +14 -0
- package/dist/turn/intent.js +29 -0
- package/dist/turn/turnLoop.d.ts +2 -0
- package/dist/turn/turnLoop.js +8 -1
- 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/gridInstances.d.ts +13 -0
- package/dist/world/gridInstances.js +22 -0
- package/dist/world/placementController.d.ts +22 -1
- package/dist/world/placementController.js +56 -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/llms.txt +4102 -0
- package/package.json +3 -2
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>;
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
export const VOXEL_FACES = ["px", "nx", "py", "ny", "pz", "nz"];
|
|
2
|
+
export const VOXEL_FACE_NORMALS = {
|
|
3
|
+
px: [1, 0, 0],
|
|
4
|
+
nx: [-1, 0, 0],
|
|
5
|
+
py: [0, 1, 0],
|
|
6
|
+
ny: [0, -1, 0],
|
|
7
|
+
pz: [0, 0, 1],
|
|
8
|
+
nz: [0, 0, -1],
|
|
9
|
+
};
|
|
10
|
+
function cellKey(x, y, z) {
|
|
11
|
+
return `${x},${y},${z}`;
|
|
12
|
+
}
|
|
13
|
+
function entryFace(dx, dy, dz) {
|
|
14
|
+
const ax = Math.abs(dx);
|
|
15
|
+
const ay = Math.abs(dy);
|
|
16
|
+
const az = Math.abs(dz);
|
|
17
|
+
if (ax >= ay && ax >= az && ax > 0)
|
|
18
|
+
return dx > 0 ? "nx" : "px";
|
|
19
|
+
if (az >= ay && az > 0)
|
|
20
|
+
return dz > 0 ? "nz" : "pz";
|
|
21
|
+
return dy > 0 ? "ny" : "py";
|
|
22
|
+
}
|
|
23
|
+
export function createVoxelField(config) {
|
|
24
|
+
const chunkSize = config?.chunkSize ?? 16;
|
|
25
|
+
if (!Number.isInteger(chunkSize) || chunkSize <= 0) {
|
|
26
|
+
throw new Error("createVoxelField: chunkSize must be a positive integer");
|
|
27
|
+
}
|
|
28
|
+
const cells = new Map();
|
|
29
|
+
const chunkVersions = new Map();
|
|
30
|
+
const listeners = new Set();
|
|
31
|
+
function chunkOf(x, y, z) {
|
|
32
|
+
return {
|
|
33
|
+
cx: Math.floor(x / chunkSize),
|
|
34
|
+
cy: Math.floor(y / chunkSize),
|
|
35
|
+
cz: Math.floor(z / chunkSize),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function bumpChunk(x, y, z) {
|
|
39
|
+
const { cx, cy, cz } = chunkOf(x, y, z);
|
|
40
|
+
const key = cellKey(cx, cy, cz);
|
|
41
|
+
chunkVersions.set(key, (chunkVersions.get(key) ?? 0) + 1);
|
|
42
|
+
}
|
|
43
|
+
function notify() {
|
|
44
|
+
for (const listener of listeners)
|
|
45
|
+
listener();
|
|
46
|
+
}
|
|
47
|
+
function setCell(x, y, z, type) {
|
|
48
|
+
const key = cellKey(x, y, z);
|
|
49
|
+
const existing = cells.get(key);
|
|
50
|
+
if (existing !== undefined && existing.type === type)
|
|
51
|
+
return false;
|
|
52
|
+
cells.set(key, { x, y, z, type });
|
|
53
|
+
bumpChunk(x, y, z);
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
function getCell(x, y, z) {
|
|
57
|
+
return cells.get(cellKey(x, y, z));
|
|
58
|
+
}
|
|
59
|
+
function computeBounds() {
|
|
60
|
+
if (cells.size === 0)
|
|
61
|
+
return null;
|
|
62
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
63
|
+
let minY = Number.POSITIVE_INFINITY;
|
|
64
|
+
let minZ = Number.POSITIVE_INFINITY;
|
|
65
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
66
|
+
let maxY = Number.NEGATIVE_INFINITY;
|
|
67
|
+
let maxZ = Number.NEGATIVE_INFINITY;
|
|
68
|
+
for (const cell of cells.values()) {
|
|
69
|
+
if (cell.x < minX)
|
|
70
|
+
minX = cell.x;
|
|
71
|
+
if (cell.y < minY)
|
|
72
|
+
minY = cell.y;
|
|
73
|
+
if (cell.z < minZ)
|
|
74
|
+
minZ = cell.z;
|
|
75
|
+
if (cell.x > maxX)
|
|
76
|
+
maxX = cell.x;
|
|
77
|
+
if (cell.y > maxY)
|
|
78
|
+
maxY = cell.y;
|
|
79
|
+
if (cell.z > maxZ)
|
|
80
|
+
maxZ = cell.z;
|
|
81
|
+
}
|
|
82
|
+
return { min: [minX, minY, minZ], max: [maxX, maxY, maxZ] };
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
set(x, y, z, type) {
|
|
86
|
+
const changed = setCell(x, y, z, type);
|
|
87
|
+
if (changed)
|
|
88
|
+
notify();
|
|
89
|
+
return changed;
|
|
90
|
+
},
|
|
91
|
+
remove(x, y, z) {
|
|
92
|
+
const removed = cells.delete(cellKey(x, y, z));
|
|
93
|
+
if (removed) {
|
|
94
|
+
bumpChunk(x, y, z);
|
|
95
|
+
notify();
|
|
96
|
+
}
|
|
97
|
+
return removed;
|
|
98
|
+
},
|
|
99
|
+
get(x, y, z) {
|
|
100
|
+
return getCell(x, y, z)?.type ?? null;
|
|
101
|
+
},
|
|
102
|
+
has(x, y, z) {
|
|
103
|
+
return cells.has(cellKey(x, y, z));
|
|
104
|
+
},
|
|
105
|
+
fill(min, max, type) {
|
|
106
|
+
let changed = 0;
|
|
107
|
+
for (let x = min[0]; x <= max[0]; x += 1) {
|
|
108
|
+
for (let y = min[1]; y <= max[1]; y += 1) {
|
|
109
|
+
for (let z = min[2]; z <= max[2]; z += 1) {
|
|
110
|
+
if (setCell(x, y, z, type))
|
|
111
|
+
changed += 1;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (changed > 0)
|
|
116
|
+
notify();
|
|
117
|
+
return changed;
|
|
118
|
+
},
|
|
119
|
+
clear() {
|
|
120
|
+
if (cells.size === 0)
|
|
121
|
+
return;
|
|
122
|
+
for (const cell of cells.values())
|
|
123
|
+
bumpChunk(cell.x, cell.y, cell.z);
|
|
124
|
+
cells.clear();
|
|
125
|
+
notify();
|
|
126
|
+
},
|
|
127
|
+
count() {
|
|
128
|
+
return cells.size;
|
|
129
|
+
},
|
|
130
|
+
cells() {
|
|
131
|
+
return cells.values();
|
|
132
|
+
},
|
|
133
|
+
bounds() {
|
|
134
|
+
return computeBounds();
|
|
135
|
+
},
|
|
136
|
+
neighbors(x, y, z) {
|
|
137
|
+
const out = [];
|
|
138
|
+
for (const face of VOXEL_FACES) {
|
|
139
|
+
const [nx, ny, nz] = VOXEL_FACE_NORMALS[face];
|
|
140
|
+
const cell = getCell(x + nx, y + ny, z + nz);
|
|
141
|
+
if (cell !== undefined)
|
|
142
|
+
out.push(cell);
|
|
143
|
+
}
|
|
144
|
+
return out;
|
|
145
|
+
},
|
|
146
|
+
exposedFaces(x, y, z) {
|
|
147
|
+
if (!cells.has(cellKey(x, y, z)))
|
|
148
|
+
return [];
|
|
149
|
+
return VOXEL_FACES.filter((face) => {
|
|
150
|
+
const [nx, ny, nz] = VOXEL_FACE_NORMALS[face];
|
|
151
|
+
return !cells.has(cellKey(x + nx, y + ny, z + nz));
|
|
152
|
+
});
|
|
153
|
+
},
|
|
154
|
+
raycast(origin, direction, maxDistance) {
|
|
155
|
+
const [ox, oy, oz] = origin;
|
|
156
|
+
const [dx, dy, dz] = direction;
|
|
157
|
+
let ix = Math.floor(ox);
|
|
158
|
+
let iy = Math.floor(oy);
|
|
159
|
+
let iz = Math.floor(oz);
|
|
160
|
+
const startCell = getCell(ix, iy, iz);
|
|
161
|
+
if (startCell !== undefined) {
|
|
162
|
+
return {
|
|
163
|
+
x: ix,
|
|
164
|
+
y: iy,
|
|
165
|
+
z: iz,
|
|
166
|
+
type: startCell.type,
|
|
167
|
+
face: entryFace(dx, dy, dz),
|
|
168
|
+
adjacent: [ix, iy, iz],
|
|
169
|
+
distance: 0,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
const stepX = dx > 0 ? 1 : dx < 0 ? -1 : 0;
|
|
173
|
+
const stepY = dy > 0 ? 1 : dy < 0 ? -1 : 0;
|
|
174
|
+
const stepZ = dz > 0 ? 1 : dz < 0 ? -1 : 0;
|
|
175
|
+
const tDeltaX = dx !== 0 ? Math.abs(1 / dx) : Number.POSITIVE_INFINITY;
|
|
176
|
+
const tDeltaY = dy !== 0 ? Math.abs(1 / dy) : Number.POSITIVE_INFINITY;
|
|
177
|
+
const tDeltaZ = dz !== 0 ? Math.abs(1 / dz) : Number.POSITIVE_INFINITY;
|
|
178
|
+
const boundary = (i, o, step) => (step > 0 ? i + 1 - o : o - i);
|
|
179
|
+
let tMaxX = dx !== 0 ? boundary(ix, ox, stepX) * tDeltaX : Number.POSITIVE_INFINITY;
|
|
180
|
+
let tMaxY = dy !== 0 ? boundary(iy, oy, stepY) * tDeltaY : Number.POSITIVE_INFINITY;
|
|
181
|
+
let tMaxZ = dz !== 0 ? boundary(iz, oz, stepZ) * tDeltaZ : Number.POSITIVE_INFINITY;
|
|
182
|
+
let face = "py";
|
|
183
|
+
let travelled = 0;
|
|
184
|
+
while (travelled <= maxDistance) {
|
|
185
|
+
let px = ix;
|
|
186
|
+
let py = iy;
|
|
187
|
+
let pz = iz;
|
|
188
|
+
if (tMaxX < tMaxY && tMaxX < tMaxZ) {
|
|
189
|
+
ix += stepX;
|
|
190
|
+
travelled = tMaxX;
|
|
191
|
+
tMaxX += tDeltaX;
|
|
192
|
+
face = stepX > 0 ? "nx" : "px";
|
|
193
|
+
}
|
|
194
|
+
else if (tMaxY < tMaxZ) {
|
|
195
|
+
iy += stepY;
|
|
196
|
+
travelled = tMaxY;
|
|
197
|
+
tMaxY += tDeltaY;
|
|
198
|
+
face = stepY > 0 ? "ny" : "py";
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
iz += stepZ;
|
|
202
|
+
travelled = tMaxZ;
|
|
203
|
+
tMaxZ += tDeltaZ;
|
|
204
|
+
face = stepZ > 0 ? "nz" : "pz";
|
|
205
|
+
}
|
|
206
|
+
if (travelled > maxDistance)
|
|
207
|
+
break;
|
|
208
|
+
const cell = getCell(ix, iy, iz);
|
|
209
|
+
if (cell !== undefined) {
|
|
210
|
+
return {
|
|
211
|
+
x: ix,
|
|
212
|
+
y: iy,
|
|
213
|
+
z: iz,
|
|
214
|
+
type: cell.type,
|
|
215
|
+
face,
|
|
216
|
+
adjacent: [px, py, pz],
|
|
217
|
+
distance: travelled,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return null;
|
|
222
|
+
},
|
|
223
|
+
chunkOf,
|
|
224
|
+
chunkVersion(cx, cy, cz) {
|
|
225
|
+
return chunkVersions.get(cellKey(cx, cy, cz)) ?? 0;
|
|
226
|
+
},
|
|
227
|
+
subscribe(listener) {
|
|
228
|
+
listeners.add(listener);
|
|
229
|
+
return () => {
|
|
230
|
+
listeners.delete(listener);
|
|
231
|
+
};
|
|
232
|
+
},
|
|
233
|
+
summary() {
|
|
234
|
+
const types = {};
|
|
235
|
+
for (const cell of cells.values()) {
|
|
236
|
+
types[cell.type] = (types[cell.type] ?? 0) + 1;
|
|
237
|
+
}
|
|
238
|
+
return { blocks: cells.size, types, bounds: computeBounds() };
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
}
|