auto-git:
[change] tests/domain/terrains.test.ts
This commit is contained in:
@@ -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);
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
const resizedTerrain = resizeTerrainGrid(terrain, {
|
||||||
expect(resizedTerrain.heights[4]).toBeCloseTo(3);
|
sampleCountX: 5,
|
||||||
expect(resizedTerrain.paintWeights[centerOffset]).toBeCloseTo(0.15);
|
sampleCountZ: 5,
|
||||||
expect(resizedTerrain.paintWeights[centerOffset + 1]).toBeCloseTo(0.125);
|
cellSize: terrain.cellSize
|
||||||
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
|
|
||||||
});
|
});
|
||||||
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