auto-git:
[change] src/document/terrains.ts
This commit is contained in:
@@ -12,6 +12,12 @@ export interface TerrainFoliageMask {
|
|||||||
values: number[];
|
values: number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TerrainFoliageBlockerMask {
|
||||||
|
resolutionX: number;
|
||||||
|
resolutionZ: number;
|
||||||
|
values: number[];
|
||||||
|
}
|
||||||
|
|
||||||
export type TerrainFoliageMaskRegistry = Record<string, TerrainFoliageMask>;
|
export type TerrainFoliageMaskRegistry = Record<string, TerrainFoliageMask>;
|
||||||
|
|
||||||
export interface Terrain {
|
export interface Terrain {
|
||||||
@@ -29,6 +35,7 @@ export interface Terrain {
|
|||||||
layers: TerrainLayer[];
|
layers: TerrainLayer[];
|
||||||
paintWeights: number[];
|
paintWeights: number[];
|
||||||
foliageMasks: TerrainFoliageMaskRegistry;
|
foliageMasks: TerrainFoliageMaskRegistry;
|
||||||
|
foliageBlockerMask: TerrainFoliageBlockerMask;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TerrainHeightPatchEntry {
|
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(
|
export function getTerrainSampleIndex(
|
||||||
terrain: Pick<Terrain, "sampleCountX" | "sampleCountZ">,
|
terrain: Pick<Terrain, "sampleCountX" | "sampleCountZ">,
|
||||||
sampleX: number,
|
sampleX: number,
|
||||||
@@ -463,6 +478,65 @@ export function cloneTerrainFoliageMask(
|
|||||||
return createTerrainFoliageMask(mask);
|
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(
|
function normalizeTerrainFoliageMasks(
|
||||||
sampleCountX: number,
|
sampleCountX: number,
|
||||||
sampleCountZ: number,
|
sampleCountZ: number,
|
||||||
@@ -498,6 +572,31 @@ function normalizeTerrainFoliageMasks(
|
|||||||
return normalizedMasks;
|
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(
|
export function cloneTerrainFoliageMasks(
|
||||||
foliageMasks: TerrainFoliageMaskRegistry
|
foliageMasks: TerrainFoliageMaskRegistry
|
||||||
): TerrainFoliageMaskRegistry {
|
): TerrainFoliageMaskRegistry {
|
||||||
@@ -857,7 +956,7 @@ function getStoredTerrainPaintWeightAtSample(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getTerrainFoliageMaskSampleIndex(
|
export function getTerrainFoliageMaskSampleIndex(
|
||||||
mask: Pick<TerrainFoliageMask, "resolutionX" | "resolutionZ">,
|
mask: Pick<TerrainFoliageMask | TerrainFoliageBlockerMask, "resolutionX" | "resolutionZ">,
|
||||||
sampleX: number,
|
sampleX: number,
|
||||||
sampleZ: number
|
sampleZ: number
|
||||||
): 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(
|
export function getTerrainFoliageMask(
|
||||||
terrain: Pick<Terrain, "foliageMasks">,
|
terrain: Pick<Terrain, "foliageMasks">,
|
||||||
layerId: string
|
layerId: string
|
||||||
@@ -922,6 +1031,12 @@ export function isTerrainFoliageMaskEmpty(
|
|||||||
return mask.values.every((value) => value === 0);
|
return mask.values.every((value) => value === 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isTerrainFoliageBlockerMaskEmpty(
|
||||||
|
mask: TerrainFoliageBlockerMask
|
||||||
|
): boolean {
|
||||||
|
return mask.values.every((value) => value === 0);
|
||||||
|
}
|
||||||
|
|
||||||
function sampleTerrainPaintWeightAtGridCoordinate(
|
function sampleTerrainPaintWeightAtGridCoordinate(
|
||||||
terrain: Terrain,
|
terrain: Terrain,
|
||||||
sampleX: number,
|
sampleX: number,
|
||||||
@@ -969,7 +1084,7 @@ function sampleTerrainPaintWeightAtGridCoordinate(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function sampleTerrainFoliageMaskAtGridCoordinate(
|
function sampleTerrainFoliageMaskAtGridCoordinate(
|
||||||
mask: TerrainFoliageMask,
|
mask: TerrainFoliageMask | TerrainFoliageBlockerMask,
|
||||||
sampleX: number,
|
sampleX: number,
|
||||||
sampleZ: number
|
sampleZ: number
|
||||||
): number {
|
): number {
|
||||||
@@ -982,22 +1097,22 @@ function sampleTerrainFoliageMaskAtGridCoordinate(
|
|||||||
const blendX = clampedSampleX - minSampleX;
|
const blendX = clampedSampleX - minSampleX;
|
||||||
const blendZ = clampedSampleZ - minSampleZ;
|
const blendZ = clampedSampleZ - minSampleZ;
|
||||||
const value00 = getTerrainFoliageMaskValueAtSample(
|
const value00 = getTerrainFoliageMaskValueAtSample(
|
||||||
mask,
|
mask as TerrainFoliageMask,
|
||||||
minSampleX,
|
minSampleX,
|
||||||
minSampleZ
|
minSampleZ
|
||||||
);
|
);
|
||||||
const value10 = getTerrainFoliageMaskValueAtSample(
|
const value10 = getTerrainFoliageMaskValueAtSample(
|
||||||
mask,
|
mask as TerrainFoliageMask,
|
||||||
maxSampleX,
|
maxSampleX,
|
||||||
minSampleZ
|
minSampleZ
|
||||||
);
|
);
|
||||||
const value01 = getTerrainFoliageMaskValueAtSample(
|
const value01 = getTerrainFoliageMaskValueAtSample(
|
||||||
mask,
|
mask as TerrainFoliageMask,
|
||||||
minSampleX,
|
minSampleX,
|
||||||
maxSampleZ
|
maxSampleZ
|
||||||
);
|
);
|
||||||
const value11 = getTerrainFoliageMaskValueAtSample(
|
const value11 = getTerrainFoliageMaskValueAtSample(
|
||||||
mask,
|
mask as TerrainFoliageMask,
|
||||||
maxSampleX,
|
maxSampleX,
|
||||||
maxSampleZ
|
maxSampleZ
|
||||||
);
|
);
|
||||||
@@ -1009,6 +1124,14 @@ function sampleTerrainFoliageMaskAtGridCoordinate(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sampleTerrainFoliageBlockerMaskAtGridCoordinate(
|
||||||
|
mask: TerrainFoliageBlockerMask,
|
||||||
|
sampleX: number,
|
||||||
|
sampleZ: number
|
||||||
|
): number {
|
||||||
|
return sampleTerrainFoliageMaskAtGridCoordinate(mask, sampleX, sampleZ);
|
||||||
|
}
|
||||||
|
|
||||||
export function sampleTerrainFoliageMaskAtLocalPosition(
|
export function sampleTerrainFoliageMaskAtLocalPosition(
|
||||||
terrain: Terrain,
|
terrain: Terrain,
|
||||||
layerId: string,
|
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(
|
function createTerrainPositionFromCenter(
|
||||||
center: Vec3,
|
center: Vec3,
|
||||||
sampleCountX: number,
|
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(
|
export function resizeTerrainGrid(
|
||||||
terrain: Terrain,
|
terrain: Terrain,
|
||||||
options: Pick<Terrain, "sampleCountX" | "sampleCountZ" | "cellSize"> & {
|
options: Pick<Terrain, "sampleCountX" | "sampleCountZ" | "cellSize"> & {
|
||||||
@@ -1233,6 +1434,11 @@ export function resizeTerrainGrid(
|
|||||||
terrain,
|
terrain,
|
||||||
sampleCountX,
|
sampleCountX,
|
||||||
sampleCountZ
|
sampleCountZ
|
||||||
|
),
|
||||||
|
foliageBlockerMask: createResampledTerrainFoliageBlockerMask(
|
||||||
|
terrain,
|
||||||
|
sampleCountX,
|
||||||
|
sampleCountZ
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1266,6 +1472,7 @@ export function createTerrain(
|
|||||||
| "layers"
|
| "layers"
|
||||||
| "paintWeights"
|
| "paintWeights"
|
||||||
| "foliageMasks"
|
| "foliageMasks"
|
||||||
|
| "foliageBlockerMask"
|
||||||
>
|
>
|
||||||
> = {}
|
> = {}
|
||||||
): Terrain {
|
): Terrain {
|
||||||
@@ -1299,6 +1506,11 @@ export function createTerrain(
|
|||||||
sampleCountZ,
|
sampleCountZ,
|
||||||
overrides.foliageMasks
|
overrides.foliageMasks
|
||||||
);
|
);
|
||||||
|
const foliageBlockerMask = normalizeTerrainFoliageBlockerMask(
|
||||||
|
sampleCountX,
|
||||||
|
sampleCountZ,
|
||||||
|
overrides.foliageBlockerMask
|
||||||
|
);
|
||||||
const visible = overrides.visible ?? DEFAULT_TERRAIN_VISIBLE;
|
const visible = overrides.visible ?? DEFAULT_TERRAIN_VISIBLE;
|
||||||
const enabled = overrides.enabled ?? DEFAULT_TERRAIN_ENABLED;
|
const enabled = overrides.enabled ?? DEFAULT_TERRAIN_ENABLED;
|
||||||
const collisionEnabled = normalizeTerrainCollisionEnabled(
|
const collisionEnabled = normalizeTerrainCollisionEnabled(
|
||||||
@@ -1339,7 +1551,8 @@ export function createTerrain(
|
|||||||
heights,
|
heights,
|
||||||
layers,
|
layers,
|
||||||
paintWeights,
|
paintWeights,
|
||||||
foliageMasks
|
foliageMasks,
|
||||||
|
foliageBlockerMask
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1382,7 +1595,16 @@ export function areTerrainsEqual(left: Terrain, right: Terrain): boolean {
|
|||||||
leftMask.values.length === rightMask.values.length &&
|
leftMask.values.length === rightMask.values.length &&
|
||||||
leftMask.values.every((value, index) => value === rightMask.values[index])
|
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