#include "Vehicles/CarrierBlockout.h" #include "Vehicles/CarrierBuild.h" #include "Core/Tuning/DescentTune_Carrier.h" #include "DescentUnits.h" #include "TheDescent.h" #include "Components/PointLightComponent.h" #include "Components/SpotLightComponent.h" #include "Components/StaticMeshComponent.h" #include "Components/TextRenderComponent.h" #include "Engine/StaticMesh.h" #include "GameFramework/Actor.h" #include "HAL/IConsoleManager.h" #include "PhysicsEngine/BodySetup.h" #include "ProceduralMeshComponent.h" namespace { TAutoConsoleVariable CVarCarrierLampScale(TEXT("descent.Carrier.LampScale"), 1.0f, TEXT("Multiplies the brightness of every carrier lamp (calibration against the exposure; Tune::Carrier::LampBrightnessUnit)."), ECVF_Default); float CarrierBrightness(double Intensity) { return static_cast(Intensity * Descent::Tune::Carrier::LampBrightnessUnit) * CVarCarrierLampScale.GetValueOnGameThread(); } } UCarrierBlockoutComponent::UCarrierBlockoutComponent() { PrimaryComponentTick.bCanEverTick = false; } void UCarrierBlockoutComponent::MakeInterior(UPrimitiveComponent* Prim) { using namespace Descent::Tune::Carrier; Prim->SetLightingChannels(false, true, false); Prim->SetRenderCustomDepth(true); Prim->SetCustomDepthStencilValue(InteriorStencilValue); } void UCarrierBlockoutComponent::MakeExterior(UPrimitiveComponent* Prim) { Prim->SetLightingChannels(true, false, false); Prim->SetRenderCustomDepth(false); } void UCarrierBlockoutComponent::MakeSolid(UPrimitiveComponent* Prim) { Prim->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics); Prim->SetCollisionObjectType(ECC_WorldDynamic); Prim->SetCollisionResponseToAllChannels(ECR_Ignore); Prim->SetCollisionResponseToChannel(ECC_Pawn, ECR_Block); Prim->SetCollisionResponseToChannel(ECC_Visibility, ECR_Block); Prim->SetCollisionResponseToChannel(static_cast(ECC_GameTraceChannel1), ECR_Block); // "Placement" Prim->SetGenerateOverlapEvents(false); Prim->CanCharacterStepUpOn = ECB_Yes; } UProceduralMeshComponent* UCarrierBlockoutComponent::NewMesh(const FName& Name, bool bInterior, bool bSolid) { AActor* Owner = GetOwner(); UProceduralMeshComponent* C = NewObject(Owner, Name); C->SetMobility(EComponentMobility::Movable); C->bUseAsyncCooking = false; C->bUseComplexAsSimpleCollision = false; C->SetupAttachment(this); if (bSolid) { MakeSolid(C); } else { C->SetCollisionEnabled(ECollisionEnabled::NoCollision); } if (bInterior) { MakeInterior(C); InteriorPrims.Add(C); } else { MakeExterior(C); } C->RegisterComponent(); MeshComps.Add(C); return C; } void UCarrierBlockoutComponent::Build() { if (bBuilt) { return; } bBuilt = true; using namespace Descent::Carrier; const FLayout& L = FLayout::Get(); AActor* Owner = GetOwner(); // hull (exterior, no collision) { FMeshBuilder B; BuildHullMesh(B); // exterior light bulbs (the lights are components, see BuildLights) namespace T = Descent::Tune::Carrier; B.Box(EMat::NavRed, FVector(T::NavRedX, T::NavRedY, T::NavRedZ), FVector(0.5), false); B.Box(EMat::NavGreen, FVector(T::NavGreenX, T::NavGreenY, T::NavGreenZ), FVector(0.5), false); B.Box(EMat::NavWhite, FVector(T::SternX, T::SternY, T::SternZ), FVector(0.5), false); UProceduralMeshComponent* Hull = NewMesh(TEXT("Hull"), false, false); B.Commit(Hull, false); Hull->SetCastShadow(true); FMeshBuilder Bulb; Bulb.Box(EMat::NavWhite, FVector(T::MastX, T::MastY, T::MastZ), FVector(0.5), false); MastBulb = NewMesh(TEXT("MastBulb"), false, false); Bulb.Commit(MastBulb, false); } // rooms (interior, collision) for (const FRoomDef& R : L.Rooms) { if (!R.OverrideMesh.IsEmpty()) { // section 3.2: a real model replaces the blockout of this room; fixtures, lamps and the dry volume still come from the layout if (UStaticMesh* SM = LoadObject(nullptr, *R.OverrideMesh)) { UStaticMeshComponent* SMC = NewObject(Owner, FName(*(TEXT("Room_") + R.Id.ToString()))); SMC->SetStaticMesh(SM); SMC->SetupAttachment(this); MakeSolid(SMC); MakeInterior(SMC); InteriorPrims.Add(SMC); SMC->RegisterComponent(); continue; } UE_LOG(LogDescent, Error, TEXT("Carrier: room %s names OverrideMesh '%s' which does not load; using the blockout"), *R.Id.ToString(), *R.OverrideMesh); } FMeshBuilder B; BuildRoomMesh(R, B); UProceduralMeshComponent* C = NewMesh(FName(*(TEXT("Room_") + R.Id.ToString())), true, true); B.Commit(C, true); ++NumRooms; } { FMeshBuilder B; BuildDoorsMesh(B); UProceduralMeshComponent* C = NewMesh(TEXT("Doors"), true, true); B.Commit(C, true); } { FMeshBuilder Solid, Water; BuildPoolMesh(Solid, Water); UProceduralMeshComponent* C = NewMesh(TEXT("PoolShaft"), true, true); Solid.Commit(C, true); UProceduralMeshComponent* W = NewMesh(TEXT("PoolWater"), false, false); // the only water inside: seen through it is the sea Water.Commit(W, false); W->SetCastShadow(false); } // text signs { TArray Signs; CollectSigns(Signs); int32 Index = 0; for (const FSignDef& S : Signs) { UTextRenderComponent* T = NewObject(Owner, FName(*FString::Printf(TEXT("Sign_%d"), Index++))); T->SetupAttachment(this); T->SetText(FText::FromString(S.Text)); T->SetWorldSize(static_cast(S.HeightM * 100.0)); T->SetTextRenderColor(S.Colour.ToFColor(true)); T->SetHorizontalAlignment(EHTA_Center); T->SetVerticalAlignment(EVRTA_TextCenter); T->SetRelativeLocationAndRotation(Descent::MetresToUU(S.PosM), FRotator(0.0, S.YawDeg, 0.0)); T->SetCollisionEnabled(ECollisionEnabled::NoCollision); MakeInterior(T); InteriorPrims.Add(T); T->RegisterComponent(); ++NumSigns; } } BuildLights(); UE_LOG(LogDescent, Display, TEXT("Carrier: blockout built (%d rooms, %d mesh components, %d signs, %d room lamps, %d exterior lights)"), NumRooms, MeshComps.Num(), NumSigns, RoomLamps.Num(), ExteriorLights.Num()); } void UCarrierBlockoutComponent::BuildLights() { using namespace Descent::Carrier; namespace T = Descent::Tune::Carrier; const FLayout& L = FLayout::Get(); AActor* Owner = GetOwner(); auto MakePoint = [&](const FName& Name, const FVector& PosM, double RadiusM, const FLinearColor& Colour, float Brightness, bool bInterior) -> UPointLightComponent* { UPointLightComponent* P = NewObject(Owner, Name); P->SetupAttachment(this); P->SetMobility(EComponentMobility::Movable); P->bUseInverseSquaredFalloff = false; P->SetLightFalloffExponent(T::LampFalloffExponent); P->SetAttenuationRadius(static_cast(Descent::MetresToUU(RadiusM))); P->SetSourceRadius(T::LampSourceRadiusCm); P->SetLightColor(Colour); P->SetIntensity(Brightness); P->SetCastShadows(false); P->SetVolumetricScatteringIntensity(bInterior ? 0.f : 0.3f); P->SetLightingChannels(!bInterior, bInterior, false); P->SetRelativeLocation(Descent::MetresToUU(PosM)); P->RegisterComponent(); return P; }; for (const FRoomDef& R : L.Rooms) { int32 I = 0; for (const FLampDef& Lamp : R.Lamps) { const double Z = Lamp.ZAboveFloor >= 0.0 ? R.FloorZ + Lamp.ZAboveFloor : R.CeilZ - 0.12; const float Base = CarrierBrightness(R.LampIntensity); UPointLightComponent* P = MakePoint(FName(*FString::Printf(TEXT("Lamp_%s_%d"), *R.Id.ToString(), I++)), FVector(Lamp.X, Lamp.Y, Z - 0.35), R.LampRadiusM(), R.LampColor, Base, true); RoomLamps.Add(P); FLampInfo Info; Info.Light = P; Info.BaseBrightness = Base; Info.bPulses = R.bPulses; Info.Room = R.Id; LampInfos.Add(Info); } } // the six exterior lights (V4a); nothing else lights the hull from outside const float NavBase = CarrierBrightness(T::NavIntensity); ExteriorLights.Add(MakePoint(TEXT("NavPort"), FVector(T::NavRedX, T::NavRedY, T::NavRedZ), T::NavRadiusM, FLinearColor(1.f, 0.05f, 0.03f), NavBase, false)); ExteriorLights.Add(MakePoint(TEXT("NavStarboard"), FVector(T::NavGreenX, T::NavGreenY, T::NavGreenZ), T::NavRadiusM, FLinearColor(0.1f, 1.f, 0.2f), NavBase, false)); MastLight = MakePoint(TEXT("Mast"), FVector(T::MastX, T::MastY, T::MastZ), T::NavRadiusM, FLinearColor::White, NavBase, false); MastBase = NavBase; ExteriorLights.Add(MastLight); SternLight = MakePoint(TEXT("Stern"), FVector(T::SternX, T::SternY, T::SternZ), T::NavRadiusM, FLinearColor::White, NavBase, false); SternBase = NavBase; ExteriorLights.Add(SternLight); { USpotLightComponent* Flood = NewObject(Owner, TEXT("Flood")); Flood->SetupAttachment(this); Flood->SetMobility(EComponentMobility::Movable); Flood->bUseInverseSquaredFalloff = false; Flood->SetLightFalloffExponent(T::LampFalloffExponent); Flood->SetAttenuationRadius(static_cast(Descent::MetresToUU(T::FloodRadiusM))); Flood->SetInnerConeAngle(static_cast(T::FloodInnerConeDeg)); Flood->SetOuterConeAngle(static_cast(T::FloodOuterConeDeg)); Flood->SetLightColor(FLinearColor::FromSRGBColor(FColor(0xdf, 0xf4, 0xff))); Flood->SetIntensity(CarrierBrightness(T::FloodIntensity)); Flood->SetCastShadows(true); Flood->SetVolumetricScatteringIntensity(1.2f); Flood->SetLightingChannels(true, false, false); const FVector Pos(T::FloodX, T::FloodY, T::FloodZ); Flood->SetRelativeLocationAndRotation(Descent::MetresToUU(Pos), (FVector(T::FloodAimX, T::FloodAimY, T::FloodAimZ) - Pos).Rotation()); Flood->RegisterComponent(); ExteriorLights.Add(Flood); } ExteriorLights.Add(MakePoint(TEXT("PoolGlow"), FVector(T::PoolGlowX, T::PoolGlowY, T::PoolGlowZ), T::PoolGlowRadiusM, FLinearColor(0.55f, 0.85f, 0.9f), CarrierBrightness(T::PoolGlowIntensity), false)); } void UCarrierBlockoutComponent::SetViewerRoom(FName Room) { if (Room.IsNone() || Room == ViewerRoom) { return; } ViewerRoom = Room; for (const FLampInfo& Info : LampInfos) { if (UPointLightComponent* L = Info.Light.Get()) { L->SetLightingChannels(false, true, Info.Room == Room); } } } void UCarrierBlockoutComponent::UpdateCosmetics(double Time) { namespace T = Descent::Tune::Carrier; for (const FLampInfo& Info : LampInfos) { if (Info.bPulses && Info.Light.IsValid()) { Info.Light->SetIntensity(Info.BaseBrightness * static_cast(0.85 + 0.15 * FMath::Sin(2.4 * Time))); } } const bool bFlash = FMath::Fmod(Time, T::MastFlashPeriodSec) < T::MastFlashOnSec; if (MastLight) { MastLight->SetIntensity(bFlash ? MastBase : 0.f); } if (MastBulb) { MastBulb->SetVisibility(bFlash); } if (SternLight) { SternLight->SetIntensity(SternBase * static_cast(0.6 + 0.4 * FMath::Sin(0.9 * Time))); } } FCarrierBlockoutStats UCarrierBlockoutComponent::GatherStats() const { FCarrierBlockoutStats S; S.Rooms = NumRooms; S.MeshComponents = MeshComps.Num(); for (const UProceduralMeshComponent* C : MeshComps) { const int32 N = const_cast(C)->GetNumSections(); S.Sections += N; if (C->ProcMeshBodySetup) { S.ConvexHulls += C->ProcMeshBodySetup->AggGeom.ConvexElems.Num(); } for (int32 I = 0; I < N; ++I) { if (const FProcMeshSection* Sec = const_cast(C)->GetProcMeshSection(I)) { S.Triangles += Sec->ProcIndexBuffer.Num() / 3; } } } for (const UPrimitiveComponent* P : InteriorPrims) { ++S.InteriorPrimitives; if (P->bRenderCustomDepth && P->CustomDepthStencilValue == Descent::Tune::Carrier::InteriorStencilValue) { ++S.InteriorWithStencil; } const FLightingChannels& Ch = P->LightingChannels; if (!Ch.bChannel0 && Ch.bChannel1 && !Ch.bChannel2) { ++S.InteriorOnChannel1Only; } } S.RoomLamps = RoomLamps.Num(); S.ExteriorLights = ExteriorLights.Num(); S.Signs = NumSigns; return S; }