auto-git:
[change] src/document/terrains.ts
This commit is contained in:
@@ -12,6 +12,12 @@ export interface TerrainFoliageMask {
|
||||
values: number[];
|
||||
}
|
||||
|
||||
export interface TerrainFoliageBlockerMask {
|
||||
resolutionX: number;
|
||||
resolutionZ: number;
|
||||
values: number[];
|
||||
}
|
||||
|
||||
export type TerrainFoliageMaskRegistry = Record<string, TerrainFoliageMask>;
|
||||
|
||||
export interface Terrain {
|
||||
@@ -29,6 +35,7 @@ export interface Terrain {
|
||||
layers: TerrainLayer[];
|
||||
paintWeights: number[];
|
||||
foliageMasks: TerrainFoliageMaskRegistry;
|
||||
foliageBlockerMask: TerrainFoliageBlockerMask;
|
||||
}
|
||||
|
||||
export interface TerrainHeightPatchEntry {
|
||||
@@ -298,6 +305,14 @@ export function createFlatTerrainFoliageMaskValues(
|
||||
);
|
||||
}
|
||||
|
||||
export function createFlatTerrainFoliageBlockerMaskValues(
|
||||
resolutionX: number,
|
||||
resolutionZ: number,
|
||||
value = 0
|
||||
): number[] {
|
||||
return createFlatTerrainFoliageMaskValues(resolutionX, resolutionZ, value);
|
||||
}
|
||||
|
||||
export function getTerrainSampleIndex(
|
||||
terrain: Pick<Terrain, "sampleCountX" | "sampleCountZ">,
|
||||
sampleX: number,
|
||||
@@ -463,6 +478,65 @@ export function cloneTerrainFoliageMask(
|
||||
return createTerrainFoliageMask(mask);
|
||||
}
|
||||
|
||||
export function createTerrainFoliageBlockerMask(options: {
|
||||
resolutionX: number;
|
||||
resolutionZ: number;
|
||||
values?: readonly number[];
|
||||
}): TerrainFoliageBlockerMask {
|
||||
const resolutionX = normalizeTerrainSampleCount(
|
||||
options.resolutionX,
|
||||
"Terrain foliage blocker mask resolutionX"
|
||||
);
|
||||
const resolutionZ = normalizeTerrainSampleCount(
|
||||
options.resolutionZ,
|
||||
"Terrain foliage blocker mask resolutionZ"
|
||||
);
|
||||
const expectedValueCount = resolutionX * resolutionZ;
|
||||
const values =
|
||||
options.values === undefined
|
||||
? createFlatTerrainFoliageBlockerMaskValues(resolutionX, resolutionZ)
|
||||
: [...options.values];
|
||||
|
||||
if (values.length !== expectedValueCount) {
|
||||
throw new Error(
|
||||
`Terrain foliage blocker mask values must contain exactly ${expectedValueCount} samples.`
|
||||
);
|
||||
}
|
||||
|
||||
for (let index = 0; index < values.length; index += 1) {
|
||||
const value = values[index];
|
||||
|
||||
if (!Number.isFinite(value)) {
|
||||
throw new Error(
|
||||
"Terrain foliage blocker mask values must remain finite."
|
||||
);
|
||||
}
|
||||
|
||||
values[index] = clamp(value, 0, 1);
|
||||
}
|
||||
|
||||
return {
|
||||
resolutionX,
|
||||
resolutionZ,
|
||||
values
|
||||
};
|
||||
}
|
||||
|
||||
export function createEmptyTerrainFoliageBlockerMask(
|
||||
terrain: Pick<Terrain, "sampleCountX" | "sampleCountZ">
|
||||
): TerrainFoliageBlockerMask {
|
||||
return createTerrainFoliageBlockerMask({
|
||||
resolutionX: terrain.sampleCountX,
|
||||
resolutionZ: terrain.sampleCountZ
|
||||
});
|
||||
}
|
||||
|
||||
export function cloneTerrainFoliageBlockerMask(
|
||||
mask: TerrainFoliageBlockerMask
|
||||
): TerrainFoliageBlockerMask {
|
||||
return createTerrainFoliageBlockerMask(mask);
|
||||
}
|
||||
|
||||
function normalizeTerrainFoliageMasks(
|
||||
sampleCountX: number,
|
||||
sampleCountZ: number,
|
||||
@@ -498,6 +572,31 @@ function normalizeTerrainFoliageMasks(
|
||||
return normalizedMasks;
|
||||
}
|
||||
|
||||
function normalizeTerrainFoliageBlockerMask(
|
||||
sampleCountX: number,
|
||||
sampleCountZ: number,
|
||||
foliageBlockerMask: TerrainFoliageBlockerMask | undefined
|
||||
): TerrainFoliageBlockerMask {
|
||||
const normalizedMask =
|
||||
foliageBlockerMask === undefined
|
||||
? createTerrainFoliageBlockerMask({
|
||||
resolutionX: sampleCountX,
|
||||
resolutionZ: sampleCountZ
|
||||
})
|
||||
: createTerrainFoliageBlockerMask(foliageBlockerMask);
|
||||
|
||||
if (
|
||||
normalizedMask.resolutionX !== sampleCountX ||
|
||||
normalizedMask.resolutionZ !== sampleCountZ
|
||||
) {
|
||||
throw new Error(
|
||||
"Terrain foliage blocker mask resolution must match the terrain sample grid."
|
||||
);
|
||||
}
|
||||
|
||||
return normalizedMask;
|
||||
}
|
||||
|
||||
export function cloneTerrainFoliageMasks(
|
||||
foliageMasks: TerrainFoliageMaskRegistry
|
||||
): TerrainFoliageMaskRegistry {
|
||||
@@ -857,7 +956,7 @@ function getStoredTerrainPaintWeightAtSample(
|
||||
}
|
||||
|
||||
export function getTerrainFoliageMaskSampleIndex(
|
||||
mask: Pick<TerrainFoliageMask, "resolutionX" | "resolutionZ">,
|
||||
mask: Pick<TerrainFoliageMask | TerrainFoliageBlockerMask, "resolutionX" | "resolutionZ">,
|
||||
sampleX: number,
|
||||
sampleZ: number
|
||||
): number {
|
||||
@@ -890,6 +989,16 @@ export function getTerrainFoliageMaskValueAtSample(
|
||||
);
|
||||
}
|
||||
|
||||
export function getTerrainFoliageBlockerMaskValueAtSample(
|
||||
mask: TerrainFoliageBlockerMask,
|
||||
sampleX: number,
|
||||
sampleZ: number
|
||||
): number {
|
||||
return (
|
||||
mask.values[getTerrainFoliageMaskSampleIndex(mask, sampleX, sampleZ)] ?? 0
|
||||
);
|
||||
}
|
||||
|
||||
export function getTerrainFoliageMask(
|
||||
terrain: Pick<Terrain, "foliageMasks">,
|
||||
layerId: string
|
||||
@@ -922,6 +1031,12 @@ export function isTerrainFoliageMaskEmpty(
|
||||
return mask.values.every((value) => value === 0);
|
||||
}
|
||||
|
||||
export function isTerrainFoliageBlockerMaskEmpty(
|
||||
mask: TerrainFoliageBlockerMask
|
||||
): boolean {
|
||||
return mask.values.every((value) => value === 0);
|
||||
}
|
||||
|
||||
function sampleTerrainPaintWeightAtGridCoordinate(
|
||||
terrain: Terrain,
|
||||
sampleX: number,
|
||||
@@ -969,7 +1084,7 @@ function sampleTerrainPaintWeightAtGridCoordinate(
|
||||
}
|
||||
|
||||
function sampleTerrainFoliageMaskAtGridCoordinate(
|
||||
mask: TerrainFoliageMask,
|
||||
mask: TerrainFoliageMask | TerrainFoliageBlockerMask,
|
||||
sampleX: number,
|
||||
sampleZ: number
|
||||
): number {
|
||||
@@ -982,22 +1097,22 @@ function sampleTerrainFoliageMaskAtGridCoordinate(
|
||||
const blendX = clampedSampleX - minSampleX;
|
||||
const blendZ = clampedSampleZ - minSampleZ;
|
||||
const value00 = getTerrainFoliageMaskValueAtSample(
|
||||
mask,
|
||||
mask as TerrainFoliageMask,
|
||||
minSampleX,
|
||||
minSampleZ
|
||||
);
|
||||
const value10 = getTerrainFoliageMaskValueAtSample(
|
||||
mask,
|
||||
mask as TerrainFoliageMask,
|
||||
maxSampleX,
|
||||
minSampleZ
|
||||
);
|
||||
const value01 = getTerrainFoliageMaskValueAtSample(
|
||||
mask,
|
||||
mask as TerrainFoliageMask,
|
||||
minSampleX,
|
||||
maxSampleZ
|
||||
);
|
||||
const value11 = getTerrainFoliageMaskValueAtSample(
|
||||
mask,
|
||||
mask as TerrainFoliageMask,
|
||||
maxSampleX,
|
||||
maxSampleZ
|
||||
);
|
||||
@@ -1009,6 +1124,14 @@ function sampleTerrainFoliageMaskAtGridCoordinate(
|
||||
);
|
||||
}
|
||||
|
||||
function sampleTerrainFoliageBlockerMaskAtGridCoordinate(
|
||||
mask: TerrainFoliageBlockerMask,
|
||||
sampleX: number,
|
||||
sampleZ: number
|
||||
): number {
|
||||
return sampleTerrainFoliageMaskAtGridCoordinate(mask, sampleX, sampleZ);
|
||||
}
|
||||
|
||||
export function sampleTerrainFoliageMaskAtLocalPosition(
|
||||
terrain: Terrain,
|
||||
layerId: string,
|
||||
@@ -1061,6 +1184,49 @@ export function sampleTerrainFoliageMaskAtWorldPosition(
|
||||
);
|
||||
}
|
||||
|
||||
export function sampleTerrainFoliageBlockerMaskAtLocalPosition(
|
||||
terrain: Terrain,
|
||||
localX: number,
|
||||
localZ: number,
|
||||
clampToBounds = false
|
||||
): number | null {
|
||||
const sampleSpaceX = localX / terrain.cellSize;
|
||||
const sampleSpaceZ = localZ / terrain.cellSize;
|
||||
const maxSampleX = terrain.sampleCountX - 1;
|
||||
const maxSampleZ = terrain.sampleCountZ - 1;
|
||||
|
||||
if (!clampToBounds) {
|
||||
if (
|
||||
sampleSpaceX < 0 ||
|
||||
sampleSpaceX > maxSampleX ||
|
||||
sampleSpaceZ < 0 ||
|
||||
sampleSpaceZ > maxSampleZ
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return sampleTerrainFoliageBlockerMaskAtGridCoordinate(
|
||||
terrain.foliageBlockerMask,
|
||||
sampleSpaceX,
|
||||
sampleSpaceZ
|
||||
);
|
||||
}
|
||||
|
||||
export function sampleTerrainFoliageBlockerMaskAtWorldPosition(
|
||||
terrain: Terrain,
|
||||
worldX: number,
|
||||
worldZ: number,
|
||||
clampToBounds = false
|
||||
): number | null {
|
||||
return sampleTerrainFoliageBlockerMaskAtLocalPosition(
|
||||
terrain,
|
||||
worldX - terrain.position.x,
|
||||
worldZ - terrain.position.z,
|
||||
clampToBounds
|
||||
);
|
||||
}
|
||||
|
||||
function createTerrainPositionFromCenter(
|
||||
center: Vec3,
|
||||
sampleCountX: number,
|
||||
@@ -1192,6 +1358,41 @@ function createResampledTerrainFoliageMasks(
|
||||
);
|
||||
}
|
||||
|
||||
function createResampledTerrainFoliageBlockerMask(
|
||||
terrain: Terrain,
|
||||
sampleCountX: number,
|
||||
sampleCountZ: number
|
||||
): TerrainFoliageBlockerMask {
|
||||
const values = new Array<number>(sampleCountX * sampleCountZ);
|
||||
|
||||
for (let sampleZ = 0; sampleZ < sampleCountZ; sampleZ += 1) {
|
||||
const normalizedSampleZ =
|
||||
sampleCountZ === 1 ? 0 : sampleZ / (sampleCountZ - 1);
|
||||
const sourceSampleZ =
|
||||
normalizedSampleZ * (terrain.foliageBlockerMask.resolutionZ - 1);
|
||||
|
||||
for (let sampleX = 0; sampleX < sampleCountX; sampleX += 1) {
|
||||
const normalizedSampleX =
|
||||
sampleCountX === 1 ? 0 : sampleX / (sampleCountX - 1);
|
||||
const sourceSampleX =
|
||||
normalizedSampleX * (terrain.foliageBlockerMask.resolutionX - 1);
|
||||
|
||||
values[sampleZ * sampleCountX + sampleX] =
|
||||
sampleTerrainFoliageBlockerMaskAtGridCoordinate(
|
||||
terrain.foliageBlockerMask,
|
||||
sourceSampleX,
|
||||
sourceSampleZ
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return createTerrainFoliageBlockerMask({
|
||||
resolutionX: sampleCountX,
|
||||
resolutionZ: sampleCountZ,
|
||||
values
|
||||
});
|
||||
}
|
||||
|
||||
export function resizeTerrainGrid(
|
||||
terrain: Terrain,
|
||||
options: Pick<Terrain, "sampleCountX" | "sampleCountZ" | "cellSize"> & {
|
||||
@@ -1233,6 +1434,11 @@ export function resizeTerrainGrid(
|
||||
terrain,
|
||||
sampleCountX,
|
||||
sampleCountZ
|
||||
),
|
||||
foliageBlockerMask: createResampledTerrainFoliageBlockerMask(
|
||||
terrain,
|
||||
sampleCountX,
|
||||
sampleCountZ
|
||||
)
|
||||
});
|
||||
}
|
||||
@@ -1266,6 +1472,7 @@ export function createTerrain(
|
||||
| "layers"
|
||||
| "paintWeights"
|
||||
| "foliageMasks"
|
||||
| "foliageBlockerMask"
|
||||
>
|
||||
> = {}
|
||||
): Terrain {
|
||||
@@ -1299,6 +1506,11 @@ export function createTerrain(
|
||||
sampleCountZ,
|
||||
overrides.foliageMasks
|
||||
);
|
||||
const foliageBlockerMask = normalizeTerrainFoliageBlockerMask(
|
||||
sampleCountX,
|
||||
sampleCountZ,
|
||||
overrides.foliageBlockerMask
|
||||
);
|
||||
const visible = overrides.visible ?? DEFAULT_TERRAIN_VISIBLE;
|
||||
const enabled = overrides.enabled ?? DEFAULT_TERRAIN_ENABLED;
|
||||
const collisionEnabled = normalizeTerrainCollisionEnabled(
|
||||
@@ -1339,7 +1551,8 @@ export function createTerrain(
|
||||
heights,
|
||||
layers,
|
||||
paintWeights,
|
||||
foliageMasks
|
||||
foliageMasks,
|
||||
foliageBlockerMask
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1382,7 +1595,16 @@ export function areTerrainsEqual(left: Terrain, right: Terrain): boolean {
|
||||
leftMask.values.length === rightMask.values.length &&
|
||||
leftMask.values.every((value, index) => value === rightMask.values[index])
|
||||
);
|
||||
})
|
||||
}) &&
|
||||
left.foliageBlockerMask.resolutionX ===
|
||||
right.foliageBlockerMask.resolutionX &&
|
||||
left.foliageBlockerMask.resolutionZ ===
|
||||
right.foliageBlockerMask.resolutionZ &&
|
||||
left.foliageBlockerMask.values.length ===
|
||||
right.foliageBlockerMask.values.length &&
|
||||
left.foliageBlockerMask.values.every(
|
||||
(value, index) => value === right.foliageBlockerMask.values[index]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user