Refactor terrain resampling to use local position sampling based on cell size

This commit is contained in:
2026-05-13 13:53:51 +02:00
parent 75e37d7201
commit 35b8b1d2db

View File

@@ -1285,43 +1285,29 @@ export function sampleTerrainFoliageBlockerMaskAtWorldPosition(
); );
} }
function createTerrainPositionFromCenter( function getTerrainSampleCountForFootprint(
center: Vec3, footprint: number,
cellSize: number
): number {
return Math.max(
MIN_TERRAIN_SAMPLE_COUNT,
Math.round(footprint / cellSize) + 1
);
}
function createLocalPositionResampledTerrainHeights(
terrain: Terrain,
sampleCountX: number, sampleCountX: number,
sampleCountZ: number, sampleCountZ: number,
cellSize: number cellSize: number
): Vec3 {
return {
x: center.x - ((sampleCountX - 1) * cellSize) * 0.5,
y: center.y,
z: center.z - ((sampleCountZ - 1) * cellSize) * 0.5
};
}
function getTerrainFootprintCenter(terrain: Terrain): Vec3 {
return {
x: terrain.position.x + getTerrainFootprintWidth(terrain) * 0.5,
y: terrain.position.y,
z: terrain.position.z + getTerrainFootprintDepth(terrain) * 0.5
};
}
function createResampledTerrainHeights(
terrain: Terrain,
sampleCountX: number,
sampleCountZ: number
): number[] { ): number[] {
const heights = new Array<number>(sampleCountX * sampleCountZ); const heights = new Array<number>(sampleCountX * sampleCountZ);
for (let sampleZ = 0; sampleZ < sampleCountZ; sampleZ += 1) { for (let sampleZ = 0; sampleZ < sampleCountZ; sampleZ += 1) {
const normalizedSampleZ = const sourceSampleZ = (sampleZ * cellSize) / terrain.cellSize;
sampleCountZ === 1 ? 0 : sampleZ / (sampleCountZ - 1);
const sourceSampleZ = normalizedSampleZ * (terrain.sampleCountZ - 1);
for (let sampleX = 0; sampleX < sampleCountX; sampleX += 1) { for (let sampleX = 0; sampleX < sampleCountX; sampleX += 1) {
const normalizedSampleX = const sourceSampleX = (sampleX * cellSize) / terrain.cellSize;
sampleCountX === 1 ? 0 : sampleX / (sampleCountX - 1);
const sourceSampleX = normalizedSampleX * (terrain.sampleCountX - 1);
heights[sampleZ * sampleCountX + sampleX] = heights[sampleZ * sampleCountX + sampleX] =
sampleTerrainHeightAtGridCoordinate( sampleTerrainHeightAtGridCoordinate(
@@ -1335,10 +1321,11 @@ function createResampledTerrainHeights(
return heights; return heights;
} }
function createResampledTerrainPaintWeights( function createLocalPositionResampledTerrainPaintWeights(
terrain: Terrain, terrain: Terrain,
sampleCountX: number, sampleCountX: number,
sampleCountZ: number sampleCountZ: number,
cellSize: number
): number[] { ): number[] {
const storedWeightCount = getTerrainStoredPaintWeightCount( const storedWeightCount = getTerrainStoredPaintWeightCount(
terrain.layers.length terrain.layers.length
@@ -1348,14 +1335,10 @@ function createResampledTerrainPaintWeights(
); );
for (let sampleZ = 0; sampleZ < sampleCountZ; sampleZ += 1) { for (let sampleZ = 0; sampleZ < sampleCountZ; sampleZ += 1) {
const normalizedSampleZ = const sourceSampleZ = (sampleZ * cellSize) / terrain.cellSize;
sampleCountZ === 1 ? 0 : sampleZ / (sampleCountZ - 1);
const sourceSampleZ = normalizedSampleZ * (terrain.sampleCountZ - 1);
for (let sampleX = 0; sampleX < sampleCountX; sampleX += 1) { for (let sampleX = 0; sampleX < sampleCountX; sampleX += 1) {
const normalizedSampleX = const sourceSampleX = (sampleX * cellSize) / terrain.cellSize;
sampleCountX === 1 ? 0 : sampleX / (sampleCountX - 1);
const sourceSampleX = normalizedSampleX * (terrain.sampleCountX - 1);
const offset = const offset =
(sampleZ * sampleCountX + sampleX) * storedWeightCount; (sampleZ * sampleCountX + sampleX) * storedWeightCount;
@@ -1374,24 +1357,21 @@ function createResampledTerrainPaintWeights(
return paintWeights; return paintWeights;
} }
function createResampledTerrainFoliageMasks( function createLocalPositionResampledTerrainFoliageMasks(
terrain: Terrain, terrain: Terrain,
sampleCountX: number, sampleCountX: number,
sampleCountZ: number sampleCountZ: number,
cellSize: number
): TerrainFoliageMaskRegistry { ): TerrainFoliageMaskRegistry {
return Object.fromEntries( return Object.fromEntries(
Object.entries(terrain.foliageMasks).map(([layerId, mask]) => { Object.entries(terrain.foliageMasks).map(([layerId, mask]) => {
const values = new Array<number>(sampleCountX * sampleCountZ); const values = new Array<number>(sampleCountX * sampleCountZ);
for (let sampleZ = 0; sampleZ < sampleCountZ; sampleZ += 1) { for (let sampleZ = 0; sampleZ < sampleCountZ; sampleZ += 1) {
const normalizedSampleZ = const sourceSampleZ = (sampleZ * cellSize) / terrain.cellSize;
sampleCountZ === 1 ? 0 : sampleZ / (sampleCountZ - 1);
const sourceSampleZ = normalizedSampleZ * (mask.resolutionZ - 1);
for (let sampleX = 0; sampleX < sampleCountX; sampleX += 1) { for (let sampleX = 0; sampleX < sampleCountX; sampleX += 1) {
const normalizedSampleX = const sourceSampleX = (sampleX * cellSize) / terrain.cellSize;
sampleCountX === 1 ? 0 : sampleX / (sampleCountX - 1);
const sourceSampleX = normalizedSampleX * (mask.resolutionX - 1);
values[sampleZ * sampleCountX + sampleX] = values[sampleZ * sampleCountX + sampleX] =
sampleTerrainFoliageMaskAtGridCoordinate( sampleTerrainFoliageMaskAtGridCoordinate(
@@ -1415,24 +1395,19 @@ function createResampledTerrainFoliageMasks(
); );
} }
function createResampledTerrainFoliageBlockerMask( function createLocalPositionResampledTerrainFoliageBlockerMask(
terrain: Terrain, terrain: Terrain,
sampleCountX: number, sampleCountX: number,
sampleCountZ: number sampleCountZ: number,
cellSize: number
): TerrainFoliageBlockerMask { ): TerrainFoliageBlockerMask {
const values = new Array<number>(sampleCountX * sampleCountZ); const values = new Array<number>(sampleCountX * sampleCountZ);
for (let sampleZ = 0; sampleZ < sampleCountZ; sampleZ += 1) { for (let sampleZ = 0; sampleZ < sampleCountZ; sampleZ += 1) {
const normalizedSampleZ = const sourceSampleZ = (sampleZ * cellSize) / terrain.cellSize;
sampleCountZ === 1 ? 0 : sampleZ / (sampleCountZ - 1);
const sourceSampleZ =
normalizedSampleZ * (terrain.foliageBlockerMask.resolutionZ - 1);
for (let sampleX = 0; sampleX < sampleCountX; sampleX += 1) { for (let sampleX = 0; sampleX < sampleCountX; sampleX += 1) {
const normalizedSampleX = const sourceSampleX = (sampleX * cellSize) / terrain.cellSize;
sampleCountX === 1 ? 0 : sampleX / (sampleCountX - 1);
const sourceSampleX =
normalizedSampleX * (terrain.foliageBlockerMask.resolutionX - 1);
values[sampleZ * sampleCountX + sampleX] = values[sampleZ * sampleCountX + sampleX] =
sampleTerrainFoliageBlockerMaskAtGridCoordinate( sampleTerrainFoliageBlockerMaskAtGridCoordinate(
@@ -1450,56 +1425,118 @@ function createResampledTerrainFoliageBlockerMask(
}); });
} }
export function resizeTerrainGrid( export function changeTerrainCellSizePreservingFootprint(
terrain: Terrain, terrain: Terrain,
options: Pick<Terrain, "sampleCountX" | "sampleCountZ" | "cellSize"> & { requestedCellSize: number
preserveCenter?: boolean;
}
): Terrain { ): Terrain {
const sampleCountX = normalizeTerrainSampleCount( const cellSize = normalizeTerrainCellSize(requestedCellSize);
options.sampleCountX, const sampleCountX = getTerrainSampleCountForFootprint(
"Terrain sampleCountX" getTerrainFootprintWidth(terrain),
cellSize
); );
const sampleCountZ = normalizeTerrainSampleCount( const sampleCountZ = getTerrainSampleCountForFootprint(
options.sampleCountZ, getTerrainFootprintDepth(terrain),
"Terrain sampleCountZ" cellSize
); );
const cellSize = normalizeTerrainCellSize(options.cellSize);
const preserveCenter = options.preserveCenter ?? true; return createTerrain({
const nextPosition = preserveCenter ...terrain,
? createTerrainPositionFromCenter( position: cloneVec3(terrain.position),
getTerrainFootprintCenter(terrain), sampleCountX,
sampleCountZ,
cellSize,
heights: createLocalPositionResampledTerrainHeights(
terrain,
sampleCountX,
sampleCountZ,
cellSize
),
paintWeights: createLocalPositionResampledTerrainPaintWeights(
terrain,
sampleCountX,
sampleCountZ,
cellSize
),
foliageMasks: createLocalPositionResampledTerrainFoliageMasks(
terrain,
sampleCountX,
sampleCountZ,
cellSize
),
foliageBlockerMask: createLocalPositionResampledTerrainFoliageBlockerMask(
terrain,
sampleCountX, sampleCountX,
sampleCountZ, sampleCountZ,
cellSize cellSize
) )
: cloneVec3(terrain.position); });
}
export function changeTerrainSampleCountsByExtendingOrCropping(
terrain: Terrain,
requestedSampleCountX: number,
requestedSampleCountZ: number
): Terrain {
const sampleCountX = normalizeTerrainSampleCount(
requestedSampleCountX,
"Terrain sampleCountX"
);
const sampleCountZ = normalizeTerrainSampleCount(
requestedSampleCountZ,
"Terrain sampleCountZ"
);
const cellSize = terrain.cellSize;
return createTerrain({ return createTerrain({
...terrain, ...terrain,
position: nextPosition, position: cloneVec3(terrain.position),
sampleCountX, sampleCountX,
sampleCountZ, sampleCountZ,
cellSize, cellSize,
heights: createResampledTerrainHeights(terrain, sampleCountX, sampleCountZ), heights: createLocalPositionResampledTerrainHeights(
paintWeights: createResampledTerrainPaintWeights(
terrain, terrain,
sampleCountX, sampleCountX,
sampleCountZ sampleCountZ,
cellSize
), ),
foliageMasks: createResampledTerrainFoliageMasks( paintWeights: createLocalPositionResampledTerrainPaintWeights(
terrain, terrain,
sampleCountX, sampleCountX,
sampleCountZ sampleCountZ,
cellSize
), ),
foliageBlockerMask: createResampledTerrainFoliageBlockerMask( foliageMasks: createLocalPositionResampledTerrainFoliageMasks(
terrain, terrain,
sampleCountX, sampleCountX,
sampleCountZ sampleCountZ,
cellSize
),
foliageBlockerMask: createLocalPositionResampledTerrainFoliageBlockerMask(
terrain,
sampleCountX,
sampleCountZ,
cellSize
) )
}); });
} }
export function resizeTerrainGrid(
terrain: Terrain,
options: Pick<Terrain, "sampleCountX" | "sampleCountZ" | "cellSize">
): Terrain {
const cellSize = normalizeTerrainCellSize(options.cellSize);
if (cellSize !== terrain.cellSize) {
return changeTerrainCellSizePreservingFootprint(terrain, cellSize);
}
return changeTerrainSampleCountsByExtendingOrCropping(
terrain,
options.sampleCountX,
options.sampleCountZ
);
}
function createDefaultTerrainPosition( function createDefaultTerrainPosition(
sampleCountX: number, sampleCountX: number,
sampleCountZ: number, sampleCountZ: number,