auto-git:
[change] src/geometry/terrain-mesh.ts [change] tests/geometry/terrain-mesh.test.ts
This commit is contained in:
@@ -37,11 +37,7 @@ export interface DerivedTerrainMeshData {
|
|||||||
export const TERRAIN_LOD_CHUNK_SIZE_CELLS = 64;
|
export const TERRAIN_LOD_CHUNK_SIZE_CELLS = 64;
|
||||||
export const TERRAIN_LOD_STRIDES = [1, 2, 4, 8, 16] as const;
|
export const TERRAIN_LOD_STRIDES = [1, 2, 4, 8, 16] as const;
|
||||||
export const TERRAIN_LOD_DEBUG_COLORS = [
|
export const TERRAIN_LOD_DEBUG_COLORS = [
|
||||||
0xff4d4d,
|
0xff4d4d, 0xffa53d, 0xffe66d, 0x4ee06f, 0x4ba3ff
|
||||||
0xffa53d,
|
|
||||||
0xffe66d,
|
|
||||||
0x4ee06f,
|
|
||||||
0x4ba3ff
|
|
||||||
] as const;
|
] as const;
|
||||||
const TERRAIN_LOD_DISTANCE_MULTIPLIERS = [0.75, 1.5, 3, 6] as const;
|
const TERRAIN_LOD_DISTANCE_MULTIPLIERS = [0.75, 1.5, 3, 6] as const;
|
||||||
const TERRAIN_LOD_HYSTERESIS_RATIO = 0.16;
|
const TERRAIN_LOD_HYSTERESIS_RATIO = 0.16;
|
||||||
@@ -232,7 +228,9 @@ export function buildTerrainDerivedMeshData(
|
|||||||
const vertexCount = terrain.sampleCountX * terrain.sampleCountZ;
|
const vertexCount = terrain.sampleCountX * terrain.sampleCountZ;
|
||||||
const positions = new Float32Array(vertexCount * 3);
|
const positions = new Float32Array(vertexCount * 3);
|
||||||
const uvs = new Float32Array(vertexCount * 2);
|
const uvs = new Float32Array(vertexCount * 2);
|
||||||
const layerWeights = new Float32Array(vertexCount * TERRAIN_SHADER_LAYER_COUNT);
|
const layerWeights = new Float32Array(
|
||||||
|
vertexCount * TERRAIN_SHADER_LAYER_COUNT
|
||||||
|
);
|
||||||
const foliageMaskWeights = new Float32Array(vertexCount);
|
const foliageMaskWeights = new Float32Array(vertexCount);
|
||||||
const foliageMask =
|
const foliageMask =
|
||||||
options.foliageMaskLayerId === undefined ||
|
options.foliageMaskLayerId === undefined ||
|
||||||
@@ -289,11 +287,7 @@ export function buildTerrainDerivedMeshData(
|
|||||||
)
|
)
|
||||||
: foliageMask === null
|
: foliageMask === null
|
||||||
? 0
|
? 0
|
||||||
: getTerrainFoliageMaskValueAtSample(
|
: getTerrainFoliageMaskValueAtSample(foliageMask, sampleX, sampleZ);
|
||||||
foliageMask,
|
|
||||||
sampleX,
|
|
||||||
sampleZ
|
|
||||||
);
|
|
||||||
foliageMaskWeightOffset += 1;
|
foliageMaskWeightOffset += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -494,7 +488,11 @@ function sampleTerrainLayerWeightsAtSamplePosition(
|
|||||||
): number[] {
|
): number[] {
|
||||||
const layerWeights = new Array<number>(terrain.layers.length).fill(0);
|
const layerWeights = new Array<number>(terrain.layers.length).fill(0);
|
||||||
|
|
||||||
for (let layerIndex = 0; layerIndex < terrain.layers.length; layerIndex += 1) {
|
for (
|
||||||
|
let layerIndex = 0;
|
||||||
|
layerIndex < terrain.layers.length;
|
||||||
|
layerIndex += 1
|
||||||
|
) {
|
||||||
layerWeights[layerIndex] = sampleTerrainScalarAtSamplePosition(
|
layerWeights[layerIndex] = sampleTerrainScalarAtSamplePosition(
|
||||||
terrain,
|
terrain,
|
||||||
sampleX,
|
sampleX,
|
||||||
@@ -550,13 +548,22 @@ function pushTerrainLodNormal(
|
|||||||
sampleZ: number,
|
sampleZ: number,
|
||||||
normals: number[]
|
normals: number[]
|
||||||
) {
|
) {
|
||||||
const leftSampleX = clampSampleCoordinate(sampleX - 1, terrain.sampleCountX - 1);
|
const leftSampleX = clampSampleCoordinate(
|
||||||
const rightSampleX = clampSampleCoordinate(sampleX + 1, terrain.sampleCountX - 1);
|
sampleX - 1,
|
||||||
|
terrain.sampleCountX - 1
|
||||||
|
);
|
||||||
|
const rightSampleX = clampSampleCoordinate(
|
||||||
|
sampleX + 1,
|
||||||
|
terrain.sampleCountX - 1
|
||||||
|
);
|
||||||
const bottomSampleZ = clampSampleCoordinate(
|
const bottomSampleZ = clampSampleCoordinate(
|
||||||
sampleZ - 1,
|
sampleZ - 1,
|
||||||
terrain.sampleCountZ - 1
|
terrain.sampleCountZ - 1
|
||||||
);
|
);
|
||||||
const topSampleZ = clampSampleCoordinate(sampleZ + 1, terrain.sampleCountZ - 1);
|
const topSampleZ = clampSampleCoordinate(
|
||||||
|
sampleZ + 1,
|
||||||
|
terrain.sampleCountZ - 1
|
||||||
|
);
|
||||||
const dxDenominator = Math.max(
|
const dxDenominator = Math.max(
|
||||||
(rightSampleX - leftSampleX) * terrain.cellSize,
|
(rightSampleX - leftSampleX) * terrain.cellSize,
|
||||||
Number.EPSILON
|
Number.EPSILON
|
||||||
@@ -752,7 +759,10 @@ function pushTerrainLodBoundaryPoint(
|
|||||||
) {
|
) {
|
||||||
const previous = points[points.length - 1];
|
const previous = points[points.length - 1];
|
||||||
|
|
||||||
if (previous !== undefined && areTerrainLodSamplePointsEqual(previous, point)) {
|
if (
|
||||||
|
previous !== undefined &&
|
||||||
|
areTerrainLodSamplePointsEqual(previous, point)
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -844,16 +854,8 @@ function buildTerrainLodLevelMeshData(
|
|||||||
stride: number,
|
stride: number,
|
||||||
options: TerrainMeshBuildOptions
|
options: TerrainMeshBuildOptions
|
||||||
): TerrainLodLevelMeshData {
|
): TerrainLodLevelMeshData {
|
||||||
const sampleXs = createLodSampleCoordinates(
|
const sampleXs = createLodSampleCoordinates(startSampleX, endSampleX, stride);
|
||||||
startSampleX,
|
const sampleZs = createLodSampleCoordinates(startSampleZ, endSampleZ, stride);
|
||||||
endSampleX,
|
|
||||||
stride
|
|
||||||
);
|
|
||||||
const sampleZs = createLodSampleCoordinates(
|
|
||||||
startSampleZ,
|
|
||||||
endSampleZ,
|
|
||||||
stride
|
|
||||||
);
|
|
||||||
const positions: number[] = [];
|
const positions: number[] = [];
|
||||||
const uvs: number[] = [];
|
const uvs: number[] = [];
|
||||||
const layerWeights: number[] = [];
|
const layerWeights: number[] = [];
|
||||||
@@ -1307,29 +1309,23 @@ export function resolveTerrainLodLevelIndexWithHysteresis(options: {
|
|||||||
const lowerBoundary =
|
const lowerBoundary =
|
||||||
activeLevelIndex <= 0
|
activeLevelIndex <= 0
|
||||||
? Number.NEGATIVE_INFINITY
|
? Number.NEGATIVE_INFINITY
|
||||||
: TERRAIN_LOD_DISTANCE_MULTIPLIERS[activeLevelIndex - 1] ??
|
: (TERRAIN_LOD_DISTANCE_MULTIPLIERS[activeLevelIndex - 1] ??
|
||||||
TERRAIN_LOD_DISTANCE_MULTIPLIERS[
|
TERRAIN_LOD_DISTANCE_MULTIPLIERS[
|
||||||
TERRAIN_LOD_DISTANCE_MULTIPLIERS.length - 1
|
TERRAIN_LOD_DISTANCE_MULTIPLIERS.length - 1
|
||||||
]!;
|
]!);
|
||||||
const upperBoundary =
|
const upperBoundary =
|
||||||
activeLevelIndex >= options.levelCount - 1
|
activeLevelIndex >= options.levelCount - 1
|
||||||
? Number.POSITIVE_INFINITY
|
? Number.POSITIVE_INFINITY
|
||||||
: TERRAIN_LOD_DISTANCE_MULTIPLIERS[activeLevelIndex] ??
|
: (TERRAIN_LOD_DISTANCE_MULTIPLIERS[activeLevelIndex] ??
|
||||||
TERRAIN_LOD_DISTANCE_MULTIPLIERS[
|
TERRAIN_LOD_DISTANCE_MULTIPLIERS[
|
||||||
TERRAIN_LOD_DISTANCE_MULTIPLIERS.length - 1
|
TERRAIN_LOD_DISTANCE_MULTIPLIERS.length - 1
|
||||||
]!;
|
]!);
|
||||||
|
|
||||||
if (
|
if (normalizedDistance > upperBoundary * (1 + TERRAIN_LOD_HYSTERESIS_RATIO)) {
|
||||||
normalizedDistance >
|
|
||||||
upperBoundary * (1 + TERRAIN_LOD_HYSTERESIS_RATIO)
|
|
||||||
) {
|
|
||||||
return Math.min(activeLevelIndex + 1, options.levelCount - 1);
|
return Math.min(activeLevelIndex + 1, options.levelCount - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (normalizedDistance < lowerBoundary * (1 - TERRAIN_LOD_HYSTERESIS_RATIO)) {
|
||||||
normalizedDistance <
|
|
||||||
lowerBoundary * (1 - TERRAIN_LOD_HYSTERESIS_RATIO)
|
|
||||||
) {
|
|
||||||
return Math.max(activeLevelIndex - 1, 0);
|
return Math.max(activeLevelIndex - 1, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,10 @@ import {
|
|||||||
resolveTerrainLodLevelIndexWithHysteresis
|
resolveTerrainLodLevelIndexWithHysteresis
|
||||||
} from "../../src/geometry/terrain-mesh";
|
} from "../../src/geometry/terrain-mesh";
|
||||||
|
|
||||||
function createJaggedTerrainHeights(sampleCountX: number, sampleCountZ: number) {
|
function createJaggedTerrainHeights(
|
||||||
|
sampleCountX: number,
|
||||||
|
sampleCountZ: number
|
||||||
|
) {
|
||||||
return Array.from({ length: sampleCountX * sampleCountZ }, (_, index) => {
|
return Array.from({ length: sampleCountX * sampleCountZ }, (_, index) => {
|
||||||
const sampleX = index % sampleCountX;
|
const sampleX = index % sampleCountX;
|
||||||
const sampleZ = Math.floor(index / sampleCountX);
|
const sampleZ = Math.floor(index / sampleCountX);
|
||||||
@@ -62,13 +65,9 @@ function collectTopEdgeKeys(options: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sampleX =
|
const sampleX =
|
||||||
options.axis === "x"
|
options.axis === "x" ? options.sampleCoordinate : roundedVaryingSample;
|
||||||
? options.sampleCoordinate
|
|
||||||
: roundedVaryingSample;
|
|
||||||
const sampleZ =
|
const sampleZ =
|
||||||
options.axis === "x"
|
options.axis === "x" ? roundedVaryingSample : options.sampleCoordinate;
|
||||||
? roundedVaryingSample
|
|
||||||
: options.sampleCoordinate;
|
|
||||||
const expectedHeight = getTerrainHeightAtSample(
|
const expectedHeight = getTerrainHeightAtSample(
|
||||||
options.terrain,
|
options.terrain,
|
||||||
sampleX,
|
sampleX,
|
||||||
@@ -83,7 +82,9 @@ function collectTopEdgeKeys(options: {
|
|||||||
keys.add(`${roundedVaryingSample}:${height.toFixed(6)}`);
|
keys.add(`${roundedVaryingSample}:${height.toFixed(6)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return [...keys].sort((left, right) => Number(left.split(":")[0]) - Number(right.split(":")[0]));
|
return [...keys].sort(
|
||||||
|
(left, right) => Number(left.split(":")[0]) - Number(right.split(":")[0])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function collectLoweredEdgeVertexCount(options: {
|
function collectLoweredEdgeVertexCount(options: {
|
||||||
@@ -123,18 +124,17 @@ function collectLoweredEdgeVertexCount(options: {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (roundedVaryingSample === 0 || roundedVaryingSample === maxVaryingSample) {
|
if (
|
||||||
|
roundedVaryingSample === 0 ||
|
||||||
|
roundedVaryingSample === maxVaryingSample
|
||||||
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sampleX =
|
const sampleX =
|
||||||
options.axis === "x"
|
options.axis === "x" ? options.sampleCoordinate : roundedVaryingSample;
|
||||||
? options.sampleCoordinate
|
|
||||||
: roundedVaryingSample;
|
|
||||||
const sampleZ =
|
const sampleZ =
|
||||||
options.axis === "x"
|
options.axis === "x" ? roundedVaryingSample : options.sampleCoordinate;
|
||||||
? roundedVaryingSample
|
|
||||||
: options.sampleCoordinate;
|
|
||||||
const expectedHeight = getTerrainHeightAtSample(
|
const expectedHeight = getTerrainHeightAtSample(
|
||||||
options.terrain,
|
options.terrain,
|
||||||
sampleX,
|
sampleX,
|
||||||
@@ -201,27 +201,16 @@ describe("terrain mesh generation", () => {
|
|||||||
});
|
});
|
||||||
const derivedMesh = buildTerrainDerivedMeshData(terrain);
|
const derivedMesh = buildTerrainDerivedMeshData(terrain);
|
||||||
|
|
||||||
expect(Array.from(derivedMesh.uvs)).toEqual([10, -6, 12, -6, 10, -4, 12, -4]);
|
expect(Array.from(derivedMesh.uvs)).toEqual([
|
||||||
|
10, -6, 12, -6, 10, -4, 12, -4
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("derives full per-vertex layer weights from the compact terrain paint data", () => {
|
it("derives full per-vertex layer weights from the compact terrain paint data", () => {
|
||||||
const terrain = createTerrain({
|
const terrain = createTerrain({
|
||||||
sampleCountX: 2,
|
sampleCountX: 2,
|
||||||
sampleCountZ: 2,
|
sampleCountZ: 2,
|
||||||
paintWeights: [
|
paintWeights: [0.2, 0.3, 0.1, 0, 0.5, 0, 0.1, 0.1, 0.1, 0.25, 0.25, 0.25]
|
||||||
0.2,
|
|
||||||
0.3,
|
|
||||||
0.1,
|
|
||||||
0,
|
|
||||||
0.5,
|
|
||||||
0,
|
|
||||||
0.1,
|
|
||||||
0.1,
|
|
||||||
0.1,
|
|
||||||
0.25,
|
|
||||||
0.25,
|
|
||||||
0.25
|
|
||||||
]
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const derivedMesh = buildTerrainDerivedMeshData(terrain);
|
const derivedMesh = buildTerrainDerivedMeshData(terrain);
|
||||||
@@ -318,11 +307,7 @@ describe("terrain mesh generation", () => {
|
|||||||
|
|
||||||
expect(chunk).toBeDefined();
|
expect(chunk).toBeDefined();
|
||||||
expect(chunk!.levels.map((level) => level.stride)).toEqual([
|
expect(chunk!.levels.map((level) => level.stride)).toEqual([
|
||||||
1,
|
1, 2, 4, 8, 16
|
||||||
2,
|
|
||||||
4,
|
|
||||||
8,
|
|
||||||
16
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const vertexCounts = chunk!.levels.map(
|
const vertexCounts = chunk!.levels.map(
|
||||||
@@ -362,11 +347,7 @@ describe("terrain mesh generation", () => {
|
|||||||
const [chunk] = buildTerrainLodMeshData(terrain).chunks;
|
const [chunk] = buildTerrainLodMeshData(terrain).chunks;
|
||||||
const [level] = chunk!.levels;
|
const [level] = chunk!.levels;
|
||||||
|
|
||||||
for (
|
for (let offset = 0; offset < level!.layerWeights.length; offset += 8) {
|
||||||
let offset = 0;
|
|
||||||
offset < level!.layerWeights.length;
|
|
||||||
offset += 8
|
|
||||||
) {
|
|
||||||
const sum =
|
const sum =
|
||||||
level!.layerWeights[offset]! +
|
level!.layerWeights[offset]! +
|
||||||
level!.layerWeights[offset + 1]! +
|
level!.layerWeights[offset + 1]! +
|
||||||
|
|||||||
Reference in New Issue
Block a user