#include "World/DescentTerrainActor.h" #include "World/DescentTerrainSubsystem.h" #include "Core/DescentBoot.h" #include "Core/DescentMath.h" #include "Core/Tuning/DescentTune_Terrain.h" #include "HAL/PlatformTime.h" #include "TheDescent.h" #include "Components/SceneComponent.h" #include "Engine/World.h" #include "Materials/Material.h" #include "Materials/MaterialInterface.h" #include "ProceduralMeshComponent.h" #include "UObject/UObjectGlobals.h" ADescentTerrainActor::ADescentTerrainActor() { PrimaryActorTick.bCanEverTick = false; USceneComponent* Root = CreateDefaultSubobject(TEXT("Root")); SetRootComponent(Root); } void ADescentTerrainActor::BeginPlay() { Super::BeginPlay(); if (!TerrainMaterial) { UMaterialInterface* M = LoadObject(nullptr, TEXT("/Game/Materials/MI_Terrain.MI_Terrain"), nullptr, LOAD_NoWarn); if (!M) { M = LoadObject(nullptr, TEXT("/Game/Materials/M_Terrain.M_Terrain"), nullptr, LOAD_NoWarn); } if (!M) { UE_LOG(LogDescent, Warning, TEXT("Terrain: /Game/Materials/M_Terrain is missing (run Tools/import_assets.ps1); using the engine default material")); M = UMaterial::GetDefaultMaterial(MD_Surface); } TerrainMaterial = M; } } void ADescentTerrainActor::SetTerrainMaterial(UMaterialInterface* Material) { TerrainMaterial = Material; for (UProceduralMeshComponent* C : AllComps) { if (C) { C->SetMaterial(0, Material); } } } UProceduralMeshComponent* ADescentTerrainActor::AcquireChunkComponent(const FVector& OriginUU) { UProceduralMeshComponent* C = nullptr; if (Pool.Num() > 0) { C = Pool.Pop(EAllowShrinking::No); } else { C = NewObject(this, NAME_None, RF_Transient); C->SetMobility(EComponentMobility::Movable); C->bUseAsyncCooking = false; C->bUseComplexAsSimpleCollision = false; C->SetCollisionEnabled(ECollisionEnabled::NoCollision); C->SetGenerateOverlapEvents(false); C->SetCanEverAffectNavigation(false); C->SetCastShadow(true); C->bAffectDistanceFieldLighting = false; C->bAffectDynamicIndirectLighting = false; C->SetupAttachment(GetRootComponent()); C->RegisterComponent(); AllComps.Add(C); } if (TerrainMaterial) { C->SetMaterial(0, TerrainMaterial); } C->SetWorldLocation(OriginUU); C->SetVisibility(true); ++NumActiveComps; return C; } void ADescentTerrainActor::ReleaseChunkComponent(UProceduralMeshComponent* Comp) { if (!Comp) { return; } Comp->ClearAllMeshSections(); Comp->SetVisibility(false); Pool.Add(Comp); --NumActiveComps; } // Boot (DESIGN_UE.md section 1.5 step 2): the terrain actor exists before anything streams. DESCENT_BOOT_STEP(SpawnTerrainActor, Descent::Boot::Order::SpawnTerrainActor) { UDescentTerrainSubsystem* T = Ctx.World ? Ctx.World->GetSubsystem() : nullptr; if (!T) { Ctx.Fail(TEXT("no terrain subsystem")); return; } FActorSpawnParameters Params; Params.Name = TEXT("DescentTerrain"); ADescentTerrainActor* A = Ctx.World->SpawnActor(FVector::ZeroVector, FRotator::ZeroRotator, Params); if (!A) { Ctx.Fail(TEXT("could not spawn the terrain actor")); return; } T->SetTerrainActor(A); } // Boot step 4: the first rock, loaded before the player is placed. The carrier does not exist in this track's copy, // so it streams around the default start pose (0, 0, -30); the carrier track adds its own stream focus. DESCENT_BOOT_STEP(StreamInitial, Descent::Boot::Order::StreamInitial) { UDescentTerrainSubsystem* T = Ctx.World ? Ctx.World->GetSubsystem() : nullptr; if (!T) { Ctx.Fail(TEXT("no terrain subsystem")); return; } const double T0 = FPlatformTime::Seconds(); T->StreamInitialBlocking(FVector(0.0, 0.0, -30.0), Descent::Tune::Terrain::InitialRadiusM); UE_LOG(LogDescent, Display, TEXT("Terrain: initial stream %.0f ms, %d chunks meshed, %d triangles"), (FPlatformTime::Seconds() - T0) * 1000.0, T->GetStats().ChunksMeshed, T->GetStats().Triangles); }