Add fog material creation and disposal methods in RuntimeHost

This commit is contained in:
2026-04-07 11:33:12 +02:00
parent 1b16aacf93
commit f6cb305243
2 changed files with 98 additions and 23 deletions

View File

@@ -14,6 +14,7 @@ import { RuntimeAudioSystem } from "./runtime-audio-system";
import { OrbitVisitorNavigationController } from "./orbit-visitor-navigation-controller";
import { resolveUnderwaterFogState } from "./underwater-fog";
const FALLBACK_FACE_COLOR = 0x747d89;
const BOX_FACE_MATERIAL_COUNT = 6;
const WATER_REFLECTION_UPDATE_INTERVAL_MS = 96;
export class RuntimeHost {
scene = new Scene();
@@ -379,14 +380,15 @@ export class RuntimeHost {
const contactPatches = brush.volume.mode === "water"
? this.mergeRuntimeWaterContactPatches(staticContactPatches, this.collectRuntimePlayerWaterContactPatches(brush))
: [];
const materials = [
this.createFaceMaterial(brush, "posX", brush.faces.posX.material, volumeRenderPaths, contactPatches, staticContactPatches),
this.createFaceMaterial(brush, "negX", brush.faces.negX.material, volumeRenderPaths, contactPatches, staticContactPatches),
this.createFaceMaterial(brush, "posY", brush.faces.posY.material, volumeRenderPaths, contactPatches, staticContactPatches),
this.createFaceMaterial(brush, "negY", brush.faces.negY.material, volumeRenderPaths, contactPatches, staticContactPatches),
this.createFaceMaterial(brush, "posZ", brush.faces.posZ.material, volumeRenderPaths, contactPatches, staticContactPatches),
this.createFaceMaterial(brush, "negZ", brush.faces.negZ.material, volumeRenderPaths, contactPatches, staticContactPatches)
];
const materials = this.createFogMaterialSet(brush, volumeRenderPaths) ??
[
this.createFaceMaterial(brush, "posX", brush.faces.posX.material, volumeRenderPaths, contactPatches, staticContactPatches),
this.createFaceMaterial(brush, "negX", brush.faces.negX.material, volumeRenderPaths, contactPatches, staticContactPatches),
this.createFaceMaterial(brush, "posY", brush.faces.posY.material, volumeRenderPaths, contactPatches, staticContactPatches),
this.createFaceMaterial(brush, "negY", brush.faces.negY.material, volumeRenderPaths, contactPatches, staticContactPatches),
this.createFaceMaterial(brush, "posZ", brush.faces.posZ.material, volumeRenderPaths, contactPatches, staticContactPatches),
this.createFaceMaterial(brush, "negZ", brush.faces.negZ.material, volumeRenderPaths, contactPatches, staticContactPatches)
];
const mesh = new Mesh(geometry, materials);
mesh.position.set(brush.center.x, brush.center.y, brush.center.z);
mesh.rotation.set((brush.rotationDegrees.x * Math.PI) / 180, (brush.rotationDegrees.y * Math.PI) / 180, (brush.rotationDegrees.z * Math.PI) / 180);
@@ -396,6 +398,34 @@ export class RuntimeHost {
}
this.applyShadowState();
}
createFogMaterialSet(brush, volumeRenderPaths) {
if (brush.volume.mode !== "fog") {
return null;
}
if (volumeRenderPaths.fog === "quality") {
const fogMaterial = createFogQualityMaterial({
colorHex: brush.volume.fog.colorHex,
density: brush.volume.fog.density,
padding: brush.volume.fog.padding,
time: this.volumeTime,
halfSize: {
x: brush.size.x * 0.5,
y: brush.size.y * 0.5,
z: brush.size.z * 0.5
}
});
this.volumeAnimatedUniforms.push(fogMaterial.animationUniform);
return Array.from({ length: BOX_FACE_MATERIAL_COUNT }, () => fogMaterial.material);
}
const densityOpacity = Math.max(0.06, Math.min(0.72, brush.volume.fog.density * 0.8 + 0.08));
const fogMaterial = new MeshBasicMaterial({
color: brush.volume.fog.colorHex,
transparent: true,
opacity: densityOpacity,
depthWrite: false
});
return Array.from({ length: BOX_FACE_MATERIAL_COUNT }, () => fogMaterial);
}
configureFogVolumeMesh(mesh, materials) {
const fogMaterials = materials.filter((material) => material instanceof ShaderMaterial && material.uniforms["localCameraPosition"] !== undefined);
if (fogMaterials.length === 0) {
@@ -666,9 +696,7 @@ export class RuntimeHost {
for (const mesh of this.brushMeshes.values()) {
this.brushGroup.remove(mesh);
mesh.geometry.dispose();
for (const material of mesh.material) {
material.dispose();
}
this.disposeUniqueMaterials(mesh.material);
}
this.brushMeshes.clear();
this.volumeAnimatedUniforms.length = 0;
@@ -677,6 +705,11 @@ export class RuntimeHost {
}
this.runtimeWaterContactUniforms.length = 0;
}
disposeUniqueMaterials(materials) {
for (const material of new Set(materials)) {
material.dispose();
}
}
createPlayerWaterContactBounds() {
if (this.runtimeScene === null || this.currentFirstPersonTelemetry === null) {
return null;

View File

@@ -94,6 +94,7 @@ interface RuntimeWaterContactUniformBinding {
}
const FALLBACK_FACE_COLOR = 0x747d89;
const BOX_FACE_MATERIAL_COUNT = 6;
const WATER_REFLECTION_UPDATE_INTERVAL_MS = 96;
export class RuntimeHost {
@@ -562,14 +563,16 @@ export class RuntimeHost {
? this.mergeRuntimeWaterContactPatches(brush, staticContactPatches, this.collectRuntimePlayerWaterContactPatches(brush))
: [];
const materials = [
this.createFaceMaterial(brush, "posX", brush.faces.posX.material, volumeRenderPaths, contactPatches, staticContactPatches),
this.createFaceMaterial(brush, "negX", brush.faces.negX.material, volumeRenderPaths, contactPatches, staticContactPatches),
this.createFaceMaterial(brush, "posY", brush.faces.posY.material, volumeRenderPaths, contactPatches, staticContactPatches),
this.createFaceMaterial(brush, "negY", brush.faces.negY.material, volumeRenderPaths, contactPatches, staticContactPatches),
this.createFaceMaterial(brush, "posZ", brush.faces.posZ.material, volumeRenderPaths, contactPatches, staticContactPatches),
this.createFaceMaterial(brush, "negZ", brush.faces.negZ.material, volumeRenderPaths, contactPatches, staticContactPatches)
];
const materials =
this.createFogMaterialSet(brush, volumeRenderPaths) ??
[
this.createFaceMaterial(brush, "posX", brush.faces.posX.material, volumeRenderPaths, contactPatches, staticContactPatches),
this.createFaceMaterial(brush, "negX", brush.faces.negX.material, volumeRenderPaths, contactPatches, staticContactPatches),
this.createFaceMaterial(brush, "posY", brush.faces.posY.material, volumeRenderPaths, contactPatches, staticContactPatches),
this.createFaceMaterial(brush, "negY", brush.faces.negY.material, volumeRenderPaths, contactPatches, staticContactPatches),
this.createFaceMaterial(brush, "posZ", brush.faces.posZ.material, volumeRenderPaths, contactPatches, staticContactPatches),
this.createFaceMaterial(brush, "negZ", brush.faces.negZ.material, volumeRenderPaths, contactPatches, staticContactPatches)
];
const mesh = new Mesh(geometry, materials);
mesh.position.set(brush.center.x, brush.center.y, brush.center.z);
@@ -586,6 +589,42 @@ export class RuntimeHost {
this.applyShadowState();
}
private createFogMaterialSet(
brush: RuntimeBoxBrushInstance,
volumeRenderPaths: { fog: "performance" | "quality"; water: "performance" | "quality" }
): Material[] | null {
if (brush.volume.mode !== "fog") {
return null;
}
if (volumeRenderPaths.fog === "quality") {
const fogMaterial = createFogQualityMaterial({
colorHex: brush.volume.fog.colorHex,
density: brush.volume.fog.density,
padding: brush.volume.fog.padding,
time: this.volumeTime,
halfSize: {
x: brush.size.x * 0.5,
y: brush.size.y * 0.5,
z: brush.size.z * 0.5
}
});
this.volumeAnimatedUniforms.push(fogMaterial.animationUniform);
return Array.from({ length: BOX_FACE_MATERIAL_COUNT }, () => fogMaterial.material);
}
const densityOpacity = Math.max(0.06, Math.min(0.72, brush.volume.fog.density * 0.8 + 0.08));
const fogMaterial = new MeshBasicMaterial({
color: brush.volume.fog.colorHex,
transparent: true,
opacity: densityOpacity,
depthWrite: false
});
return Array.from({ length: BOX_FACE_MATERIAL_COUNT }, () => fogMaterial);
}
private configureFogVolumeMesh(mesh: Mesh<BufferGeometry, Material[]>, materials: Material[]) {
const fogMaterials = materials.filter(
(material): material is ShaderMaterial => material instanceof ShaderMaterial && material.uniforms["localCameraPosition"] !== undefined
@@ -926,10 +965,7 @@ export class RuntimeHost {
for (const mesh of this.brushMeshes.values()) {
this.brushGroup.remove(mesh);
mesh.geometry.dispose();
for (const material of mesh.material) {
material.dispose();
}
this.disposeUniqueMaterials(mesh.material);
}
this.brushMeshes.clear();
@@ -940,6 +976,12 @@ export class RuntimeHost {
this.runtimeWaterContactUniforms.length = 0;
}
private disposeUniqueMaterials(materials: Material[]) {
for (const material of new Set(materials)) {
material.dispose();
}
}
private createPlayerWaterContactBounds() {
if (this.runtimeScene === null || this.currentFirstPersonTelemetry === null) {
return null;