auto-git:

[change] src/geometry/terrain-brush.ts
This commit is contained in:
2026-05-01 17:56:39 +02:00
parent bee39c70b3
commit 5f01182675

View File

@@ -30,6 +30,8 @@ export interface TerrainBrushDirtySampleBounds {
export interface TerrainBrushStampMutationResult { export interface TerrainBrushStampMutationResult {
changed: boolean; changed: boolean;
dirtyBounds: TerrainBrushDirtySampleBounds | null; dirtyBounds: TerrainBrushDirtySampleBounds | null;
heightSampleIndices: number[];
paintWeightIndices: number[];
} }
interface TerrainSmoothHeightSource { interface TerrainSmoothHeightSource {
@@ -243,11 +245,27 @@ function setTerrainSamplePaintWeights(
sampleX: number, sampleX: number,
sampleZ: number, sampleZ: number,
weights: readonly [number, number, number, number] weights: readonly [number, number, number, number]
) { ): number[] {
const offset = getTerrainPaintWeightSampleOffset(terrain, sampleX, sampleZ); const offset = getTerrainPaintWeightSampleOffset(terrain, sampleX, sampleZ);
paintWeights[offset] = weights[1]; const changedIndices: number[] = [];
paintWeights[offset + 1] = weights[2];
paintWeights[offset + 2] = weights[3]; for (
let layerOffset = 0;
layerOffset < TERRAIN_LAYER_COUNT - 1;
layerOffset += 1
) {
const paintWeightIndex = offset + layerOffset;
const nextWeight = weights[layerOffset + 1] ?? 0;
if ((paintWeights[paintWeightIndex] ?? 0) === nextWeight) {
continue;
}
paintWeights[paintWeightIndex] = nextWeight;
changedIndices.push(paintWeightIndex);
}
return changedIndices;
} }
export function applyTerrainBrushStamp(options: { export function applyTerrainBrushStamp(options: {
@@ -304,7 +322,9 @@ export function applyTerrainBrushStampInPlace(options: {
if (minSampleX > maxSampleX || minSampleZ > maxSampleZ) { if (minSampleX > maxSampleX || minSampleZ > maxSampleZ) {
return { return {
changed: false, changed: false,
dirtyBounds: null dirtyBounds: null,
heightSampleIndices: [],
paintWeightIndices: []
}; };
} }
@@ -320,6 +340,8 @@ export function applyTerrainBrushStampInPlace(options: {
: null; : null;
const smoothingStrength = clamp01(strength); const smoothingStrength = clamp01(strength);
let dirtyBounds: TerrainBrushDirtySampleBounds | null = null; let dirtyBounds: TerrainBrushDirtySampleBounds | null = null;
const heightSampleIndices: number[] = [];
const paintWeightIndices: number[] = [];
const markDirty = (sampleX: number, sampleZ: number) => { const markDirty = (sampleX: number, sampleZ: number) => {
if (dirtyBounds === null) { if (dirtyBounds === null) {
@@ -411,21 +433,26 @@ export function applyTerrainBrushStampInPlace(options: {
nextWeights[2] !== currentWeights[2] || nextWeights[2] !== currentWeights[2] ||
nextWeights[3] !== currentWeights[3] nextWeights[3] !== currentWeights[3]
) { ) {
setTerrainSamplePaintWeights( const changedPaintWeightIndices = setTerrainSamplePaintWeights(
terrain.paintWeights, terrain.paintWeights,
terrain, terrain,
sampleX, sampleX,
sampleZ, sampleZ,
nextWeights nextWeights
); );
if (changedPaintWeightIndices.length > 0) {
paintWeightIndices.push(...changedPaintWeightIndices);
markDirty(sampleX, sampleZ); markDirty(sampleX, sampleZ);
} }
}
continue; continue;
} }
} }
if (nextHeight !== currentHeight) { if (nextHeight !== currentHeight) {
terrain.heights[sampleIndex] = nextHeight; terrain.heights[sampleIndex] = nextHeight;
heightSampleIndices.push(sampleIndex);
markDirty(sampleX, sampleZ); markDirty(sampleX, sampleZ);
} }
} }
@@ -433,16 +460,19 @@ export function applyTerrainBrushStampInPlace(options: {
return { return {
changed: dirtyBounds !== null, changed: dirtyBounds !== null,
dirtyBounds dirtyBounds,
heightSampleIndices,
paintWeightIndices
}; };
} }
export function createTerrainBrushPatchFromTerrains(options: { export function createTerrainBrushPatchFromTerrains(options: {
before: Terrain; before: Terrain;
after: Terrain; after: Terrain;
dirtyBounds: TerrainBrushDirtySampleBounds; heightSampleIndices: Iterable<number>;
paintWeightIndices: Iterable<number>;
}): TerrainBrushPatch { }): TerrainBrushPatch {
const { before, after, dirtyBounds } = options; const { before, after } = options;
if (before.id !== after.id) { if (before.id !== after.id) {
throw new Error("Terrain brush patches require matching terrain ids."); throw new Error("Terrain brush patches require matching terrain ids.");
@@ -461,34 +491,36 @@ export function createTerrainBrushPatchFromTerrains(options: {
const heightSamples: TerrainBrushPatch["heightSamples"] = []; const heightSamples: TerrainBrushPatch["heightSamples"] = [];
const paintWeights: TerrainBrushPatch["paintWeights"] = []; const paintWeights: TerrainBrushPatch["paintWeights"] = [];
const minSampleX = clamp( const normalizeIndices = (
Math.floor(dirtyBounds.minSampleX), indices: Iterable<number>,
0, length: number,
before.sampleCountX - 1 label: string
); ): number[] => {
const maxSampleX = clamp( const uniqueIndices = new Set<number>();
Math.ceil(dirtyBounds.maxSampleX),
0,
before.sampleCountX - 1
);
const minSampleZ = clamp(
Math.floor(dirtyBounds.minSampleZ),
0,
before.sampleCountZ - 1
);
const maxSampleZ = clamp(
Math.ceil(dirtyBounds.maxSampleZ),
0,
before.sampleCountZ - 1
);
for (let sampleZ = minSampleZ; sampleZ <= maxSampleZ; sampleZ += 1) { for (const index of indices) {
for (let sampleX = minSampleX; sampleX <= maxSampleX; sampleX += 1) { if (!Number.isInteger(index) || index < 0 || index >= length) {
const sampleIndex = getTerrainSampleIndex(before, sampleX, sampleZ); throw new Error(`${label} patch index ${index} is out of range.`);
}
uniqueIndices.add(index);
}
return [...uniqueIndices].sort((left, right) => left - right);
};
for (const sampleIndex of normalizeIndices(
options.heightSampleIndices,
before.heights.length,
"Terrain height"
)) {
const beforeHeight = before.heights[sampleIndex] ?? 0; const beforeHeight = before.heights[sampleIndex] ?? 0;
const afterHeight = after.heights[sampleIndex] ?? 0; const afterHeight = after.heights[sampleIndex] ?? 0;
if (beforeHeight !== afterHeight) { if (beforeHeight === afterHeight) {
continue;
}
heightSamples.push({ heightSamples.push({
index: sampleIndex, index: sampleIndex,
before: beforeHeight, before: beforeHeight,
@@ -496,18 +528,11 @@ export function createTerrainBrushPatchFromTerrains(options: {
}); });
} }
const paintOffset = getTerrainPaintWeightSampleOffset( for (const paintWeightIndex of normalizeIndices(
before, options.paintWeightIndices,
sampleX, before.paintWeights.length,
sampleZ "Terrain paint weight"
); )) {
for (
let layerOffset = 0;
layerOffset < TERRAIN_LAYER_COUNT - 1;
layerOffset += 1
) {
const paintWeightIndex = paintOffset + layerOffset;
const beforeWeight = before.paintWeights[paintWeightIndex] ?? 0; const beforeWeight = before.paintWeights[paintWeightIndex] ?? 0;
const afterWeight = after.paintWeights[paintWeightIndex] ?? 0; const afterWeight = after.paintWeights[paintWeightIndex] ?? 0;
@@ -521,8 +546,6 @@ export function createTerrainBrushPatchFromTerrains(options: {
after: afterWeight after: afterWeight
}); });
} }
}
}
return { return {
terrainId: before.id, terrainId: before.id,