Add utilities for managing terrain layers (add, remove, and check existence by material ID)
This commit is contained in:
@@ -1618,6 +1618,152 @@ export function cloneTerrain(terrain: Terrain): Terrain {
|
|||||||
return createTerrain(terrain);
|
return createTerrain(terrain);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function findTerrainLayerIndexByMaterialId(
|
||||||
|
terrain: Pick<Terrain, "layers">,
|
||||||
|
materialId: string | null
|
||||||
|
): number {
|
||||||
|
return terrain.layers.findIndex((layer) => layer.materialId === materialId);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createTerrainWithAddedLayer(
|
||||||
|
terrain: Terrain,
|
||||||
|
materialId: string | null = null
|
||||||
|
): Terrain {
|
||||||
|
if (terrain.layers.length >= MAX_TERRAIN_LAYER_COUNT) {
|
||||||
|
throw new Error(
|
||||||
|
`Terrain already has the maximum of ${MAX_TERRAIN_LAYER_COUNT} material layers.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldStoredWeightCount = getTerrainStoredPaintWeightCount(
|
||||||
|
terrain.layers.length
|
||||||
|
);
|
||||||
|
const nextLayers = [
|
||||||
|
...cloneTerrainLayers(terrain.layers),
|
||||||
|
{
|
||||||
|
materialId: normalizeTerrainLayerMaterialId(
|
||||||
|
materialId,
|
||||||
|
`Terrain layer ${terrain.layers.length}.materialId`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const nextStoredWeightCount = getTerrainStoredPaintWeightCount(
|
||||||
|
nextLayers.length
|
||||||
|
);
|
||||||
|
const sampleCount = terrain.sampleCountX * terrain.sampleCountZ;
|
||||||
|
const nextPaintWeights = new Array<number>(
|
||||||
|
sampleCount * nextStoredWeightCount
|
||||||
|
).fill(0);
|
||||||
|
|
||||||
|
for (let sampleIndex = 0; sampleIndex < sampleCount; sampleIndex += 1) {
|
||||||
|
const oldOffset = sampleIndex * oldStoredWeightCount;
|
||||||
|
const nextOffset = sampleIndex * nextStoredWeightCount;
|
||||||
|
|
||||||
|
for (
|
||||||
|
let layerOffset = 0;
|
||||||
|
layerOffset < oldStoredWeightCount;
|
||||||
|
layerOffset += 1
|
||||||
|
) {
|
||||||
|
nextPaintWeights[nextOffset + layerOffset] =
|
||||||
|
terrain.paintWeights[oldOffset + layerOffset] ?? 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return createTerrain({
|
||||||
|
...terrain,
|
||||||
|
layers: nextLayers,
|
||||||
|
paintWeights: nextPaintWeights
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createTerrainWithRemovedLayer(
|
||||||
|
terrain: Terrain,
|
||||||
|
layerIndex: number
|
||||||
|
): Terrain {
|
||||||
|
if (
|
||||||
|
!Number.isInteger(layerIndex) ||
|
||||||
|
layerIndex < 0 ||
|
||||||
|
layerIndex >= terrain.layers.length
|
||||||
|
) {
|
||||||
|
throw new Error(`Terrain layer index ${layerIndex} is out of range.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (layerIndex === 0) {
|
||||||
|
throw new Error("Terrain base layer cannot be removed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (terrain.layers.length <= MIN_TERRAIN_LAYER_COUNT) {
|
||||||
|
throw new Error("Terrain must keep at least one material layer.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldStoredWeightCount = getTerrainStoredPaintWeightCount(
|
||||||
|
terrain.layers.length
|
||||||
|
);
|
||||||
|
const nextLayers = cloneTerrainLayers(terrain.layers).filter(
|
||||||
|
(_, currentLayerIndex) => currentLayerIndex !== layerIndex
|
||||||
|
);
|
||||||
|
const nextStoredWeightCount = getTerrainStoredPaintWeightCount(
|
||||||
|
nextLayers.length
|
||||||
|
);
|
||||||
|
const sampleCount = terrain.sampleCountX * terrain.sampleCountZ;
|
||||||
|
const nextPaintWeights = new Array<number>(
|
||||||
|
sampleCount * nextStoredWeightCount
|
||||||
|
).fill(0);
|
||||||
|
const removedLayerOffset = layerIndex - 1;
|
||||||
|
|
||||||
|
for (let sampleIndex = 0; sampleIndex < sampleCount; sampleIndex += 1) {
|
||||||
|
const oldOffset = sampleIndex * oldStoredWeightCount;
|
||||||
|
const nextOffset = sampleIndex * nextStoredWeightCount;
|
||||||
|
let nextLayerOffset = 0;
|
||||||
|
|
||||||
|
for (
|
||||||
|
let oldLayerOffset = 0;
|
||||||
|
oldLayerOffset < oldStoredWeightCount;
|
||||||
|
oldLayerOffset += 1
|
||||||
|
) {
|
||||||
|
if (oldLayerOffset === removedLayerOffset) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextPaintWeights[nextOffset + nextLayerOffset] =
|
||||||
|
terrain.paintWeights[oldOffset + oldLayerOffset] ?? 0;
|
||||||
|
nextLayerOffset += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return createTerrain({
|
||||||
|
...terrain,
|
||||||
|
layers: nextLayers,
|
||||||
|
paintWeights: nextPaintWeights
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ensureTerrainMaterialLayer(
|
||||||
|
terrain: Terrain,
|
||||||
|
materialId: string
|
||||||
|
): { terrain: Terrain; layerIndex: number; added: boolean } {
|
||||||
|
const existingLayerIndex = findTerrainLayerIndexByMaterialId(
|
||||||
|
terrain,
|
||||||
|
materialId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existingLayerIndex !== -1) {
|
||||||
|
return {
|
||||||
|
terrain,
|
||||||
|
layerIndex: existingLayerIndex,
|
||||||
|
added: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextTerrain = createTerrainWithAddedLayer(terrain, materialId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
terrain: nextTerrain,
|
||||||
|
layerIndex: nextTerrain.layers.length - 1,
|
||||||
|
added: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function areTerrainsEqual(left: Terrain, right: Terrain): boolean {
|
export function areTerrainsEqual(left: Terrain, right: Terrain): boolean {
|
||||||
return (
|
return (
|
||||||
left.id === right.id &&
|
left.id === right.id &&
|
||||||
|
|||||||
Reference in New Issue
Block a user