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, getTerrainFoliageBlockerMaskValueAtSample,
getTerrainFoliageMask, getTerrainFoliageMask,
getTerrainFoliageMaskValueAtSample, getTerrainFoliageMaskValueAtSample,
getTerrainFootprintDepth,
getTerrainFootprintWidth,
getTerrainHeightAtSample,
getTerrainPaintWeightSampleOffset, getTerrainPaintWeightSampleOffset,
createTerrainWithAddedLayer, createTerrainWithAddedLayer,
createTerrainWithRemovedLayer, createTerrainWithRemovedLayer,
sampleTerrainHeightAtLocalPosition,
sampleTerrainFoliageBlockerMaskAtWorldPosition, sampleTerrainFoliageBlockerMaskAtWorldPosition,
sampleTerrainFoliageMaskAtWorldPosition, sampleTerrainFoliageMaskAtWorldPosition,
updateTerrainBoundsCacheAfterHeightPatch, updateTerrainBoundsCacheAfterHeightPatch,
@@ -17,80 +21,268 @@ import {
createTerrain createTerrain
} from "../../src/document/terrains"; } 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", () => { describe("terrain grid resizing", () => {
it("keeps terrain centered while bilinearly resampling heights and paint weights", () => { it("changes Cell Size from 1m to 0.5m by increasing resolution and preserving footprint", () => {
const foliageLayerId = "foliage-layer-resample";
const terrain = createTerrain({ const terrain = createTerrain({
id: "terrain-resample-main", id: "terrain-cell-size-resolution",
position: { sampleCountX: 9,
x: 0, sampleCountZ: 9,
y: 1,
z: 0
},
sampleCountX: 2,
sampleCountZ: 2,
cellSize: 1, cellSize: 1,
heights: [0, 2, 4, 6], heights: createIndexedHeights(9, 9)
paintWeights: [ });
0,
0, const resizedTerrain = resizeTerrainGrid(terrain, {
0, sampleCountX: terrain.sampleCountX,
0.6, sampleCountZ: terrain.sampleCountZ,
0, cellSize: 0.5
0, });
0,
0.5, expect(resizedTerrain.sampleCountX).toBe(17);
0, expect(resizedTerrain.sampleCountZ).toBe(17);
0, expect(resizedTerrain.cellSize).toBe(0.5);
0, expect(getTerrainFootprintWidth(resizedTerrain)).toBe(8);
0.25 expect(getTerrainFootprintDepth(resizedTerrain)).toBe(8);
], expect(resizedTerrain.position).toEqual(terrain.position);
foliageMasks: { });
[foliageLayerId]: createTerrainFoliageMask({
layerId: foliageLayerId, it("preserves terrain heights at representative local positions when Cell Size changes", () => {
resolutionX: 2, const terrain = createTerrain({
resolutionZ: 2, id: "terrain-cell-size-height-preserve",
values: [0, 1, 0, 0.5] sampleCountX: 9,
}) sampleCountZ: 9,
}, cellSize: 1,
foliageBlockerMask: createTerrainFoliageBlockerMask({ heights: createIndexedHeights(9, 9)
resolutionX: 2, });
resolutionZ: 2,
values: [0, 0.25, 0.5, 1] 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, { const resizedTerrain = resizeTerrainGrid(terrain, {
sampleCountX: 3, sampleCountX: 3,
sampleCountZ: 3, sampleCountZ: 3,
cellSize: 1 cellSize: terrain.cellSize
}); });
const centerOffset = getTerrainPaintWeightSampleOffset(resizedTerrain, 1, 1);
const resizedMask = getTerrainFoliageMask(resizedTerrain, foliageLayerId);
expect(resizedTerrain.position).toEqual({ expect(resizedTerrain.position).toEqual(terrain.position);
x: -0.5, expect(resizedTerrain.cellSize).toBe(1);
y: 1, expect(getTerrainFootprintWidth(resizedTerrain)).toBe(2);
z: -0.5 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);
}); });
expect(resizedTerrain.sampleCountX).toBe(3);
expect(resizedTerrain.sampleCountZ).toBe(3); it("fills newly extended height samples from the nearest existing edge height", () => {
expect(resizedTerrain.heights[4]).toBeCloseTo(3); const terrain = createTerrain({
expect(resizedTerrain.paintWeights[centerOffset]).toBeCloseTo(0.15); id: "terrain-samples-edge-fill",
expect(resizedTerrain.paintWeights[centerOffset + 1]).toBeCloseTo(0.125); sampleCountX: 3,
expect(resizedTerrain.paintWeights[centerOffset + 2]).toBeCloseTo(0.0625); sampleCountZ: 3,
expect(resizedMask?.resolutionX).toBe(3); cellSize: 1,
expect(resizedMask?.resolutionZ).toBe(3); heights: createIndexedHeights(3, 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
}); });
expect(terrain.heights).toEqual([0, 2, 4, 6]);
const resizedTerrain = resizeTerrainGrid(terrain, {
sampleCountX: 5,
sampleCountZ: 5,
cellSize: terrain.cellSize
});
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);
}); });
}); });