diff --git a/src/runtime-three/runtime-host.ts b/src/runtime-three/runtime-host.ts index cef43b46..0d433096 100644 --- a/src/runtime-three/runtime-host.ts +++ b/src/runtime-three/runtime-host.ts @@ -41,6 +41,7 @@ import { createModelInstanceRenderGroup, disposeModelInstance } from "../assets/model-instance-rendering"; +import { FoliageInstancedRenderer } from "../foliage/foliage-instanced-renderer"; import type { LoadedModelAsset } from "../assets/gltf-model-import"; import type { LoadedImageAsset } from "../assets/image-assets"; import type { LoadedAudioAsset } from "../assets/audio-assets"; @@ -759,6 +760,11 @@ export class RuntimeHost { LightVolumeRenderObjects >(); private readonly modelRenderObjects = new Map(); + private readonly foliageRenderer = new FoliageInstancedRenderer({ + onRebuilt: () => { + this.applyShadowState(); + } + }); private readonly materialTextureCache = new Map< string, CachedMaterialTexture @@ -882,6 +888,7 @@ export class RuntimeHost { this.scene.add(this.lightVolumeGroup); this.scene.add(this.brushGroup); this.scene.add(this.terrainGroup); + this.scene.add(this.foliageRenderer.group); this.scene.add(this.modelGroup); this.targetingLuxMesh.renderOrder = 10000; this.targetingLuxGlowMesh.renderOrder = 9999; @@ -1208,6 +1215,7 @@ export class RuntimeHost { this.rebuildLightVolumes(runtimeScene.volumes.light); this.rebuildBrushMeshes(runtimeScene.brushes); this.rebuildTerrainMeshes(runtimeScene.terrains); + this.rebuildFoliage(runtimeScene); this.rebuildModelRenderObjects( runtimeScene.modelInstances, runtimeScene.npcDefinitions @@ -1404,6 +1412,7 @@ export class RuntimeHost { this.clearLightVolumes(); this.clearBrushMeshes(); this.clearTerrainMeshes(); + this.foliageRenderer.dispose(); this.clearModelRenderObjects(); this.collisionWorldRequestId += 1; this.clearCollisionWorld(); @@ -3163,6 +3172,11 @@ export class RuntimeHost { ); } + applyAdvancedRenderingRenderableShadowFlags( + this.foliageRenderer.group, + shadowsEnabled + ); + for (const renderGroup of this.modelRenderObjects.values()) { applyAdvancedRenderingRenderableShadowFlags(renderGroup, shadowsEnabled); } @@ -5012,6 +5026,14 @@ export class RuntimeHost { this.terrainMeshes.clear(); } + private rebuildFoliage(runtimeScene: RuntimeSceneDefinition) { + this.foliageRenderer.sync({ + terrains: runtimeScene.foliage.terrains, + foliageLayers: runtimeScene.foliage.layers, + foliagePrototypes: runtimeScene.foliage.prototypes + }); + } + private disposeUniqueMaterials(materials: Material[]) { for (const material of new Set(materials)) { material.dispose(); diff --git a/src/runtime-three/runtime-scene-build.ts b/src/runtime-three/runtime-scene-build.ts index 778b5cd9..8e875da7 100644 --- a/src/runtime-three/runtime-scene-build.ts +++ b/src/runtime-three/runtime-scene-build.ts @@ -61,12 +61,19 @@ import { type ScenePathPoint } from "../document/paths"; import { + cloneTerrain, getTerrainBounds, getTerrainFootprintDepth, getTerrainFootprintWidth, getTerrains, type Terrain } from "../document/terrains"; +import { + cloneFoliageLayerRegistry, + cloneFoliagePrototypeRegistry, + type FoliageLayerRegistry, + type FoliagePrototypeRegistry +} from "../foliage/foliage"; import { cloneWorldSettings, type WorldSettings @@ -496,6 +503,12 @@ export interface RuntimePath { totalLength: number; } +export interface RuntimeFoliageDefinition { + terrains: Terrain[]; + layers: FoliageLayerRegistry; + prototypes: FoliagePrototypeRegistry; +} + export interface RuntimeEntityCollection { playerStarts: RuntimePlayerStart[]; sceneEntries: RuntimeSceneEntry[]; @@ -527,6 +540,7 @@ export interface RuntimeSceneDefinition { staticColliders: RuntimeSceneCollider[]; colliders: RuntimeSceneCollider[]; sceneBounds: RuntimeSceneBounds | null; + foliage: RuntimeFoliageDefinition; modelInstances: RuntimeModelInstance[]; paths: RuntimePath[]; npcDefinitions: RuntimeNpcDefinition[]; @@ -1895,6 +1909,11 @@ export function buildRuntimeSceneFromDocument( const terrains = enabledTerrains.map((terrain) => buildRuntimeTerrain(terrain, document) ); + const foliage: RuntimeFoliageDefinition = { + terrains: enabledTerrains.map((terrain) => cloneTerrain(terrain)), + layers: cloneFoliageLayerRegistry(document.foliageLayers), + prototypes: cloneFoliagePrototypeRegistry(document.foliagePrototypes) + }; const staticColliders: RuntimeSceneCollider[] = []; const volumes: RuntimeBoxVolumeCollection = { fog: [], @@ -2069,6 +2088,7 @@ export function buildRuntimeSceneFromDocument( staticColliders, colliders, sceneBounds: combinedSceneBounds, + foliage, modelInstances, paths, npcDefinitions: collections.npcDefinitions, diff --git a/src/viewport-three/viewport-host.ts b/src/viewport-three/viewport-host.ts index f5bb1ba4..db33006c 100644 --- a/src/viewport-three/viewport-host.ts +++ b/src/viewport-three/viewport-host.ts @@ -86,6 +86,7 @@ import { disposeModelInstance, syncModelInstanceSelectionShell } from "../assets/model-instance-rendering"; +import { FoliageInstancedRenderer } from "../foliage/foliage-instanced-renderer"; import type { LoadedModelAsset } from "../assets/gltf-model-import"; import type { LoadedImageAsset } from "../assets/image-assets"; import type { ProjectAssetRecord } from "../assets/project-assets"; @@ -840,6 +841,11 @@ export class ViewportHost { LightVolumeRenderObjects >(); private readonly modelRenderObjects = new Map(); + private readonly foliageRenderer = new FoliageInstancedRenderer({ + onRebuilt: () => { + this.applyShadowState(); + } + }); private readonly materialTextureCache = new Map< string, CachedMaterialTexture @@ -1043,6 +1049,8 @@ export class ViewportHost { this.scene.add(this.lightVolumeGroup); this.scene.add(this.brushGroup); this.scene.add(this.terrainGroup); + this.scene.add(this.foliageRenderer.group); + this.syncFoliageVisibility(); this.terrainBrushPreviewGroup.visible = false; this.terrainBrushPreviewLine.frustumCulled = false; this.terrainBrushPreviewCenter.frustumCulled = false; @@ -1358,6 +1366,7 @@ export class ViewportHost { this.currentActiveSelectionId ); } + this.rebuildFoliage(document); this.rebuildPaths(document, this.currentSelection); this.rebuildEntityMarkers(document, this.currentSelection); this.rebuildModelInstances(document, this.currentSelection); @@ -1813,6 +1822,7 @@ export class ViewportHost { this.clearLightVolumes(); this.clearBrushMeshes(); this.clearTerrains(); + this.foliageRenderer.dispose(); this.clearPaths(); this.clearEntityMarkers(); this.creationPreviewChangeHandler = null;