// Copyright Epic Games, Inc. All Rights Reserved. #include "WaterBodyCustomComponent.h" #include "Components/StaticMeshComponent.h" #include "Materials/MaterialInstanceDynamic.h" #include "Misc/UObjectToken.h" #include "WaterBodyActor.h" #include "WaterBodyMeshComponent.h" #include "WaterSplineComponent.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(WaterBodyCustomComponent) #if WITH_EDITOR #endif #define LOCTEXT_NAMESPACE "Water" // ---------------------------------------------------------------------------------- UWaterBodyCustomComponent::UWaterBodyCustomComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { bAffectsLandscape = false; // @todo_water : Remove these checks (Once AWaterBody is no more Blueprintable, these methods should become PURE_VIRTUAL and this class should overload them) check(!IsFlatSurface()); check(!IsWaterSplineClosedLoop()); check(!IsHeightOffsetSupported()); } TArray UWaterBodyCustomComponent::GetCollisionComponents(bool bInOnlyEnabledComponents) const { TArray Result; if ((MeshComp != nullptr) && (!bInOnlyEnabledComponents || (MeshComp->GetCollisionEnabled() != ECollisionEnabled::NoCollision))) { Result.Add(MeshComp); } return Result; } TArray UWaterBodyCustomComponent::GetStandardRenderableComponents() const { TArray Result; if (MeshComp != nullptr) { Result.Add(MeshComp); } return Result; } FBoxSphereBounds UWaterBodyCustomComponent::CalcBounds(const FTransform& LocalToWorld) const { if (MeshComp) { return MeshComp->CalcBounds(MeshComp->GetRelativeTransform()).TransformBy(LocalToWorld); } return Super::CalcBounds(LocalToWorld); } void UWaterBodyCustomComponent::Reset() { AActor* Owner = GetOwner(); check(Owner); TArray MeshComponents; Owner->GetComponents(MeshComponents); MeshComp = nullptr; for (UStaticMeshComponent* MeshComponent : MeshComponents) { MeshComponent->DestroyComponent(); } } void UWaterBodyCustomComponent::OnUpdateBody(bool bWithExclusionVolumes) { AActor* OwnerActor = GetOwner(); check(OwnerActor); if (!MeshComp) { MeshComp = NewObject(OwnerActor, TEXT("CustomMeshComponent"), RF_Transactional); MeshComp->SetNetAddressable(); // it's deterministically named so it's addressable over network (needed for collision) MeshComp->SetupAttachment(this); MeshComp->bEnableAutoLODGeneration = false; // The water body component is already responsible for generating HLOD of the WaterBody actor. if(IsRegistered()) { MeshComp->RegisterComponent(); } } TInlineComponentArray PrimitiveComponents; OwnerActor->GetComponents(PrimitiveComponents); // Make no assumptions for custom water bodies: all (non-visualization) primitive components will be included in navigation except the water info components for (UPrimitiveComponent* Comp : PrimitiveComponents) { #if WITH_EDITORONLY_DATA if (Comp->IsVisualizationComponent()) { continue; } #endif // WITH_EDITORONLY_DATA if (Comp->IsA()) { continue; } // Do not copy Nav setting over Water Spline Component, its just a spline component and will report Navigation warning if only one spline point because of empty bounds box if (!Comp->IsA()) { CopySharedNavigationSettingsToComponent(Comp); } Comp->SetMobility(Mobility); } CreateOrUpdateWaterMID(); MeshComp->SetStaticMesh(GetWaterMeshOverride()); MeshComp->SetCastShadow(false); CopySharedCollisionSettingsToComponent(MeshComp); CopySharedNavigationSettingsToComponent(MeshComp); MeshComp->MarkRenderStateDirty(); } void UWaterBodyCustomComponent::CreateOrUpdateWaterMID() { Super::CreateOrUpdateWaterMID(); if (MeshComp != nullptr) { MeshComp->SetMaterial(0, WaterMID); } } FPrimitiveSceneProxy* UWaterBodyCustomComponent::CreateSceneProxy() { // Don't create a scene proxy for custom water body components since they don't render into the water info texture (yet) return nullptr; } void UWaterBodyCustomComponent::BeginUpdateWaterBody() { Super::BeginUpdateWaterBody(); UMaterialInstanceDynamic* WaterMaterialInstance = GetWaterMaterialInstance(); if (WaterMaterialInstance && MeshComp) { // We need to get(or create) the water MID at runtime and apply it to the static mesh component // The MID is transient so it will not make it through serialization, apply it here (at runtime) MeshComp->SetMaterial(0, WaterMaterialInstance); } } #if WITH_EDITOR TArray> UWaterBodyCustomComponent::CheckWaterBodyStatus() { TArray> StatusMessages = Super::CheckWaterBodyStatus(); if (!IsTemplate()) { if (WaterMeshOverride == nullptr && GetWaterBodyActor() != nullptr) { StatusMessages.Add(FTokenizedMessage::Create(EMessageSeverity::Error) ->AddToken(FUObjectToken::Create(this)) ->AddToken(FTextToken::Create(FText::Format( LOCTEXT("MapCheck_Message_MissingCustomWaterMesh", "Custom water body {0} requires a static mesh to be rendered. Please set WaterMeshOverride to a valid static mesh. "), FText::FromString(GetWaterBodyActor()->GetActorLabel()))))); } } return StatusMessages; } const TCHAR* UWaterBodyCustomComponent::GetWaterSpriteTextureName() const { return TEXT("/Water/Icons/WaterBodyCustomSprite"); } bool UWaterBodyCustomComponent::IsIconVisible() const { return (GetWaterMeshOverride() == nullptr); } void UWaterBodyCustomComponent::PostLoad() { Super::PostLoad(); // Make sure the custom mesh component is not HLOD relevant // The water body component is already responsible for generating // the HLOD of the WaterBody actor. if (MeshComp && MeshComp->IsHLODRelevant()) { MeshComp->bEnableAutoLODGeneration = false; } } #endif // WITH_EDITOR #undef LOCTEXT_NAMESPACE