Implement and validate foliage blocker masks across document systems
This commit is contained in:
@@ -3010,6 +3010,64 @@ function validateTerrain(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const blockerMask = terrain.foliageBlockerMask;
|
||||||
|
const blockerMaskPath = `${path}.foliageBlockerMask`;
|
||||||
|
|
||||||
|
if (blockerMask.resolutionX !== terrain.sampleCountX) {
|
||||||
|
diagnostics.push(
|
||||||
|
createDiagnostic(
|
||||||
|
"error",
|
||||||
|
"invalid-terrain-foliage-blocker-mask-resolution-x",
|
||||||
|
"Terrain foliage blocker mask resolutionX must match terrain sampleCountX.",
|
||||||
|
`${blockerMaskPath}.resolutionX`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blockerMask.resolutionZ !== terrain.sampleCountZ) {
|
||||||
|
diagnostics.push(
|
||||||
|
createDiagnostic(
|
||||||
|
"error",
|
||||||
|
"invalid-terrain-foliage-blocker-mask-resolution-z",
|
||||||
|
"Terrain foliage blocker mask resolutionZ must match terrain sampleCountZ.",
|
||||||
|
`${blockerMaskPath}.resolutionZ`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const expectedBlockerMaskValueCount =
|
||||||
|
blockerMask.resolutionX * blockerMask.resolutionZ;
|
||||||
|
|
||||||
|
if (blockerMask.values.length !== expectedBlockerMaskValueCount) {
|
||||||
|
diagnostics.push(
|
||||||
|
createDiagnostic(
|
||||||
|
"error",
|
||||||
|
"invalid-terrain-foliage-blocker-mask-value-count",
|
||||||
|
`Terrain foliage blocker mask values must contain exactly ${expectedBlockerMaskValueCount} samples.`,
|
||||||
|
`${blockerMaskPath}.values`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.terrainSampleValues !== "skip") {
|
||||||
|
for (let index = 0; index < blockerMask.values.length; index += 1) {
|
||||||
|
const maskValue = blockerMask.values[index];
|
||||||
|
|
||||||
|
if (isFiniteNumber(maskValue) && maskValue >= 0 && maskValue <= 1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
diagnostics.push(
|
||||||
|
createDiagnostic(
|
||||||
|
"error",
|
||||||
|
"invalid-terrain-foliage-blocker-mask-value",
|
||||||
|
"Terrain foliage blocker mask values must remain finite values between 0 and 1.",
|
||||||
|
`${blockerMaskPath}.values.${index}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateFoliagePrototype(
|
function validateFoliagePrototype(
|
||||||
|
|||||||
@@ -956,7 +956,10 @@ function getStoredTerrainPaintWeightAtSample(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getTerrainFoliageMaskSampleIndex(
|
export function getTerrainFoliageMaskSampleIndex(
|
||||||
mask: Pick<TerrainFoliageMask | TerrainFoliageBlockerMask, "resolutionX" | "resolutionZ">,
|
mask: Pick<
|
||||||
|
TerrainFoliageMask | TerrainFoliageBlockerMask,
|
||||||
|
"resolutionX" | "resolutionZ"
|
||||||
|
>,
|
||||||
sampleX: number,
|
sampleX: number,
|
||||||
sampleZ: number
|
sampleZ: number
|
||||||
): number {
|
): number {
|
||||||
@@ -1096,26 +1099,12 @@ function sampleTerrainFoliageMaskAtGridCoordinate(
|
|||||||
const maxSampleZ = Math.min(mask.resolutionZ - 1, minSampleZ + 1);
|
const maxSampleZ = Math.min(mask.resolutionZ - 1, minSampleZ + 1);
|
||||||
const blendX = clampedSampleX - minSampleX;
|
const blendX = clampedSampleX - minSampleX;
|
||||||
const blendZ = clampedSampleZ - minSampleZ;
|
const blendZ = clampedSampleZ - minSampleZ;
|
||||||
const value00 = getTerrainFoliageMaskValueAtSample(
|
const readMaskValue = (x: number, z: number) =>
|
||||||
mask as TerrainFoliageMask,
|
mask.values[getTerrainFoliageMaskSampleIndex(mask, x, z)] ?? 0;
|
||||||
minSampleX,
|
const value00 = readMaskValue(minSampleX, minSampleZ);
|
||||||
minSampleZ
|
const value10 = readMaskValue(maxSampleX, minSampleZ);
|
||||||
);
|
const value01 = readMaskValue(minSampleX, maxSampleZ);
|
||||||
const value10 = getTerrainFoliageMaskValueAtSample(
|
const value11 = readMaskValue(maxSampleX, maxSampleZ);
|
||||||
mask as TerrainFoliageMask,
|
|
||||||
maxSampleX,
|
|
||||||
minSampleZ
|
|
||||||
);
|
|
||||||
const value01 = getTerrainFoliageMaskValueAtSample(
|
|
||||||
mask as TerrainFoliageMask,
|
|
||||||
minSampleX,
|
|
||||||
maxSampleZ
|
|
||||||
);
|
|
||||||
const value11 = getTerrainFoliageMaskValueAtSample(
|
|
||||||
mask as TerrainFoliageMask,
|
|
||||||
maxSampleX,
|
|
||||||
maxSampleZ
|
|
||||||
);
|
|
||||||
|
|
||||||
return lerp(
|
return lerp(
|
||||||
lerp(value00, value10, blendX),
|
lerp(value00, value10, blendX),
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
getTerrainFootprintWidth,
|
getTerrainFootprintWidth,
|
||||||
getTerrainHeightAtSample,
|
getTerrainHeightAtSample,
|
||||||
isTerrainFoliageMaskEmpty,
|
isTerrainFoliageMaskEmpty,
|
||||||
|
sampleTerrainFoliageBlockerMaskAtWorldPosition,
|
||||||
sampleTerrainFoliageMaskAtWorldPosition,
|
sampleTerrainFoliageMaskAtWorldPosition,
|
||||||
type Terrain
|
type Terrain
|
||||||
} from "../document/terrains";
|
} from "../document/terrains";
|
||||||
@@ -91,6 +92,7 @@ interface WeightedFoliagePrototypeSet {
|
|||||||
|
|
||||||
export const DEFAULT_FOLIAGE_SCATTER_CHUNK_SIZE_METERS = 16;
|
export const DEFAULT_FOLIAGE_SCATTER_CHUNK_SIZE_METERS = 16;
|
||||||
export const DEFAULT_MAX_FOLIAGE_SCATTER_INSTANCES_PER_CHUNK = 512;
|
export const DEFAULT_MAX_FOLIAGE_SCATTER_INSTANCES_PER_CHUNK = 512;
|
||||||
|
export const FOLIAGE_BLOCKER_MASK_THRESHOLD = 0.1;
|
||||||
|
|
||||||
const HASH_OFFSET_BASIS = 2166136261;
|
const HASH_OFFSET_BASIS = 2166136261;
|
||||||
const HASH_PRIME = 16777619;
|
const HASH_PRIME = 16777619;
|
||||||
@@ -512,6 +514,18 @@ function generateChunkInstances(options: {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const blockerValue =
|
||||||
|
sampleTerrainFoliageBlockerMaskAtWorldPosition(
|
||||||
|
terrain,
|
||||||
|
worldX,
|
||||||
|
worldZ,
|
||||||
|
false
|
||||||
|
) ?? 0;
|
||||||
|
|
||||||
|
if (blockerValue > FOLIAGE_BLOCKER_MASK_THRESHOLD) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const noiseValue = sampleValueNoise(
|
const noiseValue = sampleValueNoise(
|
||||||
worldX,
|
worldX,
|
||||||
worldZ,
|
worldZ,
|
||||||
|
|||||||
@@ -391,6 +391,7 @@ interface ActiveTerrainBrushStroke {
|
|||||||
heightSampleIndices: Set<number>;
|
heightSampleIndices: Set<number>;
|
||||||
paintWeightIndices: Set<number>;
|
paintWeightIndices: Set<number>;
|
||||||
foliageMaskValueKeys: Set<string>;
|
foliageMaskValueKeys: Set<string>;
|
||||||
|
foliageBlockerMaskValueIndices: Set<number>;
|
||||||
referenceHeight: number | null;
|
referenceHeight: number | null;
|
||||||
lastAppliedPoint: {
|
lastAppliedPoint: {
|
||||||
x: number;
|
x: number;
|
||||||
@@ -498,6 +499,8 @@ const TERRAIN_BRUSH_PREVIEW_FLATTEN_COLOR = 0xf1d37d;
|
|||||||
const TERRAIN_BRUSH_PREVIEW_PAINT_COLOR = 0x8eb9ff;
|
const TERRAIN_BRUSH_PREVIEW_PAINT_COLOR = 0x8eb9ff;
|
||||||
const TERRAIN_BRUSH_PREVIEW_FOLIAGE_PAINT_COLOR = 0x65d36e;
|
const TERRAIN_BRUSH_PREVIEW_FOLIAGE_PAINT_COLOR = 0x65d36e;
|
||||||
const TERRAIN_BRUSH_PREVIEW_FOLIAGE_ERASE_COLOR = 0xf0a853;
|
const TERRAIN_BRUSH_PREVIEW_FOLIAGE_ERASE_COLOR = 0xf0a853;
|
||||||
|
const TERRAIN_BRUSH_PREVIEW_FOLIAGE_BLOCKER_PAINT_COLOR = 0xdf5e77;
|
||||||
|
const TERRAIN_BRUSH_PREVIEW_FOLIAGE_BLOCKER_ERASE_COLOR = 0x5ec6df;
|
||||||
const TERRAIN_BRUSH_PREVIEW_OFFSET = 0.05;
|
const TERRAIN_BRUSH_PREVIEW_OFFSET = 0.05;
|
||||||
const BOX_CREATE_PREVIEW_FILL = 0x89b6ff;
|
const BOX_CREATE_PREVIEW_FILL = 0x89b6ff;
|
||||||
const BOX_CREATE_PREVIEW_EDGE = 0xf3be8f;
|
const BOX_CREATE_PREVIEW_EDGE = 0xf3be8f;
|
||||||
|
|||||||
Reference in New Issue
Block a user