auto-git:

[change] tests/domain/terrains.test.ts
This commit is contained in:
2026-05-13 13:55:33 +02:00
parent b72aefe2fa
commit cafc90e5db

View File

@@ -7,9 +7,13 @@ import {
getTerrainFoliageBlockerMaskValueAtSample,
getTerrainFoliageMask,
getTerrainFoliageMaskValueAtSample,
getTerrainFootprintDepth,
getTerrainFootprintWidth,
getTerrainHeightAtSample,
getTerrainPaintWeightSampleOffset,
createTerrainWithAddedLayer,
createTerrainWithRemovedLayer,
sampleTerrainHeightAtLocalPosition,
sampleTerrainFoliageBlockerMaskAtWorldPosition,
sampleTerrainFoliageMaskAtWorldPosition,
updateTerrainBoundsCacheAfterHeightPatch,
@@ -17,80 +21,268 @@ import {
createTerrain
} from "../../src/document/terrains";
function createIndexedHeights(
sampleCountX: number,
sampleCountZ: number
): number[] {
return Array.from({ length: sampleCountX * sampleCountZ }, (_, index) => {
const sampleX = index % sampleCountX;
const sampleZ = Math.floor(index / sampleCountX);
return sampleX + sampleZ * 10;
});
}
function createIndexedPaintWeights(
sampleCountX: number,
sampleCountZ: number
): number[] {
const paintWeights: number[] = [];
for (let sampleZ = 0; sampleZ < sampleCountZ; sampleZ += 1) {
for (let sampleX = 0; sampleX < sampleCountX; sampleX += 1) {
paintWeights.push(
sampleX * 0.01 + sampleZ * 0.02,
sampleX * 0.03,
sampleZ * 0.04
);
}
}
return paintWeights;
}
function createIndexedMaskValues(
sampleCountX: number,
sampleCountZ: number
): number[] {
const sampleCount = sampleCountX * sampleCountZ;
return Array.from({ length: sampleCount }, (_, index) => index / sampleCount);
}
describe("terrain grid resizing", () => {
it("keeps terrain centered while bilinearly resampling heights and paint weights", () => {
const foliageLayerId = "foliage-layer-resample";
it("changes Cell Size from 1m to 0.5m by increasing resolution and preserving footprint", () => {
const terrain = createTerrain({
id: "terrain-resample-main",
position: {
x: 0,
y: 1,
z: 0
},
sampleCountX: 2,
sampleCountZ: 2,
id: "terrain-cell-size-resolution",
sampleCountX: 9,
sampleCountZ: 9,
cellSize: 1,
heights: [0, 2, 4, 6],
paintWeights: [
0,
0,
0,
0.6,
0,
0,
0,
0.5,
0,
0,
0,
0.25
],
foliageMasks: {
[foliageLayerId]: createTerrainFoliageMask({
layerId: foliageLayerId,
resolutionX: 2,
resolutionZ: 2,
values: [0, 1, 0, 0.5]
})
},
foliageBlockerMask: createTerrainFoliageBlockerMask({
resolutionX: 2,
resolutionZ: 2,
values: [0, 0.25, 0.5, 1]
})
heights: createIndexedHeights(9, 9)
});
const resizedTerrain = resizeTerrainGrid(terrain, {
sampleCountX: terrain.sampleCountX,
sampleCountZ: terrain.sampleCountZ,
cellSize: 0.5
});
expect(resizedTerrain.sampleCountX).toBe(17);
expect(resizedTerrain.sampleCountZ).toBe(17);
expect(resizedTerrain.cellSize).toBe(0.5);
expect(getTerrainFootprintWidth(resizedTerrain)).toBe(8);
expect(getTerrainFootprintDepth(resizedTerrain)).toBe(8);
expect(resizedTerrain.position).toEqual(terrain.position);
});
it("preserves terrain heights at representative local positions when Cell Size changes", () => {
const terrain = createTerrain({
id: "terrain-cell-size-height-preserve",
sampleCountX: 9,
sampleCountZ: 9,
cellSize: 1,
heights: createIndexedHeights(9, 9)
});
const resizedTerrain = resizeTerrainGrid(terrain, {
sampleCountX: terrain.sampleCountX,
sampleCountZ: terrain.sampleCountZ,
cellSize: 0.5
});
expect(sampleTerrainHeightAtLocalPosition(resizedTerrain, 2.5, 3.5)).toBeCloseTo(
sampleTerrainHeightAtLocalPosition(terrain, 2.5, 3.5) ?? 0
);
expect(sampleTerrainHeightAtLocalPosition(resizedTerrain, 6, 1)).toBeCloseTo(
sampleTerrainHeightAtLocalPosition(terrain, 6, 1) ?? 0
);
});
it("preserves paint weights at representative local positions when Cell Size changes", () => {
const terrain = createTerrain({
id: "terrain-cell-size-paint-preserve",
sampleCountX: 9,
sampleCountZ: 9,
cellSize: 1,
paintWeights: createIndexedPaintWeights(9, 9)
});
const resizedTerrain = resizeTerrainGrid(terrain, {
sampleCountX: terrain.sampleCountX,
sampleCountZ: terrain.sampleCountZ,
cellSize: 0.5
});
const originalOffset = getTerrainPaintWeightSampleOffset(terrain, 4, 2);
const resizedOffset = getTerrainPaintWeightSampleOffset(resizedTerrain, 8, 4);
expect(resizedTerrain.paintWeights[resizedOffset]).toBeCloseTo(
terrain.paintWeights[originalOffset] ?? 0
);
expect(resizedTerrain.paintWeights[resizedOffset + 1]).toBeCloseTo(
terrain.paintWeights[originalOffset + 1] ?? 0
);
expect(resizedTerrain.paintWeights[resizedOffset + 2]).toBeCloseTo(
terrain.paintWeights[originalOffset + 2] ?? 0
);
});
it("increases Samples X/Z by extending terrain instead of stretching existing data", () => {
const terrain = createTerrain({
id: "terrain-samples-extend",
sampleCountX: 3,
sampleCountZ: 3,
cellSize: 1,
heights: createIndexedHeights(3, 3)
});
const resizedTerrain = resizeTerrainGrid(terrain, {
sampleCountX: 5,
sampleCountZ: 5,
cellSize: terrain.cellSize
});
expect(resizedTerrain.position).toEqual(terrain.position);
expect(resizedTerrain.cellSize).toBe(1);
expect(getTerrainFootprintWidth(resizedTerrain)).toBe(4);
expect(getTerrainFootprintDepth(resizedTerrain)).toBe(4);
expect(getTerrainHeightAtSample(resizedTerrain, 0, 0)).toBe(0);
expect(getTerrainHeightAtSample(resizedTerrain, 1, 1)).toBe(11);
expect(getTerrainHeightAtSample(resizedTerrain, 2, 2)).toBe(22);
});
it("decreases Samples X/Z by cropping terrain instead of squashing existing data", () => {
const terrain = createTerrain({
id: "terrain-samples-crop",
sampleCountX: 5,
sampleCountZ: 5,
cellSize: 1,
heights: createIndexedHeights(5, 5)
});
const resizedTerrain = resizeTerrainGrid(terrain, {
sampleCountX: 3,
sampleCountZ: 3,
cellSize: 1
cellSize: terrain.cellSize
});
const centerOffset = getTerrainPaintWeightSampleOffset(resizedTerrain, 1, 1);
const resizedMask = getTerrainFoliageMask(resizedTerrain, foliageLayerId);
expect(resizedTerrain.position).toEqual({
x: -0.5,
y: 1,
z: -0.5
expect(resizedTerrain.position).toEqual(terrain.position);
expect(resizedTerrain.cellSize).toBe(1);
expect(getTerrainFootprintWidth(resizedTerrain)).toBe(2);
expect(getTerrainFootprintDepth(resizedTerrain)).toBe(2);
expect(getTerrainHeightAtSample(resizedTerrain, 0, 0)).toBe(0);
expect(getTerrainHeightAtSample(resizedTerrain, 1, 1)).toBe(11);
expect(getTerrainHeightAtSample(resizedTerrain, 2, 2)).toBe(22);
});
it("fills newly extended height samples from the nearest existing edge height", () => {
const terrain = createTerrain({
id: "terrain-samples-edge-fill",
sampleCountX: 3,
sampleCountZ: 3,
cellSize: 1,
heights: createIndexedHeights(3, 3)
});
expect(resizedTerrain.sampleCountX).toBe(3);
expect(resizedTerrain.sampleCountZ).toBe(3);
expect(resizedTerrain.heights[4]).toBeCloseTo(3);
expect(resizedTerrain.paintWeights[centerOffset]).toBeCloseTo(0.15);
expect(resizedTerrain.paintWeights[centerOffset + 1]).toBeCloseTo(0.125);
expect(resizedTerrain.paintWeights[centerOffset + 2]).toBeCloseTo(0.0625);
expect(resizedMask?.resolutionX).toBe(3);
expect(resizedMask?.resolutionZ).toBe(3);
expect(resizedMask?.values[4]).toBeCloseTo(0.375);
expect(resizedTerrain.foliageBlockerMask.resolutionX).toBe(3);
expect(resizedTerrain.foliageBlockerMask.resolutionZ).toBe(3);
expect(resizedTerrain.foliageBlockerMask.values[4]).toBeCloseTo(0.4375);
expect(terrain.position).toEqual({
x: 0,
y: 1,
z: 0
const resizedTerrain = resizeTerrainGrid(terrain, {
sampleCountX: 5,
sampleCountZ: 5,
cellSize: terrain.cellSize
});
expect(terrain.heights).toEqual([0, 2, 4, 6]);
expect(getTerrainHeightAtSample(resizedTerrain, 4, 1)).toBe(12);
expect(getTerrainHeightAtSample(resizedTerrain, 1, 4)).toBe(21);
expect(getTerrainHeightAtSample(resizedTerrain, 4, 4)).toBe(22);
});
it("keeps foliage masks and blocker masks valid after cell-size and sample-count edits", () => {
const foliageLayerId = "foliage-layer-resize-valid";
const terrain = createTerrain({
id: "terrain-masks-resize-valid",
sampleCountX: 3,
sampleCountZ: 3,
cellSize: 1,
foliageMasks: {
[foliageLayerId]: createTerrainFoliageMask({
layerId: foliageLayerId,
resolutionX: 3,
resolutionZ: 3,
values: createIndexedMaskValues(3, 3)
})
},
foliageBlockerMask: createTerrainFoliageBlockerMask({
resolutionX: 3,
resolutionZ: 3,
values: createIndexedMaskValues(3, 3)
})
});
const resolutionChangedTerrain = resizeTerrainGrid(terrain, {
sampleCountX: terrain.sampleCountX,
sampleCountZ: terrain.sampleCountZ,
cellSize: 0.5
});
const extendedTerrain = resizeTerrainGrid(resolutionChangedTerrain, {
sampleCountX: 6,
sampleCountZ: 4,
cellSize: resolutionChangedTerrain.cellSize
});
const resizedMask = getTerrainFoliageMask(extendedTerrain, foliageLayerId);
expect(resizedMask?.resolutionX).toBe(extendedTerrain.sampleCountX);
expect(resizedMask?.resolutionZ).toBe(extendedTerrain.sampleCountZ);
expect(resizedMask?.values).toHaveLength(
extendedTerrain.sampleCountX * extendedTerrain.sampleCountZ
);
expect(extendedTerrain.foliageBlockerMask.resolutionX).toBe(
extendedTerrain.sampleCountX
);
expect(extendedTerrain.foliageBlockerMask.resolutionZ).toBe(
extendedTerrain.sampleCountZ
);
expect(extendedTerrain.foliageBlockerMask.values).toHaveLength(
extendedTerrain.sampleCountX * extendedTerrain.sampleCountZ
);
expect(resizedMask?.values.every((value) => Number.isFinite(value))).toBe(
true
);
expect(
extendedTerrain.foliageBlockerMask.values.every((value) =>
Number.isFinite(value)
)
).toBe(true);
});
it("chooses deterministic sample counts for non-divisible footprint and cell-size edits", () => {
const terrain = createTerrain({
id: "terrain-cell-size-non-divisible",
sampleCountX: 9,
sampleCountZ: 9,
cellSize: 1,
heights: createIndexedHeights(9, 9)
});
const resizedTerrain = resizeTerrainGrid(terrain, {
sampleCountX: terrain.sampleCountX,
sampleCountZ: terrain.sampleCountZ,
cellSize: 3
});
expect(resizedTerrain.cellSize).toBe(3);
expect(resizedTerrain.sampleCountX).toBe(4);
expect(resizedTerrain.sampleCountZ).toBe(4);
expect(getTerrainFootprintWidth(resizedTerrain)).toBe(9);
expect(getTerrainFootprintDepth(resizedTerrain)).toBe(9);
expect(resizedTerrain.position).toEqual(terrain.position);
});
});