Enhance terrain rendering and brush feedback by implementing dirty bounds tracking and chunk refresh logic
This commit is contained in:
@@ -9089,6 +9089,99 @@ export class ViewportHost {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private terrainChunkOverlapsDirtySampleBounds(
|
||||||
|
chunk: TerrainRenderChunkObjects,
|
||||||
|
bounds: TerrainBrushDirtySampleBounds
|
||||||
|
): boolean {
|
||||||
|
return (
|
||||||
|
chunk.startSampleX <= bounds.maxSampleX &&
|
||||||
|
chunk.endSampleX >= bounds.minSampleX &&
|
||||||
|
chunk.startSampleZ <= bounds.maxSampleZ &&
|
||||||
|
chunk.endSampleZ >= bounds.minSampleZ
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private refreshDisplayedTerrainDirtyBounds(
|
||||||
|
terrainId: string,
|
||||||
|
bounds: TerrainBrushDirtySampleBounds | null
|
||||||
|
) {
|
||||||
|
if (bounds === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const terrain = this.getDisplayedTerrainState(terrainId);
|
||||||
|
const renderObjects = this.terrainRenderObjects.get(terrainId);
|
||||||
|
|
||||||
|
if (terrain === null || renderObjects === undefined) {
|
||||||
|
this.rebuildDisplayedTerrainState();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let refreshedAnyChunk = false;
|
||||||
|
|
||||||
|
for (const chunk of renderObjects.chunks) {
|
||||||
|
if (!this.terrainChunkOverlapsDirtySampleBounds(chunk, bounds)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextChunk = buildTerrainLodChunkMeshData(
|
||||||
|
terrain,
|
||||||
|
chunk.startSampleX,
|
||||||
|
chunk.startSampleZ
|
||||||
|
);
|
||||||
|
|
||||||
|
if (nextChunk === null) {
|
||||||
|
this.rebuildDisplayedTerrainState();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const previousGeometries = new Set(chunk.levelGeometries);
|
||||||
|
const nextLevelGeometries = nextChunk.levels.map(
|
||||||
|
(level) => level.geometry
|
||||||
|
);
|
||||||
|
const nextActiveLevelIndex = Math.min(
|
||||||
|
chunk.activeLevelIndex,
|
||||||
|
nextLevelGeometries.length - 1
|
||||||
|
);
|
||||||
|
|
||||||
|
chunk.levelGeometries = nextLevelGeometries;
|
||||||
|
chunk.activeLevelIndex = nextActiveLevelIndex;
|
||||||
|
chunk.startSampleX = nextChunk.startSampleX;
|
||||||
|
chunk.startSampleZ = nextChunk.startSampleZ;
|
||||||
|
chunk.endSampleX = nextChunk.endSampleX;
|
||||||
|
chunk.endSampleZ = nextChunk.endSampleZ;
|
||||||
|
chunk.worldCenter = {
|
||||||
|
x: terrain.position.x + nextChunk.localCenter.x,
|
||||||
|
y: terrain.position.y + nextChunk.localCenter.y,
|
||||||
|
z: terrain.position.z + nextChunk.localCenter.z
|
||||||
|
};
|
||||||
|
chunk.diagonal = nextChunk.diagonal;
|
||||||
|
chunk.mesh.geometry = nextLevelGeometries[nextActiveLevelIndex]!;
|
||||||
|
chunk.mesh.material =
|
||||||
|
nextActiveLevelIndex >= 2
|
||||||
|
? renderObjects.distantMaterial
|
||||||
|
: renderObjects.detailMaterial;
|
||||||
|
chunk.debugMesh.geometry = nextLevelGeometries[nextActiveLevelIndex]!;
|
||||||
|
chunk.debugMesh.material =
|
||||||
|
renderObjects.debugMaterials[nextActiveLevelIndex] ??
|
||||||
|
renderObjects.debugMaterials[renderObjects.debugMaterials.length - 1]!;
|
||||||
|
chunk.pickMesh.geometry = nextLevelGeometries[0]!;
|
||||||
|
|
||||||
|
for (const geometry of previousGeometries) {
|
||||||
|
geometry.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshedAnyChunk = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!refreshedAnyChunk) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateTerrainLodVisibility();
|
||||||
|
this.syncTerrainBrushPreview();
|
||||||
|
}
|
||||||
|
|
||||||
private isTerrainBrushActive(): boolean {
|
private isTerrainBrushActive(): boolean {
|
||||||
return (
|
return (
|
||||||
this.toolMode === "select" &&
|
this.toolMode === "select" &&
|
||||||
@@ -9309,8 +9402,8 @@ export class ViewportHost {
|
|||||||
},
|
},
|
||||||
toolState: ArmedTerrainBrushState,
|
toolState: ArmedTerrainBrushState,
|
||||||
referenceHeight: number | null
|
referenceHeight: number | null
|
||||||
): Terrain {
|
): ReturnType<typeof applyTerrainBrushStampInPlace> {
|
||||||
return applyTerrainBrushStamp({
|
return applyTerrainBrushStampInPlace({
|
||||||
terrain,
|
terrain,
|
||||||
center: point,
|
center: point,
|
||||||
settings: toolState,
|
settings: toolState,
|
||||||
@@ -9333,7 +9426,8 @@ export class ViewportHost {
|
|||||||
toolState: ArmedTerrainBrushState,
|
toolState: ArmedTerrainBrushState,
|
||||||
referenceHeight: number | null
|
referenceHeight: number | null
|
||||||
): {
|
): {
|
||||||
terrain: Terrain;
|
changed: boolean;
|
||||||
|
dirtyBounds: TerrainBrushDirtySampleBounds | null;
|
||||||
lastAppliedPoint: {
|
lastAppliedPoint: {
|
||||||
x: number;
|
x: number;
|
||||||
z: number;
|
z: number;
|
||||||
@@ -9346,14 +9440,43 @@ export class ViewportHost {
|
|||||||
|
|
||||||
if (distance < spacing) {
|
if (distance < spacing) {
|
||||||
return {
|
return {
|
||||||
terrain,
|
changed: false,
|
||||||
|
dirtyBounds: null,
|
||||||
lastAppliedPoint: from
|
lastAppliedPoint: from
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let nextTerrain = terrain;
|
let changed = false;
|
||||||
|
let dirtyBounds: TerrainBrushDirtySampleBounds | null = null;
|
||||||
let lastAppliedPoint = from;
|
let lastAppliedPoint = from;
|
||||||
const stepCount = Math.floor(distance / spacing);
|
const stepCount = Math.floor(distance / spacing);
|
||||||
|
const mergeDirtyBounds = (nextBounds: TerrainBrushDirtySampleBounds | null) => {
|
||||||
|
if (nextBounds === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dirtyBounds === null) {
|
||||||
|
dirtyBounds = { ...nextBounds };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dirtyBounds.minSampleX = Math.min(
|
||||||
|
dirtyBounds.minSampleX,
|
||||||
|
nextBounds.minSampleX
|
||||||
|
);
|
||||||
|
dirtyBounds.maxSampleX = Math.max(
|
||||||
|
dirtyBounds.maxSampleX,
|
||||||
|
nextBounds.maxSampleX
|
||||||
|
);
|
||||||
|
dirtyBounds.minSampleZ = Math.min(
|
||||||
|
dirtyBounds.minSampleZ,
|
||||||
|
nextBounds.minSampleZ
|
||||||
|
);
|
||||||
|
dirtyBounds.maxSampleZ = Math.max(
|
||||||
|
dirtyBounds.maxSampleZ,
|
||||||
|
nextBounds.maxSampleZ
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
for (let stepIndex = 1; stepIndex <= stepCount; stepIndex += 1) {
|
for (let stepIndex = 1; stepIndex <= stepCount; stepIndex += 1) {
|
||||||
const t = Math.min(1, (stepIndex * spacing) / distance);
|
const t = Math.min(1, (stepIndex * spacing) / distance);
|
||||||
@@ -9361,17 +9484,20 @@ export class ViewportHost {
|
|||||||
x: from.x + deltaX * t,
|
x: from.x + deltaX * t,
|
||||||
z: from.z + deltaZ * t
|
z: from.z + deltaZ * t
|
||||||
};
|
};
|
||||||
nextTerrain = this.applyTerrainBrushPoint(
|
const result = this.applyTerrainBrushPoint(
|
||||||
nextTerrain,
|
terrain,
|
||||||
point,
|
point,
|
||||||
toolState,
|
toolState,
|
||||||
referenceHeight
|
referenceHeight
|
||||||
);
|
);
|
||||||
|
changed ||= result.changed;
|
||||||
|
mergeDirtyBounds(result.dirtyBounds);
|
||||||
lastAppliedPoint = point;
|
lastAppliedPoint = point;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
terrain: nextTerrain,
|
changed,
|
||||||
|
dirtyBounds,
|
||||||
lastAppliedPoint
|
lastAppliedPoint
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user