auto-git:
[change] tests/domain/terrains.test.ts
This commit is contained in:
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user