j
This commit is contained in:
1 parent
f030eec08a
commit
db558d9e14
18 files changed
+1570
-264
No files matched your search
@@ -4,20 +4,27 @@
|
||||
#include "VoxelContentManager.h"
|
||||
#include "VoxelStrateManager.h"
|
||||
#include "VoxelStrateDefinition.h"
|
||||
#include "VoxelBiomeDefinition.h"
|
||||
#include "VoxelGenerator.h"
|
||||
#include "VoxelCaveMorphology.h" // VoxelHash
|
||||
#include "Components/StaticMeshComponent.h"
|
||||
#include "Components/HierarchicalInstancedStaticMeshComponent.h"
|
||||
#include "Components/LightComponent.h"
|
||||
#include "Engine/StaticMesh.h"
|
||||
#include "Engine/World.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "Materials/MaterialInterface.h"
|
||||
|
||||
// Global safety cap on spawned decorations per chunk (across all entries).
|
||||
// Global safety cap on spawned decoration ACTORS per chunk (across all entries).
|
||||
// HISM instances are exempt — they're batched render data, capped per entry by MaxPerChunk.
|
||||
static constexpr int32 GMaxDecorationsPerChunk = 400;
|
||||
|
||||
void UVoxelContentManager::Initialize(AActor* InOwner, UVoxelStrateManager* InStrateManager, int32 InSeed)
|
||||
void UVoxelContentManager::Initialize(AActor* InOwner, UVoxelStrateManager* InStrateManager,
|
||||
UVoxelGenerator* InGenerator, int32 InSeed)
|
||||
{
|
||||
Owner = InOwner;
|
||||
StrateManager = InStrateManager;
|
||||
Generator = InGenerator;
|
||||
Seed = InSeed;
|
||||
|
||||
// Engine unit plane — reused (scaled) for every water surface.
|
||||
@@ -46,25 +53,43 @@ void UVoxelContentManager::PopulateChunk(const FIntVector& ChunkCoord, const FVo
|
||||
const UVoxelStrateDefinition* Def = StrateManager->GetStrateForChunk(ChunkCoord);
|
||||
if (!Def) return;
|
||||
|
||||
// Decorations only on near (full-detail) chunks — they're game-thread actor spawns
|
||||
// and pointless on distant low-poly chunks. Water is one cheap plane, place it at any LOD.
|
||||
if (LODLevel == 0)
|
||||
// Dominant biome for this chunk (center XY) selects the content + water overrides.
|
||||
// Per-chunk granularity: a chunk straddling a biome border takes its centre's biome
|
||||
// for the whole decoration SET (fine — the set is a choice, not a per-vertex blend).
|
||||
const UVoxelBiomeDefinition* Biome = nullptr;
|
||||
if (Generator)
|
||||
{
|
||||
const float CenterVoxelX = ((float)ChunkCoord.X + 0.5f) * CHUNK_SIZE;
|
||||
const float CenterVoxelY = ((float)ChunkCoord.Y + 0.5f) * CHUNK_SIZE;
|
||||
Biome = Generator->GetDominantBiomeAt(CenterVoxelX, CenterVoxelY, ChunkCoord.Z);
|
||||
}
|
||||
|
||||
// Decorations come from the biome when it provides any, else the strate's list.
|
||||
const TArray<FStrateDecoration>& Decos =
|
||||
(Biome && Biome->Decorations.Num() > 0) ? Biome->Decorations : Def->Decorations;
|
||||
|
||||
// Per-entry LOD gate inside SpawnDecorations (FStrateDecoration::MaxLODLevel):
|
||||
// actor entries usually stay LOD0-only; instanced entries may persist to LOD1-2.
|
||||
// Water is one cheap plane, place it at any LOD.
|
||||
{
|
||||
TArray<TWeakObjectPtr<AActor>> Spawned;
|
||||
SpawnDecorations(ChunkCoord, MeshData, Def, Spawned);
|
||||
SpawnDecorations(ChunkCoord, MeshData, Decos, LODLevel, Spawned);
|
||||
if (Spawned.Num() > 0)
|
||||
{
|
||||
SpawnedActors.Add(ChunkCoord, MoveTemp(Spawned));
|
||||
}
|
||||
}
|
||||
|
||||
SpawnWater(ChunkCoord, Def);
|
||||
// Water plane: biome material override (level stays strate-global, see SpawnWater).
|
||||
UMaterialInterface* WaterMat = (Biome && Biome->WaterMaterial) ? Biome->WaterMaterial : Def->WaterMaterial;
|
||||
SpawnWater(ChunkCoord, Def, WaterMat);
|
||||
}
|
||||
|
||||
void UVoxelContentManager::SpawnDecorations(const FIntVector& ChunkCoord, const FVoxelMeshData& MeshData,
|
||||
const UVoxelStrateDefinition* Def, TArray<TWeakObjectPtr<AActor>>& Out)
|
||||
const TArray<FStrateDecoration>& Decorations, int32 LODLevel,
|
||||
TArray<TWeakObjectPtr<AActor>>& Out)
|
||||
{
|
||||
if (Def->Decorations.Num() == 0) return;
|
||||
if (Decorations.Num() == 0) return;
|
||||
|
||||
AActor* OwnerActor = Owner.Get();
|
||||
if (!OwnerActor) return;
|
||||
@@ -81,19 +106,30 @@ void UVoxelContentManager::SpawnDecorations(const FIntVector& ChunkCoord, const
|
||||
const bool bHasWater = (WaterVoxelZ != -FLT_MAX);
|
||||
const float WaterWorldZ = bHasWater ? WaterVoxelZ * VOXEL_SIZE : -FLT_MAX;
|
||||
|
||||
// Strate light culling: lights only live in the player's strate. INT32_MIN = player
|
||||
// strate not known yet (first frames) — leave lights on, the first SetActiveStrate
|
||||
// sweep corrects them.
|
||||
const int32 ChunkStrate = GetChunkStrateIndex(ChunkCoord);
|
||||
const bool bLightsOn = (ActiveStrateIndex == INT32_MIN) || (ChunkStrate == ActiveStrateIndex);
|
||||
|
||||
int32 TotalSpawned = 0;
|
||||
|
||||
for (int32 DecoIdx = 0; DecoIdx < Def->Decorations.Num(); ++DecoIdx)
|
||||
for (int32 DecoIdx = 0; DecoIdx < Decorations.Num(); ++DecoIdx)
|
||||
{
|
||||
const FStrateDecoration& Deco = Def->Decorations[DecoIdx];
|
||||
if (!Deco.ActorClass) continue;
|
||||
const FStrateDecoration& Deco = Decorations[DecoIdx];
|
||||
const bool bInstanced = (Deco.InstancedMesh != nullptr);
|
||||
if (!bInstanced && !Deco.ActorClass) continue;
|
||||
if (Deco.SpawnDensity <= 0.0f) continue;
|
||||
if (LODLevel > Deco.MaxLODLevel) continue; // entry not wanted at this LOD
|
||||
|
||||
// Lazily created on the first placement of this entry (avoids empty components).
|
||||
UHierarchicalInstancedStaticMeshComponent* HISM = nullptr;
|
||||
|
||||
int32 SpawnedThisEntry = 0;
|
||||
|
||||
for (int32 i = 0; i < NumVerts; ++i)
|
||||
{
|
||||
if (TotalSpawned >= GMaxDecorationsPerChunk) return;
|
||||
if (!bInstanced && TotalSpawned >= GMaxDecorationsPerChunk) break; // actor cap
|
||||
if (SpawnedThisEntry >= Deco.MaxPerChunk) break;
|
||||
|
||||
const FVector NormalWorld = MeshData.Normals.IsValidIndex(i)
|
||||
@@ -144,26 +180,101 @@ void UVoxelContentManager::SpawnDecorations(const FIntVector& ChunkCoord, const
|
||||
const float ScaleT = VoxelHash::ToFloat01(VoxelHash::Mix(H ^ 0x5CA1E000u));
|
||||
const float Scale = FMath::Lerp(Deco.MinScale, Deco.MaxScale, ScaleT);
|
||||
|
||||
FActorSpawnParameters SpawnParams;
|
||||
SpawnParams.Owner = OwnerActor;
|
||||
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
||||
const FTransform SpawnXf(BaseQ, SpawnPos, FVector(Scale));
|
||||
|
||||
AActor* NewActor = World->SpawnActor<AActor>(
|
||||
Deco.ActorClass,
|
||||
FTransform(BaseQ, SpawnPos, FVector(Scale)),
|
||||
SpawnParams);
|
||||
|
||||
if (NewActor)
|
||||
if (bInstanced)
|
||||
{
|
||||
Out.Add(NewActor);
|
||||
// HISM path — batched instances, no actor. Pure visual: no collision,
|
||||
// engine handles frustum/occlusion culling per cluster.
|
||||
if (!HISM)
|
||||
{
|
||||
HISM = NewObject<UHierarchicalInstancedStaticMeshComponent>(OwnerActor);
|
||||
HISM->SetStaticMesh(Deco.InstancedMesh);
|
||||
HISM->SetMobility(EComponentMobility::Movable);
|
||||
HISM->SetCollisionEnabled(ECollisionEnabled::NoCollision);
|
||||
HISM->RegisterComponent();
|
||||
HISM->AttachToComponent(OwnerActor->GetRootComponent(),
|
||||
FAttachmentTransformRules::KeepRelativeTransform);
|
||||
ChunkInstances.FindOrAdd(ChunkCoord).Add(HISM);
|
||||
}
|
||||
HISM->AddInstance(SpawnXf, /*bWorldSpace=*/true);
|
||||
++SpawnedThisEntry;
|
||||
++TotalSpawned;
|
||||
}
|
||||
else
|
||||
{
|
||||
FActorSpawnParameters SpawnParams;
|
||||
SpawnParams.Owner = OwnerActor;
|
||||
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
||||
|
||||
AActor* NewActor = World->SpawnActor<AActor>(Deco.ActorClass, SpawnXf, SpawnParams);
|
||||
|
||||
if (NewActor)
|
||||
{
|
||||
// Strate light culling — wrong strate ⇒ lights start hidden.
|
||||
if (!bLightsOn)
|
||||
{
|
||||
SetActorLightsEnabled(NewActor, false);
|
||||
}
|
||||
Out.Add(NewActor);
|
||||
++SpawnedThisEntry;
|
||||
++TotalSpawned;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UVoxelContentManager::SpawnWater(const FIntVector& ChunkCoord, const UVoxelStrateDefinition* Def)
|
||||
//=============================================================================
|
||||
// STRATE LIGHT CULLING
|
||||
//=============================================================================
|
||||
// Seals + the bedrock gap make strates light-tight by construction, but a shadowless
|
||||
// point light shines straight through rock (blocking IS shadowing), and a shadowed one
|
||||
// pays full shadow cost to render black. Lights can therefore only legitimately matter
|
||||
// in the player's own strate — toggle them analytically on strate change instead of
|
||||
// asking the GPU to discover it per-pixel.
|
||||
|
||||
int32 UVoxelContentManager::GetChunkStrateIndex(const FIntVector& ChunkCoord) const
|
||||
{
|
||||
if (!StrateManager) return INT32_MIN;
|
||||
// GetStrateIndex expects Unreal world units (cm) — it converts cm→chunkZ internally.
|
||||
// (Previously this passed voxel-Z, so the strate match for light culling was wrong.)
|
||||
const float CenterWorldZ = ((float)ChunkCoord.Z + 0.5f) * CHUNK_SIZE * VOXEL_SIZE;
|
||||
return StrateManager->GetStrateIndex(CenterWorldZ);
|
||||
}
|
||||
|
||||
void UVoxelContentManager::SetActorLightsEnabled(AActor* Actor, bool bEnabled)
|
||||
{
|
||||
TInlineComponentArray<ULightComponent*> Lights(Actor);
|
||||
for (ULightComponent* Light : Lights)
|
||||
{
|
||||
if (Light)
|
||||
{
|
||||
Light->SetVisibility(bEnabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UVoxelContentManager::SetActiveStrate(int32 PlayerStrateIndex)
|
||||
{
|
||||
if (PlayerStrateIndex == ActiveStrateIndex) return; // every-Tick fast path
|
||||
ActiveStrateIndex = PlayerStrateIndex;
|
||||
|
||||
// Strate change (rare): sweep all populated chunks and toggle their actors' lights.
|
||||
for (const auto& Pair : SpawnedActors)
|
||||
{
|
||||
const bool bEnable = (GetChunkStrateIndex(Pair.Key) == ActiveStrateIndex);
|
||||
for (const TWeakObjectPtr<AActor>& A : Pair.Value)
|
||||
{
|
||||
if (AActor* Act = A.Get())
|
||||
{
|
||||
SetActorLightsEnabled(Act, bEnable);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UVoxelContentManager::SpawnWater(const FIntVector& ChunkCoord, const UVoxelStrateDefinition* Def,
|
||||
UMaterialInterface* WaterMaterial)
|
||||
{
|
||||
if (!Def->bHasWater || !PlaneMesh) return;
|
||||
|
||||
@@ -196,9 +307,9 @@ void UVoxelContentManager::SpawnWater(const FIntVector& ChunkCoord, const UVoxel
|
||||
const float ChunkWorld = (float)CHUNK_SIZE * VOXEL_SIZE;
|
||||
Plane->SetWorldScale3D(FVector(ChunkWorld / 100.0f, ChunkWorld / 100.0f, 1.0f));
|
||||
|
||||
if (Def->WaterMaterial)
|
||||
if (WaterMaterial)
|
||||
{
|
||||
Plane->SetMaterial(0, Def->WaterMaterial);
|
||||
Plane->SetMaterial(0, WaterMaterial);
|
||||
}
|
||||
|
||||
WaterPlanes.Add(ChunkCoord, Plane);
|
||||
@@ -218,6 +329,18 @@ void UVoxelContentManager::ClearChunk(const FIntVector& ChunkCoord)
|
||||
SpawnedActors.Remove(ChunkCoord);
|
||||
}
|
||||
|
||||
if (TArray<TWeakObjectPtr<UHierarchicalInstancedStaticMeshComponent>>* Comps = ChunkInstances.Find(ChunkCoord))
|
||||
{
|
||||
for (const TWeakObjectPtr<UHierarchicalInstancedStaticMeshComponent>& C : *Comps)
|
||||
{
|
||||
if (UHierarchicalInstancedStaticMeshComponent* Comp = C.Get())
|
||||
{
|
||||
Comp->DestroyComponent();
|
||||
}
|
||||
}
|
||||
ChunkInstances.Remove(ChunkCoord);
|
||||
}
|
||||
|
||||
if (UStaticMeshComponent** Plane = WaterPlanes.Find(ChunkCoord))
|
||||
{
|
||||
if (*Plane)
|
||||
@@ -236,7 +359,13 @@ void UVoxelContentManager::ClearAll()
|
||||
{
|
||||
ClearChunk(C);
|
||||
}
|
||||
// Any water planes without decorations.
|
||||
// Chunks that only have instances or water (no actors).
|
||||
TArray<FIntVector> InstanceCoords;
|
||||
ChunkInstances.GetKeys(InstanceCoords);
|
||||
for (const FIntVector& C : InstanceCoords)
|
||||
{
|
||||
ClearChunk(C);
|
||||
}
|
||||
TArray<FIntVector> WaterCoords;
|
||||
WaterPlanes.GetKeys(WaterCoords);
|
||||
for (const FIntVector& C : WaterCoords)
|
||||
@@ -244,5 +373,6 @@ void UVoxelContentManager::ClearAll()
|
||||
ClearChunk(C);
|
||||
}
|
||||
SpawnedActors.Empty();
|
||||
ChunkInstances.Empty();
|
||||
WaterPlanes.Empty();
|
||||
}
|
||||
Reference in new issue
Block a user