auto-git:

[change] src/document/terrains.ts
This commit is contained in:
2026-05-01 17:56:07 +02:00
parent f0ad920e3b
commit bee39c70b3

View File

@@ -21,6 +21,26 @@ export interface Terrain {
paintWeights: number[];
}
export interface TerrainHeightPatchEntry {
index: number;
before: number;
after: number;
}
interface TerrainBoundsCacheEntry {
heights: number[];
position: Vec3;
sampleCountX: number;
sampleCountZ: number;
cellSize: number;
minHeight: number;
maxHeight: number;
bounds: {
min: Vec3;
max: Vec3;
};
}
export const DEFAULT_TERRAIN_VISIBLE = true;
export const DEFAULT_TERRAIN_ENABLED = true;
export const DEFAULT_TERRAIN_COLLISION_ENABLED = true;
@@ -45,6 +65,16 @@ function cloneVec3(vector: Vec3): Vec3 {
};
}
function cloneTerrainBounds(bounds: { min: Vec3; max: Vec3 }): {
min: Vec3;
max: Vec3;
} {
return {
min: cloneVec3(bounds.min),
max: cloneVec3(bounds.max)
};
}
function areVec3Equal(left: Vec3, right: Vec3): boolean {
return left.x === right.x && left.y === right.y && left.z === right.z;
}
@@ -352,9 +382,125 @@ export function getTerrainFootprintDepth(
return (terrain.sampleCountZ - 1) * terrain.cellSize;
}
export function getTerrainBounds(terrain: Terrain): { min: Vec3; max: Vec3 } {
const terrainBoundsCache = new WeakMap<Terrain, TerrainBoundsCacheEntry>();
function createTerrainBoundsCacheEntry(
terrain: Terrain,
minHeight: number,
maxHeight: number
): TerrainBoundsCacheEntry {
const width = getTerrainFootprintWidth(terrain);
const depth = getTerrainFootprintDepth(terrain);
const bounds = {
min: {
x: terrain.position.x,
y: terrain.position.y + minHeight,
z: terrain.position.z
},
max: {
x: terrain.position.x + width,
y: terrain.position.y + maxHeight,
z: terrain.position.z + depth
}
};
return {
heights: terrain.heights,
position: cloneVec3(terrain.position),
sampleCountX: terrain.sampleCountX,
sampleCountZ: terrain.sampleCountZ,
cellSize: terrain.cellSize,
minHeight,
maxHeight,
bounds
};
}
function isTerrainBoundsCacheEntryCurrent(
terrain: Terrain,
entry: TerrainBoundsCacheEntry
): boolean {
return (
entry.heights === terrain.heights &&
entry.sampleCountX === terrain.sampleCountX &&
entry.sampleCountZ === terrain.sampleCountZ &&
entry.cellSize === terrain.cellSize &&
areVec3Equal(entry.position, terrain.position)
);
}
export function invalidateTerrainBoundsCache(terrain: Terrain) {
terrainBoundsCache.delete(terrain);
}
export function updateTerrainBoundsCacheAfterHeightPatch(
terrain: Terrain,
patch: readonly TerrainHeightPatchEntry[]
) {
if (patch.length === 0) {
return;
}
const cachedEntry = terrainBoundsCache.get(terrain);
if (
cachedEntry === undefined ||
!isTerrainBoundsCacheEntryCurrent(terrain, cachedEntry)
) {
return;
}
let minHeight = cachedEntry.minHeight;
let maxHeight = cachedEntry.maxHeight;
let requiresRescan = false;
for (const entry of patch) {
if (
!Number.isInteger(entry.index) ||
entry.index < 0 ||
entry.index >= terrain.heights.length ||
!Number.isFinite(entry.before) ||
!Number.isFinite(entry.after)
) {
requiresRescan = true;
break;
}
if (entry.before === minHeight && entry.after > entry.before) {
requiresRescan = true;
break;
}
if (entry.before === maxHeight && entry.after < entry.before) {
requiresRescan = true;
break;
}
minHeight = Math.min(minHeight, entry.after);
maxHeight = Math.max(maxHeight, entry.after);
}
if (requiresRescan) {
invalidateTerrainBoundsCache(terrain);
return;
}
terrainBoundsCache.set(
terrain,
createTerrainBoundsCacheEntry(terrain, minHeight, maxHeight)
);
}
export function getTerrainBounds(terrain: Terrain): { min: Vec3; max: Vec3 } {
const cachedEntry = terrainBoundsCache.get(terrain);
if (
cachedEntry !== undefined &&
isTerrainBoundsCacheEntryCurrent(terrain, cachedEntry)
) {
return cloneTerrainBounds(cachedEntry.bounds);
}
let minHeight = Number.POSITIVE_INFINITY;
let maxHeight = Number.NEGATIVE_INFINITY;
@@ -368,18 +514,14 @@ export function getTerrainBounds(terrain: Terrain): { min: Vec3; max: Vec3 } {
maxHeight = 0;
}
return {
min: {
x: terrain.position.x,
y: terrain.position.y + minHeight,
z: terrain.position.z
},
max: {
x: terrain.position.x + width,
y: terrain.position.y + maxHeight,
z: terrain.position.z + depth
}
};
const nextEntry = createTerrainBoundsCacheEntry(
terrain,
minHeight,
maxHeight
);
terrainBoundsCache.set(terrain, nextEntry);
return cloneTerrainBounds(nextEntry.bounds);
}
function clamp(value: number, min: number, max: number): number {