You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
4752 lines
166 KiB
C++
4752 lines
166 KiB
C++
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
|
|
|
|
/*=============================================================================
|
|
LandscapeEdit.cpp: Landscape editing
|
|
=============================================================================*/
|
|
|
|
#include "Landscape.h"
|
|
#include "LandscapeProxy.h"
|
|
#include "Materials/MaterialInstanceDynamic.h"
|
|
#include "Materials/MaterialExpressionLandscapeVisibilityMask.h"
|
|
#include "Materials/MaterialExpressionLandscapeLayerWeight.h"
|
|
#include "Materials/MaterialExpressionLandscapeLayerSample.h"
|
|
#include "Materials/MaterialExpressionLandscapeLayerBlend.h"
|
|
#include "Materials/MaterialExpressionLandscapeLayerSwitch.h"
|
|
#include "LandscapeDataAccess.h"
|
|
#include "LandscapeEdit.h"
|
|
#include "LandscapeRender.h"
|
|
#include "LandscapeRenderMobile.h"
|
|
#include "LandscapeInfo.h"
|
|
#include "LandscapeLayerInfoObject.h"
|
|
#include "LandscapeMaterialInstanceConstant.h"
|
|
#include "LandscapeHeightfieldCollisionComponent.h"
|
|
#include "LandscapeMeshCollisionComponent.h"
|
|
#include "LandscapeSplinesComponent.h"
|
|
#include "LandscapeGizmoActiveActor.h"
|
|
#include "InstancedFoliageActor.h"
|
|
#include "LevelUtils.h"
|
|
#include "MessageLog.h"
|
|
#include "MapErrors.h"
|
|
#if WITH_EDITOR
|
|
#include "RawMesh.h"
|
|
#include "ScopedTransaction.h"
|
|
#include "ImageWrapper.h"
|
|
#include "Engine/Level.h"
|
|
#include "EngineUtils.h"
|
|
#include "Engine/Engine.h"
|
|
#include "EngineGlobals.h"
|
|
#include "ShowFlags.h"
|
|
#include "ConvexVolume.h"
|
|
#endif
|
|
#include "ComponentReregisterContext.h"
|
|
|
|
DEFINE_LOG_CATEGORY(LogLandscape);
|
|
|
|
#define LOCTEXT_NAMESPACE "Landscape"
|
|
|
|
#if WITH_EDITOR
|
|
|
|
ULandscapeLayerInfoObject* ALandscapeProxy::VisibilityLayer = NULL;
|
|
|
|
void ULandscapeComponent::Init(int32 InBaseX, int32 InBaseY, int32 InComponentSizeQuads, int32 InNumSubsections, int32 InSubsectionSizeQuads)
|
|
{
|
|
SetSectionBase(FIntPoint(InBaseX, InBaseY));
|
|
FVector RelativeLocation = FVector(GetSectionBase() - GetLandscapeProxy()->LandscapeSectionOffset);
|
|
SetRelativeLocation(RelativeLocation);
|
|
ComponentSizeQuads = InComponentSizeQuads;
|
|
NumSubsections = InNumSubsections;
|
|
SubsectionSizeQuads = InSubsectionSizeQuads;
|
|
check(NumSubsections * SubsectionSizeQuads == ComponentSizeQuads);
|
|
ULandscapeInfo* Info = GetLandscapeInfo();
|
|
}
|
|
|
|
void ULandscapeComponent::UpdateCachedBounds()
|
|
{
|
|
FLandscapeComponentDataInterface CDI(this);
|
|
|
|
// Update local-space bounding box
|
|
CachedLocalBox.Init();
|
|
for (int32 y = 0; y < ComponentSizeQuads + 1; y++)
|
|
{
|
|
for (int32 x = 0; x < ComponentSizeQuads + 1; x++)
|
|
{
|
|
CachedLocalBox += CDI.GetLocalVertex(x, y);
|
|
}
|
|
}
|
|
|
|
// Update collision component bounds
|
|
ULandscapeHeightfieldCollisionComponent* HFCollisionComponent = CollisionComponent.Get();
|
|
if (HFCollisionComponent)
|
|
{
|
|
HFCollisionComponent->Modify();
|
|
HFCollisionComponent->CachedLocalBox = CachedLocalBox;
|
|
HFCollisionComponent->UpdateComponentToWorld();
|
|
}
|
|
}
|
|
|
|
void ULandscapeComponent::UpdateNavigationRelevance()
|
|
{
|
|
ALandscapeProxy* Proxy = GetLandscapeProxy();
|
|
if (CollisionComponent && Proxy)
|
|
{
|
|
CollisionComponent->bCanEverAffectNavigation = Proxy->bUsedForNavigation;
|
|
|
|
UNavigationSystem::UpdateNavOctree(CollisionComponent.Get());
|
|
}
|
|
}
|
|
|
|
ULandscapeMaterialInstanceConstant* ALandscapeProxy::GetLayerThumbnailMIC(UMaterialInterface* LandscapeMaterial, FName LayerName, UTexture2D* ThumbnailWeightmap, UTexture2D* ThumbnailHeightmap, ALandscapeProxy* Proxy)
|
|
{
|
|
if (!LandscapeMaterial)
|
|
{
|
|
LandscapeMaterial = Proxy ? Proxy->GetLandscapeMaterial() : UMaterial::GetDefaultMaterial(MD_Surface);
|
|
}
|
|
|
|
UMaterialInstanceConstant* CombinationMaterialInstance = NULL;
|
|
FString LayerKey = LandscapeMaterial->GetPathName() + FString::Printf(TEXT("_%s_0"), *LayerName.ToString());
|
|
if (Proxy)
|
|
{
|
|
CombinationMaterialInstance = Proxy->MaterialInstanceConstantMap.FindRef(*LayerKey);
|
|
}
|
|
|
|
if (CombinationMaterialInstance == NULL || CombinationMaterialInstance->Parent != LandscapeMaterial || (Proxy && Proxy->GetOutermost() != CombinationMaterialInstance->GetOutermost()))
|
|
{
|
|
FlushRenderingCommands();
|
|
UObject* MICOuter = GetTransientPackage();
|
|
if (Proxy)
|
|
{
|
|
MICOuter = Proxy->GetOutermost();
|
|
}
|
|
CombinationMaterialInstance = NewObject<ULandscapeMaterialInstanceConstant>(MICOuter);
|
|
if (Proxy)
|
|
{
|
|
UE_LOG(LogLandscape, Log, TEXT("Looking for key %s, making new combination %s"), *LayerKey, *CombinationMaterialInstance->GetName());
|
|
Proxy->MaterialInstanceConstantMap.Add(*LayerKey, CombinationMaterialInstance);
|
|
}
|
|
CombinationMaterialInstance->SetParentEditorOnly(LandscapeMaterial);
|
|
|
|
FStaticParameterSet StaticParameters;
|
|
CombinationMaterialInstance->GetStaticParameterValues(StaticParameters);
|
|
|
|
for (int32 LayerParameterIdx = 0; LayerParameterIdx < StaticParameters.TerrainLayerWeightParameters.Num(); LayerParameterIdx++)
|
|
{
|
|
FStaticTerrainLayerWeightParameter& LayerParameter = StaticParameters.TerrainLayerWeightParameters[LayerParameterIdx];
|
|
if (LayerParameter.ParameterName == LayerName)
|
|
{
|
|
LayerParameter.WeightmapIndex = 0;
|
|
LayerParameter.bOverride = true;
|
|
}
|
|
else
|
|
{
|
|
LayerParameter.WeightmapIndex = INDEX_NONE;
|
|
}
|
|
}
|
|
|
|
CombinationMaterialInstance->UpdateStaticPermutation(StaticParameters);
|
|
|
|
CombinationMaterialInstance->PostEditChange();
|
|
}
|
|
|
|
// Create the instance for this component, that will use the layer combination instance.
|
|
ULandscapeMaterialInstanceConstant* MaterialInstance = NewObject<ULandscapeMaterialInstanceConstant>();
|
|
MaterialInstance->SetParentEditorOnly(CombinationMaterialInstance);
|
|
MaterialInstance->bIsLayerThumbnail = true;
|
|
|
|
FLinearColor Mask(1.0f, 0.0f, 0.0f, 0.0f);
|
|
MaterialInstance->SetVectorParameterValueEditorOnly(FName(*FString::Printf(TEXT("LayerMask_%s"), *LayerName.ToString())), Mask);
|
|
MaterialInstance->SetTextureParameterValueEditorOnly(FName(TEXT("Weightmap0")), ThumbnailWeightmap);
|
|
MaterialInstance->SetTextureParameterValueEditorOnly(FName(TEXT("Heightmap")), ThumbnailHeightmap);
|
|
MaterialInstance->PostEditChange();
|
|
|
|
return MaterialInstance;
|
|
}
|
|
|
|
UMaterialInstanceConstant* ULandscapeComponent::GetCombinationMaterial(bool bMobile /*= false*/)
|
|
{
|
|
check(GIsEditor);
|
|
|
|
ALandscapeProxy* Proxy = GetLandscapeProxy();
|
|
|
|
const bool bComponentHasHoles = ComponentHasVisibilityPainted();
|
|
UMaterialInterface* const LandscapeMaterial = GetLandscapeMaterial();
|
|
UMaterialInterface* const HoleMaterial = bComponentHasHoles ? GetLandscapeHoleMaterial() : nullptr;
|
|
UMaterialInterface* const MaterialToUse = bComponentHasHoles && HoleMaterial ? HoleMaterial : LandscapeMaterial;
|
|
const bool bOverrideBlendMode = bComponentHasHoles && !HoleMaterial && LandscapeMaterial->GetBlendMode() == BLEND_Opaque;
|
|
|
|
if (ensure(MaterialToUse != nullptr))
|
|
{
|
|
// Ensure top level UMaterial has appropriate usage flags set.
|
|
bool bNeedsRecompile;
|
|
UMaterial* ParentUMaterial = MaterialToUse->GetMaterial();
|
|
if (ParentUMaterial && ParentUMaterial != UMaterial::GetDefaultMaterial(MD_Surface))
|
|
{
|
|
ParentUMaterial->SetMaterialUsage(bNeedsRecompile, MATUSAGE_Landscape);
|
|
ParentUMaterial->SetMaterialUsage(bNeedsRecompile, MATUSAGE_StaticLighting);
|
|
}
|
|
|
|
FString LayerKey = GetLayerAllocationKey(MaterialToUse, bMobile);
|
|
//UE_LOG(LogLandscape, Log, TEXT("Looking for key %s"), *LayerKey);
|
|
|
|
// Find or set a matching MIC in the Landscape's map.
|
|
UMaterialInstanceConstant* CombinationMaterialInstance = Proxy->MaterialInstanceConstantMap.FindRef(*LayerKey);
|
|
if (CombinationMaterialInstance == nullptr || CombinationMaterialInstance->Parent != MaterialToUse || GetOutermost() != CombinationMaterialInstance->GetOutermost())
|
|
{
|
|
FlushRenderingCommands();
|
|
|
|
CombinationMaterialInstance = NewObject<ULandscapeMaterialInstanceConstant>(GetOutermost());
|
|
UE_LOG(LogLandscape, Log, TEXT("Looking for key %s, making new combination %s"), *LayerKey, *CombinationMaterialInstance->GetName());
|
|
Proxy->MaterialInstanceConstantMap.Add(*LayerKey, CombinationMaterialInstance);
|
|
CombinationMaterialInstance->SetParentEditorOnly(MaterialToUse);
|
|
|
|
FStaticParameterSet StaticParameters;
|
|
CombinationMaterialInstance->GetStaticParameterValues(StaticParameters);
|
|
|
|
// Find weightmap mapping for each layer parameter, or disable if the layer is not used in this component.
|
|
for (int32 LayerParameterIdx = 0; LayerParameterIdx < StaticParameters.TerrainLayerWeightParameters.Num(); LayerParameterIdx++)
|
|
{
|
|
FStaticTerrainLayerWeightParameter& LayerParameter = StaticParameters.TerrainLayerWeightParameters[LayerParameterIdx];
|
|
LayerParameter.WeightmapIndex = INDEX_NONE;
|
|
|
|
// Look through our allocations to see if we need this layer.
|
|
// If not found, this component doesn't use the layer, and WeightmapIndex remains as INDEX_NONE.
|
|
for (int32 AllocIdx = 0; AllocIdx < WeightmapLayerAllocations.Num(); AllocIdx++)
|
|
{
|
|
FWeightmapLayerAllocationInfo& Allocation = WeightmapLayerAllocations[AllocIdx];
|
|
if (Allocation.LayerInfo != NULL)
|
|
{
|
|
FName ThisLayerName = (Allocation.LayerInfo == ALandscapeProxy::VisibilityLayer) ? UMaterialExpressionLandscapeVisibilityMask::ParameterName : Allocation.LayerInfo->LayerName;
|
|
if (ThisLayerName == LayerParameter.ParameterName)
|
|
{
|
|
LayerParameter.WeightmapIndex = Allocation.WeightmapTextureIndex;
|
|
LayerParameter.bOverride = true;
|
|
// UE_LOG(LogLandscape, Log, TEXT(" Layer %s channel %d"), *LayerParameter.ParameterName.ToString(), LayerParameter.WeightmapIndex);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CombinationMaterialInstance->BasePropertyOverrides.bOverride_BlendMode = bOverrideBlendMode;
|
|
if (bOverrideBlendMode)
|
|
{
|
|
CombinationMaterialInstance->BasePropertyOverrides.BlendMode = bComponentHasHoles ? BLEND_Masked : BLEND_Opaque;
|
|
}
|
|
|
|
CombinationMaterialInstance->UpdateStaticPermutation(StaticParameters);
|
|
|
|
CombinationMaterialInstance->PostEditChange();
|
|
}
|
|
|
|
return CombinationMaterialInstance;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void ULandscapeComponent::UpdateMaterialInstances()
|
|
{
|
|
check(GIsEditor);
|
|
|
|
// Find or set a matching MIC in the Landscape's map.
|
|
UMaterialInstanceConstant* CombinationMaterialInstance = GetCombinationMaterial(false);
|
|
|
|
if (CombinationMaterialInstance != NULL)
|
|
{
|
|
// Create the instance for this component, that will use the layer combination instance.
|
|
if (MaterialInstance == NULL || GetOutermost() != MaterialInstance->GetOutermost())
|
|
{
|
|
MaterialInstance = NewObject<ULandscapeMaterialInstanceConstant>(GetOutermost());
|
|
}
|
|
|
|
// For undo
|
|
MaterialInstance->SetFlags(RF_Transactional);
|
|
MaterialInstance->Modify();
|
|
|
|
MaterialInstance->SetParentEditorOnly(CombinationMaterialInstance);
|
|
|
|
FLinearColor Masks[4];
|
|
Masks[0] = FLinearColor(1.0f, 0.0f, 0.0f, 0.0f);
|
|
Masks[1] = FLinearColor(0.0f, 1.0f, 0.0f, 0.0f);
|
|
Masks[2] = FLinearColor(0.0f, 0.0f, 1.0f, 0.0f);
|
|
Masks[3] = FLinearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
|
|
|
// Set the layer mask
|
|
for (int32 AllocIdx = 0; AllocIdx < WeightmapLayerAllocations.Num(); AllocIdx++)
|
|
{
|
|
FWeightmapLayerAllocationInfo& Allocation = WeightmapLayerAllocations[AllocIdx];
|
|
|
|
FName LayerName = Allocation.LayerInfo == ALandscapeProxy::VisibilityLayer ? UMaterialExpressionLandscapeVisibilityMask::ParameterName : Allocation.LayerInfo ? Allocation.LayerInfo->LayerName : NAME_None;
|
|
MaterialInstance->SetVectorParameterValueEditorOnly(FName(*FString::Printf(TEXT("LayerMask_%s"), *LayerName.ToString())), Masks[Allocation.WeightmapTextureChannel]);
|
|
}
|
|
|
|
// Set the weightmaps
|
|
for (int32 i = 0; i < WeightmapTextures.Num(); i++)
|
|
{
|
|
// UE_LOG(LogLandscape, Log, TEXT("Setting Weightmap%d = %s"), i, *WeightmapTextures(i)->GetName());
|
|
MaterialInstance->SetTextureParameterValueEditorOnly(FName(*FString::Printf(TEXT("Weightmap%d"), i)), WeightmapTextures[i]);
|
|
}
|
|
// Set the heightmap, if needed.
|
|
|
|
if (HeightmapTexture)
|
|
{
|
|
MaterialInstance->SetTextureParameterValueEditorOnly(FName(TEXT("Heightmap")), HeightmapTexture);
|
|
}
|
|
MaterialInstance->PostEditChange();
|
|
|
|
// Recreate the render state, needed to update the static drawlist which has cached the MaterialRenderProxy.
|
|
RecreateRenderState_Concurrent();
|
|
}
|
|
}
|
|
|
|
int32 ULandscapeComponent::GetNumMaterials() const
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
class UMaterialInterface* ULandscapeComponent::GetMaterial(int32 ElementIndex) const
|
|
{
|
|
if (ensure(ElementIndex == 0))
|
|
{
|
|
return GetLandscapeMaterial();
|
|
}
|
|
else
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
void ULandscapeComponent::SetMaterial(int32 ElementIndex, class UMaterialInterface* Material)
|
|
{
|
|
if (ensure(ElementIndex == 0))
|
|
{
|
|
GetLandscapeProxy()->LandscapeMaterial = Material;
|
|
}
|
|
}
|
|
|
|
bool ULandscapeComponent::ComponentIsTouchingSelectionBox(const FBox& InSelBBox, const FEngineShowFlags& ShowFlags, const bool bConsiderOnlyBSP, const bool bMustEncompassEntireComponent) const
|
|
{
|
|
if (ShowFlags.Landscape)
|
|
{
|
|
return Super::ComponentIsTouchingSelectionBox(InSelBBox, ShowFlags, bConsiderOnlyBSP, bMustEncompassEntireComponent);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool ULandscapeComponent::ComponentIsTouchingSelectionFrustum(const FConvexVolume& InFrustum, const FEngineShowFlags& ShowFlags, const bool bConsiderOnlyBSP, const bool bMustEncompassEntireComponent) const
|
|
{
|
|
if (ShowFlags.Landscape)
|
|
{
|
|
return Super::ComponentIsTouchingSelectionFrustum(InFrustum, ShowFlags, bConsiderOnlyBSP, bMustEncompassEntireComponent);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void ULandscapeComponent::PostEditUndo()
|
|
{
|
|
UpdateMaterialInstances();
|
|
|
|
Super::PostEditUndo();
|
|
|
|
if (EditToolRenderData)
|
|
{
|
|
EditToolRenderData->UpdateDebugColorMaterial();
|
|
EditToolRenderData->UpdateSelectionMaterial(EditToolRenderData->SelectedType);
|
|
}
|
|
|
|
TSet<ULandscapeComponent*> Components;
|
|
Components.Add(this);
|
|
GetLandscapeProxy()->FlushGrassComponents(&Components);
|
|
}
|
|
|
|
void ULandscapeComponent::FixupWeightmaps()
|
|
{
|
|
if (GIsEditor && !HasAnyFlags(RF_ClassDefaultObject))
|
|
{
|
|
ULandscapeInfo* Info = GetLandscapeInfo();
|
|
ALandscapeProxy* Proxy = GetLandscapeProxy();
|
|
|
|
if (Info)
|
|
{
|
|
TArray<ULandscapeLayerInfoObject*> DeletedLayers;
|
|
bool bFixedLayerDeletion = false;
|
|
|
|
if (Info->Layers.Num() && Cast<ALandscape>(Proxy))
|
|
{
|
|
// LayerName Validation check...
|
|
for (int32 LayerIdx = 0; LayerIdx < WeightmapLayerAllocations.Num(); LayerIdx++)
|
|
{
|
|
if (!WeightmapLayerAllocations[LayerIdx].LayerInfo
|
|
|| (WeightmapLayerAllocations[LayerIdx].LayerInfo != ALandscapeProxy::VisibilityLayer && Info->GetLayerInfoIndex(WeightmapLayerAllocations[LayerIdx].LayerInfo) == INDEX_NONE))
|
|
{
|
|
if (!bFixedLayerDeletion)
|
|
{
|
|
FFormatNamedArguments Arguments;
|
|
Arguments.Add(TEXT("LandscapeName"), FText::FromString(GetName()));
|
|
FMessageLog("MapCheck").Warning()
|
|
->AddToken(FTextToken::Create(FText::Format(LOCTEXT("MapCheck_Message_FixedUpDeletedLayerWeightmap", "{LandscapeName} : Fixed up deleted layer weightmap"), Arguments)))
|
|
->AddToken(FMapErrorToken::Create(FMapErrors::FixedUpDeletedLayerWeightmap));
|
|
}
|
|
|
|
bFixedLayerDeletion = true;
|
|
DeletedLayers.Add(WeightmapLayerAllocations[LayerIdx].LayerInfo);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bFixedLayerDeletion)
|
|
{
|
|
FLandscapeEditDataInterface LandscapeEdit(Info);
|
|
for (int32 Idx = 0; Idx < DeletedLayers.Num(); ++Idx)
|
|
{
|
|
DeleteLayer(DeletedLayers[Idx], &LandscapeEdit);
|
|
}
|
|
}
|
|
|
|
bool bFixedWeightmapTextureIndex = false;
|
|
|
|
// Store the weightmap allocations in WeightmapUsageMap
|
|
for (int32 LayerIdx = 0; LayerIdx < WeightmapLayerAllocations.Num(); LayerIdx++)
|
|
{
|
|
FWeightmapLayerAllocationInfo& Allocation = WeightmapLayerAllocations[LayerIdx];
|
|
|
|
// Fix up any problems caused by the layer deletion bug.
|
|
if (Allocation.WeightmapTextureIndex >= WeightmapTextures.Num())
|
|
{
|
|
Allocation.WeightmapTextureIndex = WeightmapTextures.Num() - 1;
|
|
if (!bFixedWeightmapTextureIndex)
|
|
{
|
|
FFormatNamedArguments Arguments;
|
|
Arguments.Add(TEXT("LandscapeName"), FText::FromString(GetName()));
|
|
FMessageLog("MapCheck").Warning()
|
|
->AddToken(FTextToken::Create(FText::Format(LOCTEXT("MapCheck_Message_FixedUpIncorrectLayerWeightmap", "{LandscapeName} : Fixed up incorrect layer weightmap texture index"), Arguments)))
|
|
->AddToken(FMapErrorToken::Create(FMapErrors::FixedUpIncorrectLayerWeightmap));
|
|
}
|
|
bFixedWeightmapTextureIndex = true;
|
|
}
|
|
|
|
UTexture2D* WeightmapTexture = WeightmapTextures[Allocation.WeightmapTextureIndex];
|
|
FLandscapeWeightmapUsage& Usage = Proxy->WeightmapUsageMap.FindOrAdd(WeightmapTexture);
|
|
|
|
// Detect a shared layer allocation, caused by a previous undo or layer deletion bugs
|
|
if (Usage.ChannelUsage[Allocation.WeightmapTextureChannel] != NULL &&
|
|
Usage.ChannelUsage[Allocation.WeightmapTextureChannel] != this)
|
|
{
|
|
FFormatNamedArguments Arguments;
|
|
Arguments.Add(TEXT("LayerName"), FText::FromString(Allocation.GetLayerName().ToString()));
|
|
Arguments.Add(TEXT("LandscapeName"), FText::FromString(GetName()));
|
|
Arguments.Add(TEXT("ChannelName"), FText::FromString(Usage.ChannelUsage[Allocation.WeightmapTextureChannel]->GetName()));
|
|
FMessageLog("MapCheck").Warning()
|
|
->AddToken(FTextToken::Create(FText::Format(LOCTEXT("MapCheck_Message_FixedUpSharedLayerWeightmap", "Fixed up shared weightmap texture for layer {LayerName} in component '{LandscapeName}' (shares with '{ChannelName}')"), Arguments)))
|
|
->AddToken(FMapErrorToken::Create(FMapErrors::FixedUpSharedLayerWeightmap));
|
|
WeightmapLayerAllocations.RemoveAt(LayerIdx);
|
|
LayerIdx--;
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
Usage.ChannelUsage[Allocation.WeightmapTextureChannel] = this;
|
|
}
|
|
}
|
|
|
|
RemoveInvalidWeightmaps();
|
|
|
|
// Store the layer combination in the MaterialInstanceConstantMap
|
|
if (MaterialInstance != NULL)
|
|
{
|
|
UMaterialInstanceConstant* CombinationMaterialInstance = Cast<UMaterialInstanceConstant>(MaterialInstance->Parent);
|
|
if (CombinationMaterialInstance)
|
|
{
|
|
Proxy->MaterialInstanceConstantMap.Add(*GetLayerAllocationKey(CombinationMaterialInstance->Parent), CombinationMaterialInstance);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// LandscapeComponentAlphaInfo
|
|
//
|
|
struct FLandscapeComponentAlphaInfo
|
|
{
|
|
int32 LayerIndex;
|
|
TArray<uint8> AlphaValues;
|
|
|
|
// tor
|
|
FLandscapeComponentAlphaInfo(ULandscapeComponent* InOwner, int32 InLayerIndex)
|
|
: LayerIndex(InLayerIndex)
|
|
{
|
|
AlphaValues.Empty(FMath::Square(InOwner->ComponentSizeQuads + 1));
|
|
AlphaValues.AddZeroed(FMath::Square(InOwner->ComponentSizeQuads + 1));
|
|
}
|
|
|
|
bool IsLayerAllZero() const
|
|
{
|
|
for (int32 Index = 0; Index < AlphaValues.Num(); Index++)
|
|
{
|
|
if (AlphaValues[Index] != 0)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
|
|
void ULandscapeComponent::UpdateCollisionHeightData(const FColor* HeightmapTextureMipData, int32 ComponentX1, int32 ComponentY1, int32 ComponentX2, int32 ComponentY2, bool bUpdateBounds, const FColor* XYOffsetmapTextureData, bool bRebuild)
|
|
{
|
|
ULandscapeInfo* Info = GetLandscapeInfo();
|
|
ALandscapeProxy* Proxy = GetLandscapeProxy();
|
|
FIntPoint ComponentKey = GetSectionBase() / ComponentSizeQuads;
|
|
ULandscapeHeightfieldCollisionComponent* CollisionComp = CollisionComponent.Get();
|
|
ULandscapeMeshCollisionComponent* MeshCollisionComponent = Cast<ULandscapeMeshCollisionComponent>(CollisionComp);
|
|
|
|
ULandscapeHeightfieldCollisionComponent* OldCollisionComponent = CollisionComp;
|
|
|
|
ALandscapeProxy* CollisionProxy = NULL;
|
|
if (CollisionComp && bRebuild)
|
|
{
|
|
// Remove existing component
|
|
CollisionProxy = CollisionComp->GetLandscapeProxy();
|
|
if (CollisionProxy)
|
|
{
|
|
CollisionComp->DestroyComponent();
|
|
CollisionComp = NULL;
|
|
}
|
|
}
|
|
|
|
int32 CollisionSubsectionSizeVerts = ((SubsectionSizeQuads + 1) >> CollisionMipLevel);
|
|
int32 CollisionSubsectionSizeQuads = CollisionSubsectionSizeVerts - 1;
|
|
int32 CollisionSizeVerts = NumSubsections*CollisionSubsectionSizeQuads + 1;
|
|
|
|
uint16* CollisionHeightData = NULL;
|
|
uint16* CollisionXYOffsetData = NULL;
|
|
bool CreatedNew = false;
|
|
bool ChangeType = false;
|
|
TArray<uint8> DominantLayerData;
|
|
TArray<ULandscapeLayerInfoObject*> LayerInfos;
|
|
|
|
if (CollisionComp)
|
|
{
|
|
CollisionComp->Modify();
|
|
}
|
|
|
|
// Existing collision component is same type with collision
|
|
if (CollisionComp && ((XYOffsetmapTexture == NULL) == (MeshCollisionComponent == NULL)))
|
|
{
|
|
if (bUpdateBounds)
|
|
{
|
|
CollisionComp->CachedLocalBox = CachedLocalBox;
|
|
CollisionComp->UpdateComponentToWorld();
|
|
}
|
|
|
|
CollisionHeightData = (uint16*)CollisionComp->CollisionHeightData.Lock(LOCK_READ_WRITE);
|
|
|
|
if (XYOffsetmapTexture && MeshCollisionComponent)
|
|
{
|
|
CollisionXYOffsetData = (uint16*)MeshCollisionComponent->CollisionXYOffsetData.Lock(LOCK_READ_WRITE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ComponentX1 = 0;
|
|
ComponentY1 = 0;
|
|
ComponentX2 = MAX_int32;
|
|
ComponentY2 = MAX_int32;
|
|
|
|
if (CollisionComp) // remove old component before changing to other type collision...
|
|
{
|
|
ChangeType = true;
|
|
|
|
if (CollisionComp->DominantLayerData.GetElementCount())
|
|
{
|
|
DominantLayerData.AddUninitialized(FMath::Square(CollisionSizeVerts));
|
|
|
|
const uint8* SrcDominantLayerData = (uint8*)CollisionComp->DominantLayerData.Lock(LOCK_READ_ONLY);
|
|
FMemory::Memcpy(DominantLayerData.GetData(), SrcDominantLayerData, sizeof(uint8)*FMath::Square(CollisionSizeVerts));
|
|
CollisionComp->DominantLayerData.Unlock();
|
|
}
|
|
|
|
if (CollisionComp->ComponentLayerInfos.Num())
|
|
{
|
|
LayerInfos = CollisionComp->ComponentLayerInfos;
|
|
}
|
|
|
|
if (Info)
|
|
{
|
|
Info->Modify();
|
|
}
|
|
Proxy->Modify();
|
|
CollisionComp->DestroyComponent();
|
|
CollisionComp = NULL;
|
|
}
|
|
|
|
MeshCollisionComponent = XYOffsetmapTexture ? NewObject<ULandscapeMeshCollisionComponent>(Proxy, NAME_None, RF_Transactional) : NULL;
|
|
CollisionComp = XYOffsetmapTexture ? Cast<ULandscapeHeightfieldCollisionComponent>(MeshCollisionComponent) :
|
|
NewObject<ULandscapeHeightfieldCollisionComponent>(Proxy, NAME_None, RF_Transactional);
|
|
|
|
CollisionComp->SetRelativeLocation(RelativeLocation);
|
|
CollisionComp->AttachTo(Proxy->GetRootComponent(), NAME_None);
|
|
Proxy->CollisionComponents.Add(CollisionComp);
|
|
|
|
CollisionComp->RenderComponent = this;
|
|
CollisionComp->SetSectionBase(GetSectionBase());
|
|
CollisionComp->CollisionSizeQuads = CollisionSubsectionSizeQuads * NumSubsections;
|
|
CollisionComp->CollisionScale = (float)(ComponentSizeQuads) / (float)(CollisionComp->CollisionSizeQuads);
|
|
CollisionComp->CachedLocalBox = CachedLocalBox;
|
|
CreatedNew = true;
|
|
|
|
// Reallocate raw collision data
|
|
CollisionComp->CollisionHeightData.Lock(LOCK_READ_WRITE);
|
|
CollisionHeightData = (uint16*)CollisionComp->CollisionHeightData.Realloc(FMath::Square(CollisionSizeVerts));
|
|
FMemory::Memzero(CollisionHeightData, sizeof(uint16)*FMath::Square(CollisionSizeVerts));
|
|
|
|
if (XYOffsetmapTexture && MeshCollisionComponent)
|
|
{
|
|
// Need XYOffsetData for Collision Component
|
|
MeshCollisionComponent->CollisionXYOffsetData.Lock(LOCK_READ_WRITE);
|
|
CollisionXYOffsetData = (uint16*)MeshCollisionComponent->CollisionXYOffsetData.Realloc(FMath::Square(CollisionSizeVerts) * 2);
|
|
FMemory::Memzero(CollisionXYOffsetData, sizeof(uint16)*FMath::Square(CollisionSizeVerts) * 2);
|
|
|
|
if (DominantLayerData.Num())
|
|
{
|
|
MeshCollisionComponent->DominantLayerData.Lock(LOCK_READ_WRITE);
|
|
uint8* DestDominantLayerData = (uint8*)MeshCollisionComponent->DominantLayerData.Realloc(FMath::Square(CollisionSizeVerts));
|
|
FMemory::Memcpy(DestDominantLayerData, DominantLayerData.GetData(), sizeof(uint8)*FMath::Square(CollisionSizeVerts));
|
|
MeshCollisionComponent->DominantLayerData.Unlock();
|
|
}
|
|
|
|
if (LayerInfos.Num())
|
|
{
|
|
MeshCollisionComponent->ComponentLayerInfos = LayerInfos;
|
|
}
|
|
}
|
|
}
|
|
|
|
int32 HeightmapSizeU = HeightmapTexture->Source.GetSizeX();
|
|
int32 HeightmapSizeV = HeightmapTexture->Source.GetSizeY();
|
|
int32 MipSizeU = HeightmapSizeU >> CollisionMipLevel;
|
|
int32 MipSizeV = HeightmapSizeV >> CollisionMipLevel;
|
|
|
|
int32 XYMipSizeU = XYOffsetmapTexture ? XYOffsetmapTexture->Source.GetSizeX() >> CollisionMipLevel : 0;
|
|
int32 XYMipSizeV = XYOffsetmapTexture ? XYOffsetmapTexture->Source.GetSizeY() >> CollisionMipLevel : 0;
|
|
|
|
// Ratio to convert update region coordinate to collision mip coordinates
|
|
float CollisionQuadRatio = (float)CollisionSubsectionSizeQuads / (float)SubsectionSizeQuads;
|
|
|
|
// XY offset into heightmap mip data
|
|
int32 HeightmapOffsetX = FMath::RoundToInt(HeightmapScaleBias.Z * (float)HeightmapSizeU) >> CollisionMipLevel;
|
|
int32 HeightmapOffsetY = FMath::RoundToInt(HeightmapScaleBias.W * (float)HeightmapSizeV) >> CollisionMipLevel;
|
|
|
|
//int32 WeightmapOffsetX = FMath::RoundToInt(WeightmapScaleBias.Z * (float)XYMipSizeU) >> CollisionMipLevel;
|
|
//int32 WeightmapOffsetY = FMath::RoundToInt(WeightmapScaleBias.W * (float)XYMipSizeV) >> CollisionMipLevel;
|
|
|
|
for (int32 SubsectionY = 0; SubsectionY < NumSubsections; SubsectionY++)
|
|
{
|
|
// Check if subsection is fully above or below the area we are interested in
|
|
if ((ComponentY2 < SubsectionSizeQuads*SubsectionY) || // above
|
|
(ComponentY1 > SubsectionSizeQuads*(SubsectionY + 1))) // below
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (int32 SubsectionX = 0; SubsectionX < NumSubsections; SubsectionX++)
|
|
{
|
|
// Check if subsection is fully to the left or right of the area we are interested in
|
|
if ((ComponentX2 < SubsectionSizeQuads*SubsectionX) || // left
|
|
(ComponentX1 > SubsectionSizeQuads*(SubsectionX + 1))) // right
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Area to update in subsection coordinates
|
|
int32 SubX1 = ComponentX1 - SubsectionSizeQuads*SubsectionX;
|
|
int32 SubY1 = ComponentY1 - SubsectionSizeQuads*SubsectionY;
|
|
int32 SubX2 = ComponentX2 - SubsectionSizeQuads*SubsectionX;
|
|
int32 SubY2 = ComponentY2 - SubsectionSizeQuads*SubsectionY;
|
|
|
|
// Area to update in collision mip level coords
|
|
int32 CollisionSubX1 = FMath::FloorToInt((float)SubX1 * CollisionQuadRatio);
|
|
int32 CollisionSubY1 = FMath::FloorToInt((float)SubY1 * CollisionQuadRatio);
|
|
int32 CollisionSubX2 = FMath::CeilToInt((float)SubX2 * CollisionQuadRatio);
|
|
int32 CollisionSubY2 = FMath::CeilToInt((float)SubY2 * CollisionQuadRatio);
|
|
|
|
// Clamp area to update
|
|
int32 VertX1 = FMath::Clamp<int32>(CollisionSubX1, 0, CollisionSubsectionSizeQuads);
|
|
int32 VertY1 = FMath::Clamp<int32>(CollisionSubY1, 0, CollisionSubsectionSizeQuads);
|
|
int32 VertX2 = FMath::Clamp<int32>(CollisionSubX2, 0, CollisionSubsectionSizeQuads);
|
|
int32 VertY2 = FMath::Clamp<int32>(CollisionSubY2, 0, CollisionSubsectionSizeQuads);
|
|
|
|
for (int32 VertY = VertY1; VertY <= VertY2; VertY++)
|
|
{
|
|
for (int32 VertX = VertX1; VertX <= VertX2; VertX++)
|
|
{
|
|
{
|
|
// X/Y of the vertex we're looking indexed into the texture data
|
|
int32 TexX = HeightmapOffsetX + CollisionSubsectionSizeVerts * SubsectionX + VertX;
|
|
int32 TexY = HeightmapOffsetY + CollisionSubsectionSizeVerts * SubsectionY + VertY;
|
|
const FColor& TexData = HeightmapTextureMipData[TexX + TexY * MipSizeU];
|
|
|
|
// this uses Quads as we don't want the duplicated vertices
|
|
int32 CompVertX = CollisionSubsectionSizeQuads * SubsectionX + VertX;
|
|
int32 CompVertY = CollisionSubsectionSizeQuads * SubsectionY + VertY;
|
|
|
|
// Copy collision data
|
|
uint16& CollisionHeight = CollisionHeightData[CompVertX + CompVertY*CollisionSizeVerts];
|
|
uint16 NewHeight = TexData.R << 8 | TexData.G;
|
|
|
|
CollisionHeight = NewHeight;
|
|
}
|
|
|
|
if (XYOffsetmapTexture && XYOffsetmapTextureData && CollisionXYOffsetData)
|
|
{
|
|
// X/Y of the vertex we're looking indexed into the texture data
|
|
int32 TexX = CollisionSubsectionSizeVerts * SubsectionX + VertX;
|
|
int32 TexY = CollisionSubsectionSizeVerts * SubsectionY + VertY;
|
|
const FColor& TexData = XYOffsetmapTextureData[TexX + TexY * XYMipSizeU];
|
|
|
|
// this uses Quads as we don't want the duplicated vertices
|
|
int32 CompVertX = CollisionSubsectionSizeQuads * SubsectionX + VertX;
|
|
int32 CompVertY = CollisionSubsectionSizeQuads * SubsectionY + VertY;
|
|
|
|
// Copy collision data
|
|
uint16 NewXOffset = TexData.R << 8 | TexData.G;
|
|
uint16 NewYOffset = TexData.B << 8 | TexData.A;
|
|
|
|
int32 XYIndex = CompVertX + CompVertY*CollisionSizeVerts;
|
|
CollisionXYOffsetData[XYIndex * 2] = NewXOffset;
|
|
CollisionXYOffsetData[XYIndex * 2 + 1] = NewYOffset;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CollisionComp->CollisionHeightData.Unlock();
|
|
|
|
if (XYOffsetmapTexture && MeshCollisionComponent)
|
|
{
|
|
MeshCollisionComponent->CollisionXYOffsetData.Unlock();
|
|
}
|
|
|
|
// If we updated an existing component, we need to update the PhysX copy of the data
|
|
if (!CreatedNew)
|
|
{
|
|
if (MeshCollisionComponent)
|
|
{
|
|
// Will be done once for XY Offset data update in FXYOffsetmapAccessor() destructor with UpdateCachedBounds()
|
|
//MeshCollisionComponent->RecreateCollision(false);
|
|
}
|
|
else if (CollisionMipLevel == 0)
|
|
{
|
|
CollisionComp->UpdateHeightfieldRegion(ComponentX1, ComponentY1, ComponentX2, ComponentY2);
|
|
}
|
|
else
|
|
{
|
|
int32 CollisionCompX1 = FMath::FloorToInt((float)ComponentX1 * CollisionQuadRatio);
|
|
int32 CollisionCompY1 = FMath::FloorToInt((float)ComponentY1 * CollisionQuadRatio);
|
|
int32 CollisionCompX2 = FMath::CeilToInt((float)ComponentX2 * CollisionQuadRatio);
|
|
int32 CollisionCompY2 = FMath::CeilToInt((float)ComponentY2 * CollisionQuadRatio);
|
|
CollisionComp->UpdateHeightfieldRegion(CollisionCompX1, CollisionCompY1, CollisionCompX2, CollisionCompY2);
|
|
}
|
|
}
|
|
|
|
{
|
|
// set relevancy for navigation system
|
|
ALandscapeProxy* LandscapeProxy = CollisionComp->GetLandscapeProxy();
|
|
CollisionComp->bCanEverAffectNavigation = LandscapeProxy ? LandscapeProxy->bUsedForNavigation : false;
|
|
}
|
|
|
|
// Move any foliage instances if we created a new collision component.
|
|
if (OldCollisionComponent && OldCollisionComponent != CollisionComp)
|
|
{
|
|
AInstancedFoliageActor::MoveInstancesToNewComponent(Proxy->GetWorld(), OldCollisionComponent, CollisionComp);
|
|
}
|
|
|
|
if (bRebuild && CollisionProxy)
|
|
{
|
|
CollisionProxy->RegisterAllComponents();
|
|
}
|
|
|
|
// Set new collision component to pointer
|
|
CollisionComponent = CollisionComp;
|
|
|
|
if (ChangeType || CreatedNew)
|
|
{
|
|
Proxy->RegisterAllComponents();
|
|
}
|
|
}
|
|
|
|
|
|
void ULandscapeComponent::UpdateCollisionLayerData(TArray<FColor*>& WeightmapTextureMipData, int32 ComponentX1, int32 ComponentY1, int32 ComponentX2, int32 ComponentY2)
|
|
{
|
|
ULandscapeInfo* Info = GetLandscapeInfo();
|
|
ALandscapeProxy* Proxy = GetLandscapeProxy();
|
|
FIntPoint ComponentKey = GetSectionBase() / ComponentSizeQuads;
|
|
|
|
if (CollisionComponent)
|
|
{
|
|
CollisionComponent->Modify();
|
|
|
|
TArray<ULandscapeLayerInfoObject*> CandidateLayers;
|
|
TArray<uint8*> CandidateDataPtrs;
|
|
|
|
// Channel remapping
|
|
int32 ChannelOffsets[4] = { (int32)STRUCT_OFFSET(FColor, R), (int32)STRUCT_OFFSET(FColor, G), (int32)STRUCT_OFFSET(FColor, B), (int32)STRUCT_OFFSET(FColor, A) };
|
|
|
|
bool bExistingLayerMismatch = false;
|
|
int32 DataLayerIdx = INDEX_NONE;
|
|
|
|
// Find the layers we're interested in
|
|
for (int32 AllocIdx = 0; AllocIdx < WeightmapLayerAllocations.Num(); AllocIdx++)
|
|
{
|
|
FWeightmapLayerAllocationInfo& AllocInfo = WeightmapLayerAllocations[AllocIdx];
|
|
ULandscapeLayerInfoObject* LayerInfo = AllocInfo.LayerInfo;
|
|
if (LayerInfo == ALandscapeProxy::VisibilityLayer || LayerInfo != nullptr)
|
|
{
|
|
int32 Idx = CandidateLayers.Add(LayerInfo);
|
|
CandidateDataPtrs.Add(((uint8*)WeightmapTextureMipData[AllocInfo.WeightmapTextureIndex]) + ChannelOffsets[AllocInfo.WeightmapTextureChannel]);
|
|
|
|
// Check if we still match the collision component.
|
|
if (!CollisionComponent->ComponentLayerInfos.IsValidIndex(Idx) || CollisionComponent->ComponentLayerInfos[Idx] != LayerInfo)
|
|
{
|
|
bExistingLayerMismatch = true;
|
|
}
|
|
|
|
if (LayerInfo == ALandscapeProxy::VisibilityLayer)
|
|
{
|
|
DataLayerIdx = Idx;
|
|
bExistingLayerMismatch = true; // always rebuild whole component for hole
|
|
}
|
|
}
|
|
}
|
|
|
|
if (CandidateLayers.Num() == 0)
|
|
{
|
|
// No layers, so don't update any weights
|
|
CollisionComponent->DominantLayerData.RemoveBulkData();
|
|
CollisionComponent->ComponentLayerInfos.Empty();
|
|
}
|
|
else
|
|
{
|
|
int32 CollisionSubsectionSizeVerts = ((SubsectionSizeQuads + 1) >> CollisionMipLevel);
|
|
int32 CollisionSubsectionSizeQuads = CollisionSubsectionSizeVerts - 1;
|
|
int32 CollisionSizeVerts = NumSubsections*CollisionSubsectionSizeQuads + 1;
|
|
uint8* DominantLayerData = NULL;
|
|
|
|
// If there's no existing data, or the layer allocations have changed, we need to update the data for the whole component.
|
|
if (bExistingLayerMismatch || CollisionComponent->DominantLayerData.GetElementCount() == 0)
|
|
{
|
|
ComponentX1 = 0;
|
|
ComponentY1 = 0;
|
|
ComponentX2 = MAX_int32;
|
|
ComponentY2 = MAX_int32;
|
|
|
|
CollisionComponent->DominantLayerData.Lock(LOCK_READ_WRITE);
|
|
DominantLayerData = (uint8*)CollisionComponent->DominantLayerData.Realloc(FMath::Square(CollisionSizeVerts));
|
|
FMemory::Memzero(DominantLayerData, FMath::Square(CollisionSizeVerts));
|
|
|
|
CollisionComponent->ComponentLayerInfos = CandidateLayers;
|
|
}
|
|
else
|
|
{
|
|
DominantLayerData = (uint8*)CollisionComponent->DominantLayerData.Lock(LOCK_READ_WRITE);
|
|
}
|
|
|
|
int32 MipSizeU = (WeightmapTextures[0]->Source.GetSizeX()) >> CollisionMipLevel;
|
|
|
|
// Ratio to convert update region coordinate to collision mip coordinates
|
|
float CollisionQuadRatio = (float)CollisionSubsectionSizeQuads / (float)SubsectionSizeQuads;
|
|
|
|
for (int32 SubsectionY = 0; SubsectionY < NumSubsections; SubsectionY++)
|
|
{
|
|
// Check if subsection is fully above or below the area we are interested in
|
|
if ((ComponentY2 < SubsectionSizeQuads*SubsectionY) || // above
|
|
(ComponentY1 > SubsectionSizeQuads*(SubsectionY + 1))) // below
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (int32 SubsectionX = 0; SubsectionX < NumSubsections; SubsectionX++)
|
|
{
|
|
// Check if subsection is fully to the left or right of the area we are interested in
|
|
if ((ComponentX2 < SubsectionSizeQuads*SubsectionX) || // left
|
|
(ComponentX1 > SubsectionSizeQuads*(SubsectionX + 1))) // right
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Area to update in subsection coordinates
|
|
int32 SubX1 = ComponentX1 - SubsectionSizeQuads*SubsectionX;
|
|
int32 SubY1 = ComponentY1 - SubsectionSizeQuads*SubsectionY;
|
|
int32 SubX2 = ComponentX2 - SubsectionSizeQuads*SubsectionX;
|
|
int32 SubY2 = ComponentY2 - SubsectionSizeQuads*SubsectionY;
|
|
|
|
// Area to update in collision mip level coords
|
|
int32 CollisionSubX1 = FMath::FloorToInt((float)SubX1 * CollisionQuadRatio);
|
|
int32 CollisionSubY1 = FMath::FloorToInt((float)SubY1 * CollisionQuadRatio);
|
|
int32 CollisionSubX2 = FMath::CeilToInt((float)SubX2 * CollisionQuadRatio);
|
|
int32 CollisionSubY2 = FMath::CeilToInt((float)SubY2 * CollisionQuadRatio);
|
|
|
|
// Clamp area to update
|
|
int32 VertX1 = FMath::Clamp<int32>(CollisionSubX1, 0, CollisionSubsectionSizeQuads);
|
|
int32 VertY1 = FMath::Clamp<int32>(CollisionSubY1, 0, CollisionSubsectionSizeQuads);
|
|
int32 VertX2 = FMath::Clamp<int32>(CollisionSubX2, 0, CollisionSubsectionSizeQuads);
|
|
int32 VertY2 = FMath::Clamp<int32>(CollisionSubY2, 0, CollisionSubsectionSizeQuads);
|
|
|
|
for (int32 VertY = VertY1; VertY <= VertY2; VertY++)
|
|
{
|
|
for (int32 VertX = VertX1; VertX <= VertX2; VertX++)
|
|
{
|
|
// X/Y of the vertex we're looking indexed into the texture data
|
|
int32 TexX = CollisionSubsectionSizeVerts * SubsectionX + VertX;
|
|
int32 TexY = CollisionSubsectionSizeVerts * SubsectionY + VertY;
|
|
int32 DataOffset = (TexX + TexY * MipSizeU) * sizeof(FColor);
|
|
|
|
uint8 DominantLayer = 255; // 255 as invalid value
|
|
int32 DominantWeight = 0;
|
|
for (int32 LayerIdx = 0; LayerIdx < CandidateDataPtrs.Num(); LayerIdx++)
|
|
{
|
|
const uint8 LayerWeight = CandidateDataPtrs[LayerIdx][DataOffset];
|
|
|
|
if (LayerIdx == DataLayerIdx) // Override value for hole
|
|
{
|
|
if (LayerWeight > 170) // 255 * 0.66...
|
|
{
|
|
DominantLayer = LayerIdx;
|
|
DominantWeight = INT_MAX;
|
|
}
|
|
}
|
|
else if (LayerWeight > DominantWeight)
|
|
{
|
|
DominantLayer = LayerIdx;
|
|
DominantWeight = LayerWeight;
|
|
}
|
|
}
|
|
|
|
// this uses Quads as we don't want the duplicated vertices
|
|
int32 CompVertX = CollisionSubsectionSizeQuads * SubsectionX + VertX;
|
|
int32 CompVertY = CollisionSubsectionSizeQuads * SubsectionY + VertY;
|
|
|
|
// Set collision data
|
|
DominantLayerData[CompVertX + CompVertY*CollisionSizeVerts] = DominantLayer;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
CollisionComponent->DominantLayerData.Unlock();
|
|
}
|
|
|
|
// We do not force an update of the physics data here. We don't need the layer information in the editor and it
|
|
// causes problems if we update it multiple times in a single frame.
|
|
}
|
|
}
|
|
|
|
|
|
void ULandscapeComponent::UpdateCollisionLayerData()
|
|
{
|
|
// Generate the dominant layer data
|
|
TArray<FColor*> WeightmapTextureMipData;
|
|
TArray<TArray<uint8> > CachedWeightmapTextureMipData;
|
|
|
|
WeightmapTextureMipData.Empty(WeightmapTextures.Num());
|
|
CachedWeightmapTextureMipData.Empty(WeightmapTextures.Num());
|
|
for (int32 Idx = 0; Idx < WeightmapTextures.Num(); Idx++)
|
|
{
|
|
TArray<uint8>* MipData = new(CachedWeightmapTextureMipData)TArray<uint8>();
|
|
WeightmapTextures[Idx]->Source.GetMipData(*MipData, CollisionMipLevel);
|
|
WeightmapTextureMipData.Add((FColor*)MipData->GetData());
|
|
}
|
|
|
|
UpdateCollisionLayerData(WeightmapTextureMipData);
|
|
}
|
|
|
|
|
|
|
|
|
|
void ULandscapeComponent::GenerateHeightmapMips(TArray<FColor*>& HeightmapTextureMipData, int32 ComponentX1/*=0*/, int32 ComponentY1/*=0*/, int32 ComponentX2/*=MAX_int32*/, int32 ComponentY2/*=MAX_int32*/, struct FLandscapeTextureDataInfo* TextureDataInfo/*=NULL*/)
|
|
{
|
|
bool EndX = false;
|
|
bool EndY = false;
|
|
|
|
if (ComponentX1 == MAX_int32)
|
|
{
|
|
EndX = true;
|
|
ComponentX1 = 0;
|
|
}
|
|
|
|
if (ComponentY1 == MAX_int32)
|
|
{
|
|
EndY = true;
|
|
ComponentY1 = 0;
|
|
}
|
|
|
|
if (ComponentX2 == MAX_int32)
|
|
{
|
|
ComponentX2 = ComponentSizeQuads;
|
|
}
|
|
if (ComponentY2 == MAX_int32)
|
|
{
|
|
ComponentY2 = ComponentSizeQuads;
|
|
}
|
|
|
|
int32 HeightmapSizeU = HeightmapTexture->Source.GetSizeX();
|
|
int32 HeightmapSizeV = HeightmapTexture->Source.GetSizeY();
|
|
|
|
int32 HeightmapOffsetX = FMath::RoundToInt(HeightmapScaleBias.Z * (float)HeightmapSizeU);
|
|
int32 HeightmapOffsetY = FMath::RoundToInt(HeightmapScaleBias.W * (float)HeightmapSizeV);
|
|
|
|
for (int32 SubsectionY = 0; SubsectionY < NumSubsections; SubsectionY++)
|
|
{
|
|
// Check if subsection is fully above or below the area we are interested in
|
|
if ((ComponentY2 < SubsectionSizeQuads*SubsectionY) || // above
|
|
(ComponentY1 > SubsectionSizeQuads*(SubsectionY + 1))) // below
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (int32 SubsectionX = 0; SubsectionX < NumSubsections; SubsectionX++)
|
|
{
|
|
// Check if subsection is fully to the left or right of the area we are interested in
|
|
if ((ComponentX2 < SubsectionSizeQuads*SubsectionX) || // left
|
|
(ComponentX1 > SubsectionSizeQuads*(SubsectionX + 1))) // right
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Area to update in previous mip level coords
|
|
int32 PrevMipSubX1 = ComponentX1 - SubsectionSizeQuads*SubsectionX;
|
|
int32 PrevMipSubY1 = ComponentY1 - SubsectionSizeQuads*SubsectionY;
|
|
int32 PrevMipSubX2 = ComponentX2 - SubsectionSizeQuads*SubsectionX;
|
|
int32 PrevMipSubY2 = ComponentY2 - SubsectionSizeQuads*SubsectionY;
|
|
|
|
int32 PrevMipSubsectionSizeQuads = SubsectionSizeQuads;
|
|
float InvPrevMipSubsectionSizeQuads = 1.0f / (float)SubsectionSizeQuads;
|
|
|
|
int32 PrevMipSizeU = HeightmapSizeU;
|
|
int32 PrevMipSizeV = HeightmapSizeV;
|
|
|
|
int32 PrevMipHeightmapOffsetX = HeightmapOffsetX;
|
|
int32 PrevMipHeightmapOffsetY = HeightmapOffsetY;
|
|
|
|
for (int32 Mip = 1; Mip < HeightmapTextureMipData.Num(); Mip++)
|
|
{
|
|
int32 MipSizeU = HeightmapSizeU >> Mip;
|
|
int32 MipSizeV = HeightmapSizeV >> Mip;
|
|
|
|
int32 MipSubsectionSizeQuads = ((SubsectionSizeQuads + 1) >> Mip) - 1;
|
|
float InvMipSubsectionSizeQuads = 1.0f / (float)MipSubsectionSizeQuads;
|
|
|
|
int32 MipHeightmapOffsetX = HeightmapOffsetX >> Mip;
|
|
int32 MipHeightmapOffsetY = HeightmapOffsetY >> Mip;
|
|
|
|
// Area to update in current mip level coords
|
|
int32 MipSubX1 = FMath::FloorToInt((float)MipSubsectionSizeQuads * (float)PrevMipSubX1 * InvPrevMipSubsectionSizeQuads);
|
|
int32 MipSubY1 = FMath::FloorToInt((float)MipSubsectionSizeQuads * (float)PrevMipSubY1 * InvPrevMipSubsectionSizeQuads);
|
|
int32 MipSubX2 = FMath::CeilToInt((float)MipSubsectionSizeQuads * (float)PrevMipSubX2 * InvPrevMipSubsectionSizeQuads);
|
|
int32 MipSubY2 = FMath::CeilToInt((float)MipSubsectionSizeQuads * (float)PrevMipSubY2 * InvPrevMipSubsectionSizeQuads);
|
|
|
|
// Clamp area to update
|
|
int32 VertX1 = FMath::Clamp<int32>(MipSubX1, 0, MipSubsectionSizeQuads);
|
|
int32 VertY1 = FMath::Clamp<int32>(MipSubY1, 0, MipSubsectionSizeQuads);
|
|
int32 VertX2 = FMath::Clamp<int32>(MipSubX2, 0, MipSubsectionSizeQuads);
|
|
int32 VertY2 = FMath::Clamp<int32>(MipSubY2, 0, MipSubsectionSizeQuads);
|
|
|
|
for (int32 VertY = VertY1; VertY <= VertY2; VertY++)
|
|
{
|
|
for (int32 VertX = VertX1; VertX <= VertX2; VertX++)
|
|
{
|
|
// Convert VertX/Y into previous mip's coords
|
|
float PrevMipVertX = (float)PrevMipSubsectionSizeQuads * (float)VertX * InvMipSubsectionSizeQuads;
|
|
float PrevMipVertY = (float)PrevMipSubsectionSizeQuads * (float)VertY * InvMipSubsectionSizeQuads;
|
|
|
|
#if 0
|
|
// Validate that the vertex we skip wouldn't use the updated data in the parent mip.
|
|
// Note this validation is doesn't do anything unless you change the VertY/VertX loops
|
|
// above to process all verts from 0 .. MipSubsectionSizeQuads.
|
|
if (VertX < VertX1 || VertX > VertX2)
|
|
{
|
|
check(FMath::CeilToInt(PrevMipVertX) < PrevMipSubX1 || FMath::FloorToInt(PrevMipVertX) > PrevMipSubX2);
|
|
continue;
|
|
}
|
|
|
|
if (VertY < VertY1 || VertY > VertY2)
|
|
{
|
|
check(FMath::CeilToInt(PrevMipVertY) < PrevMipSubY1 || FMath::FloorToInt(PrevMipVertY) > PrevMipSubY2);
|
|
continue;
|
|
}
|
|
#endif
|
|
|
|
// X/Y of the vertex we're looking indexed into the texture data
|
|
int32 TexX = (MipHeightmapOffsetX)+(MipSubsectionSizeQuads + 1) * SubsectionX + VertX;
|
|
int32 TexY = (MipHeightmapOffsetY)+(MipSubsectionSizeQuads + 1) * SubsectionY + VertY;
|
|
|
|
float fPrevMipTexX = (float)(PrevMipHeightmapOffsetX)+(float)((PrevMipSubsectionSizeQuads + 1) * SubsectionX) + PrevMipVertX;
|
|
float fPrevMipTexY = (float)(PrevMipHeightmapOffsetY)+(float)((PrevMipSubsectionSizeQuads + 1) * SubsectionY) + PrevMipVertY;
|
|
|
|
int32 PrevMipTexX = FMath::FloorToInt(fPrevMipTexX);
|
|
float fPrevMipTexFracX = FMath::Fractional(fPrevMipTexX);
|
|
int32 PrevMipTexY = FMath::FloorToInt(fPrevMipTexY);
|
|
float fPrevMipTexFracY = FMath::Fractional(fPrevMipTexY);
|
|
|
|
checkSlow(TexX >= 0 && TexX < MipSizeU);
|
|
checkSlow(TexY >= 0 && TexY < MipSizeV);
|
|
checkSlow(PrevMipTexX >= 0 && PrevMipTexX < PrevMipSizeU);
|
|
checkSlow(PrevMipTexY >= 0 && PrevMipTexY < PrevMipSizeV);
|
|
|
|
int32 PrevMipTexX1 = FMath::Min<int32>(PrevMipTexX + 1, PrevMipSizeU - 1);
|
|
int32 PrevMipTexY1 = FMath::Min<int32>(PrevMipTexY + 1, PrevMipSizeV - 1);
|
|
|
|
// Padding for missing data for MIP 0
|
|
if (Mip == 1)
|
|
{
|
|
if (EndX && SubsectionX == NumSubsections - 1 && VertX == VertX2)
|
|
{
|
|
for (int32 PaddingIdx = PrevMipTexX + PrevMipTexY * PrevMipSizeU; PaddingIdx + 1 < PrevMipTexY1 * PrevMipSizeU; ++PaddingIdx)
|
|
{
|
|
HeightmapTextureMipData[Mip - 1][PaddingIdx + 1] = HeightmapTextureMipData[Mip - 1][PaddingIdx];
|
|
}
|
|
}
|
|
|
|
if (EndY && SubsectionX == NumSubsections - 1 && SubsectionY == NumSubsections - 1 && VertY == VertY2 && VertX == VertX2)
|
|
{
|
|
for (int32 PaddingYIdx = PrevMipTexY; PaddingYIdx + 1 < PrevMipSizeV; ++PaddingYIdx)
|
|
{
|
|
for (int32 PaddingXIdx = 0; PaddingXIdx < PrevMipSizeU; ++PaddingXIdx)
|
|
{
|
|
HeightmapTextureMipData[Mip - 1][PaddingXIdx + (PaddingYIdx + 1) * PrevMipSizeU] = HeightmapTextureMipData[Mip - 1][PaddingXIdx + PaddingYIdx * PrevMipSizeU];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FColor* TexData = &(HeightmapTextureMipData[Mip])[TexX + TexY * MipSizeU];
|
|
FColor *PreMipTexData00 = &(HeightmapTextureMipData[Mip - 1])[PrevMipTexX + PrevMipTexY * PrevMipSizeU];
|
|
FColor *PreMipTexData01 = &(HeightmapTextureMipData[Mip - 1])[PrevMipTexX + PrevMipTexY1 * PrevMipSizeU];
|
|
FColor *PreMipTexData10 = &(HeightmapTextureMipData[Mip - 1])[PrevMipTexX1 + PrevMipTexY * PrevMipSizeU];
|
|
FColor *PreMipTexData11 = &(HeightmapTextureMipData[Mip - 1])[PrevMipTexX1 + PrevMipTexY1 * PrevMipSizeU];
|
|
|
|
// Lerp height values
|
|
uint16 PrevMipHeightValue00 = PreMipTexData00->R << 8 | PreMipTexData00->G;
|
|
uint16 PrevMipHeightValue01 = PreMipTexData01->R << 8 | PreMipTexData01->G;
|
|
uint16 PrevMipHeightValue10 = PreMipTexData10->R << 8 | PreMipTexData10->G;
|
|
uint16 PrevMipHeightValue11 = PreMipTexData11->R << 8 | PreMipTexData11->G;
|
|
uint16 HeightValue = FMath::RoundToInt(
|
|
FMath::Lerp(
|
|
FMath::Lerp((float)PrevMipHeightValue00, (float)PrevMipHeightValue10, fPrevMipTexFracX),
|
|
FMath::Lerp((float)PrevMipHeightValue01, (float)PrevMipHeightValue11, fPrevMipTexFracX),
|
|
fPrevMipTexFracY));
|
|
|
|
TexData->R = HeightValue >> 8;
|
|
TexData->G = HeightValue & 255;
|
|
|
|
// Lerp tangents
|
|
TexData->B = FMath::RoundToInt(
|
|
FMath::Lerp(
|
|
FMath::Lerp((float)PreMipTexData00->B, (float)PreMipTexData10->B, fPrevMipTexFracX),
|
|
FMath::Lerp((float)PreMipTexData01->B, (float)PreMipTexData11->B, fPrevMipTexFracX),
|
|
fPrevMipTexFracY));
|
|
|
|
TexData->A = FMath::RoundToInt(
|
|
FMath::Lerp(
|
|
FMath::Lerp((float)PreMipTexData00->A, (float)PreMipTexData10->A, fPrevMipTexFracX),
|
|
FMath::Lerp((float)PreMipTexData01->A, (float)PreMipTexData11->A, fPrevMipTexFracX),
|
|
fPrevMipTexFracY));
|
|
|
|
// Padding for missing data
|
|
if (EndX && SubsectionX == NumSubsections - 1 && VertX == VertX2)
|
|
{
|
|
for (int32 PaddingIdx = TexX + TexY * MipSizeU; PaddingIdx + 1 < (TexY + 1) * MipSizeU; ++PaddingIdx)
|
|
{
|
|
HeightmapTextureMipData[Mip][PaddingIdx + 1] = HeightmapTextureMipData[Mip][PaddingIdx];
|
|
}
|
|
}
|
|
|
|
if (EndY && SubsectionX == NumSubsections - 1 && SubsectionY == NumSubsections - 1 && VertY == VertY2 && VertX == VertX2)
|
|
{
|
|
for (int32 PaddingYIdx = TexY; PaddingYIdx + 1 < MipSizeV; ++PaddingYIdx)
|
|
{
|
|
for (int32 PaddingXIdx = 0; PaddingXIdx < MipSizeU; ++PaddingXIdx)
|
|
{
|
|
HeightmapTextureMipData[Mip][PaddingXIdx + (PaddingYIdx + 1) * MipSizeU] = HeightmapTextureMipData[Mip][PaddingXIdx + PaddingYIdx * MipSizeU];
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// Record the areas we updated
|
|
if (TextureDataInfo)
|
|
{
|
|
int32 TexX1 = (MipHeightmapOffsetX)+(MipSubsectionSizeQuads + 1) * SubsectionX + VertX1;
|
|
int32 TexY1 = (MipHeightmapOffsetY)+(MipSubsectionSizeQuads + 1) * SubsectionY + VertY1;
|
|
int32 TexX2 = (MipHeightmapOffsetX)+(MipSubsectionSizeQuads + 1) * SubsectionX + VertX2;
|
|
int32 TexY2 = (MipHeightmapOffsetY)+(MipSubsectionSizeQuads + 1) * SubsectionY + VertY2;
|
|
TextureDataInfo->AddMipUpdateRegion(Mip, TexX1, TexY1, TexX2, TexY2);
|
|
}
|
|
|
|
// Copy current mip values to prev as we move to the next mip.
|
|
PrevMipSubsectionSizeQuads = MipSubsectionSizeQuads;
|
|
InvPrevMipSubsectionSizeQuads = InvMipSubsectionSizeQuads;
|
|
|
|
PrevMipSizeU = MipSizeU;
|
|
PrevMipSizeV = MipSizeV;
|
|
|
|
PrevMipHeightmapOffsetX = MipHeightmapOffsetX;
|
|
PrevMipHeightmapOffsetY = MipHeightmapOffsetY;
|
|
|
|
// Use this mip's area as we move to the next mip
|
|
PrevMipSubX1 = MipSubX1;
|
|
PrevMipSubY1 = MipSubY1;
|
|
PrevMipSubX2 = MipSubX2;
|
|
PrevMipSubY2 = MipSubY2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ULandscapeComponent::CreateEmptyTextureMips(UTexture2D* Texture, bool bClear /*= false*/)
|
|
{
|
|
ETextureSourceFormat WeightmapFormat = Texture->Source.GetFormat();
|
|
int32 WeightmapSizeU = Texture->Source.GetSizeX();
|
|
int32 WeightmapSizeV = Texture->Source.GetSizeY();
|
|
|
|
if (bClear)
|
|
{
|
|
Texture->Source.Init2DWithMipChain(WeightmapSizeU, WeightmapSizeV, WeightmapFormat);
|
|
int32 NumMips = Texture->Source.GetNumMips();
|
|
for (int32 MipIndex = 0; MipIndex < NumMips; ++MipIndex)
|
|
{
|
|
uint8* MipData = Texture->Source.LockMip(MipIndex);
|
|
FMemory::Memzero(MipData, Texture->Source.CalcMipSize(MipIndex));
|
|
Texture->Source.UnlockMip(MipIndex);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TArray<uint8> TopMipData;
|
|
Texture->Source.GetMipData(TopMipData, 0);
|
|
Texture->Source.Init2DWithMipChain(WeightmapSizeU, WeightmapSizeV, WeightmapFormat);
|
|
int32 NumMips = Texture->Source.GetNumMips();
|
|
uint8* MipData = Texture->Source.LockMip(0);
|
|
FMemory::Memcpy(MipData, TopMipData.GetData(), TopMipData.Num());
|
|
Texture->Source.UnlockMip(0);
|
|
}
|
|
}
|
|
|
|
template<typename DataType>
|
|
void ULandscapeComponent::GenerateMipsTempl(int32 InNumSubsections, int32 InSubsectionSizeQuads, UTexture2D* Texture, DataType* BaseMipData)
|
|
{
|
|
// Stores pointers to the locked mip data
|
|
TArray<DataType*> MipData;
|
|
MipData.Add(BaseMipData);
|
|
for (int32 MipIndex = 1; MipIndex < Texture->Source.GetNumMips(); ++MipIndex)
|
|
{
|
|
MipData.Add((DataType*)Texture->Source.LockMip(MipIndex));
|
|
}
|
|
|
|
// Update the newly created mips
|
|
UpdateMipsTempl<DataType>(InNumSubsections, InSubsectionSizeQuads, Texture, MipData);
|
|
|
|
// Unlock all the new mips, but not the base mip's data
|
|
for (int32 i = 1; i < MipData.Num(); i++)
|
|
{
|
|
Texture->Source.UnlockMip(i);
|
|
}
|
|
}
|
|
|
|
void ULandscapeComponent::GenerateWeightmapMips(int32 InNumSubsections, int32 InSubsectionSizeQuads, UTexture2D* WeightmapTexture, FColor* BaseMipData)
|
|
{
|
|
GenerateMipsTempl<FColor>(InNumSubsections, InSubsectionSizeQuads, WeightmapTexture, BaseMipData);
|
|
}
|
|
|
|
namespace
|
|
{
|
|
template<typename DataType>
|
|
void BiLerpTextureData(DataType* Output, const DataType* Data00, const DataType* Data10, const DataType* Data01, const DataType* Data11, float FracX, float FracY)
|
|
{
|
|
*Output = FMath::RoundToInt(
|
|
FMath::Lerp(
|
|
FMath::Lerp((float)*Data00, (float)*Data10, FracX),
|
|
FMath::Lerp((float)*Data01, (float)*Data11, FracX),
|
|
FracY));
|
|
}
|
|
|
|
template<>
|
|
void BiLerpTextureData(FColor* Output, const FColor* Data00, const FColor* Data10, const FColor* Data01, const FColor* Data11, float FracX, float FracY)
|
|
{
|
|
Output->R = FMath::RoundToInt(
|
|
FMath::Lerp(
|
|
FMath::Lerp((float)Data00->R, (float)Data10->R, FracX),
|
|
FMath::Lerp((float)Data01->R, (float)Data11->R, FracX),
|
|
FracY));
|
|
Output->G = FMath::RoundToInt(
|
|
FMath::Lerp(
|
|
FMath::Lerp((float)Data00->G, (float)Data10->G, FracX),
|
|
FMath::Lerp((float)Data01->G, (float)Data11->G, FracX),
|
|
FracY));
|
|
Output->B = FMath::RoundToInt(
|
|
FMath::Lerp(
|
|
FMath::Lerp((float)Data00->B, (float)Data10->B, FracX),
|
|
FMath::Lerp((float)Data01->B, (float)Data11->B, FracX),
|
|
FracY));
|
|
Output->A = FMath::RoundToInt(
|
|
FMath::Lerp(
|
|
FMath::Lerp((float)Data00->A, (float)Data10->A, FracX),
|
|
FMath::Lerp((float)Data01->A, (float)Data11->A, FracX),
|
|
FracY));
|
|
}
|
|
|
|
template<typename DataType>
|
|
void AverageTexData(DataType* Output, const DataType* Data00, const DataType* Data10, const DataType* Data01, const DataType* Data11)
|
|
{
|
|
*Output = (((int32)(*Data00) + (int32)(*Data10) + (int32)(*Data01) + (int32)(*Data11)) >> 2);
|
|
}
|
|
|
|
template<>
|
|
void AverageTexData(FColor* Output, const FColor* Data00, const FColor* Data10, const FColor* Data01, const FColor* Data11)
|
|
{
|
|
Output->R = (((int32)Data00->R + (int32)Data10->R + (int32)Data01->R + (int32)Data11->R) >> 2);
|
|
Output->G = (((int32)Data00->G + (int32)Data10->G + (int32)Data01->G + (int32)Data11->G) >> 2);
|
|
Output->B = (((int32)Data00->B + (int32)Data10->B + (int32)Data01->B + (int32)Data11->B) >> 2);
|
|
Output->A = (((int32)Data00->A + (int32)Data10->A + (int32)Data01->A + (int32)Data11->A) >> 2);
|
|
}
|
|
|
|
};
|
|
|
|
template<typename DataType>
|
|
void ULandscapeComponent::UpdateMipsTempl(int32 InNumSubsections, int32 InSubsectionSizeQuads, UTexture2D* Texture, TArray<DataType*>& TextureMipData, int32 ComponentX1/*=0*/, int32 ComponentY1/*=0*/, int32 ComponentX2/*=MAX_int32*/, int32 ComponentY2/*=MAX_int32*/, struct FLandscapeTextureDataInfo* TextureDataInfo/*=NULL*/)
|
|
{
|
|
int32 WeightmapSizeU = Texture->Source.GetSizeX();
|
|
int32 WeightmapSizeV = Texture->Source.GetSizeY();
|
|
|
|
// Find the maximum mip where each texel's data comes from just one subsection.
|
|
int32 MaxWholeSubsectionMip = FMath::FloorLog2(InSubsectionSizeQuads + 1) - 1;
|
|
|
|
// Update the mip where each texel's data comes from just one subsection.
|
|
for (int32 SubsectionY = 0; SubsectionY < InNumSubsections; SubsectionY++)
|
|
{
|
|
// Check if subsection is fully above or below the area we are interested in
|
|
if ((ComponentY2 < InSubsectionSizeQuads*SubsectionY) || // above
|
|
(ComponentY1 > InSubsectionSizeQuads*(SubsectionY + 1))) // below
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (int32 SubsectionX = 0; SubsectionX < InNumSubsections; SubsectionX++)
|
|
{
|
|
// Check if subsection is fully to the left or right of the area we are interested in
|
|
if ((ComponentX2 < InSubsectionSizeQuads*SubsectionX) || // left
|
|
(ComponentX1 > InSubsectionSizeQuads*(SubsectionX + 1))) // right
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Area to update in previous mip level coords
|
|
int32 PrevMipSubX1 = ComponentX1 - InSubsectionSizeQuads*SubsectionX;
|
|
int32 PrevMipSubY1 = ComponentY1 - InSubsectionSizeQuads*SubsectionY;
|
|
int32 PrevMipSubX2 = ComponentX2 - InSubsectionSizeQuads*SubsectionX;
|
|
int32 PrevMipSubY2 = ComponentY2 - InSubsectionSizeQuads*SubsectionY;
|
|
|
|
int32 PrevMipSubsectionSizeQuads = InSubsectionSizeQuads;
|
|
float InvPrevMipSubsectionSizeQuads = 1.0f / (float)InSubsectionSizeQuads;
|
|
|
|
int32 PrevMipSizeU = WeightmapSizeU;
|
|
int32 PrevMipSizeV = WeightmapSizeV;
|
|
|
|
for (int32 Mip = 1; Mip <= MaxWholeSubsectionMip; Mip++)
|
|
{
|
|
int32 MipSizeU = WeightmapSizeU >> Mip;
|
|
int32 MipSizeV = WeightmapSizeV >> Mip;
|
|
|
|
int32 MipSubsectionSizeQuads = ((InSubsectionSizeQuads + 1) >> Mip) - 1;
|
|
float InvMipSubsectionSizeQuads = 1.0f / (float)MipSubsectionSizeQuads;
|
|
|
|
// Area to update in current mip level coords
|
|
int32 MipSubX1 = FMath::FloorToInt((float)MipSubsectionSizeQuads * (float)PrevMipSubX1 * InvPrevMipSubsectionSizeQuads);
|
|
int32 MipSubY1 = FMath::FloorToInt((float)MipSubsectionSizeQuads * (float)PrevMipSubY1 * InvPrevMipSubsectionSizeQuads);
|
|
int32 MipSubX2 = FMath::CeilToInt((float)MipSubsectionSizeQuads * (float)PrevMipSubX2 * InvPrevMipSubsectionSizeQuads);
|
|
int32 MipSubY2 = FMath::CeilToInt((float)MipSubsectionSizeQuads * (float)PrevMipSubY2 * InvPrevMipSubsectionSizeQuads);
|
|
|
|
// Clamp area to update
|
|
int32 VertX1 = FMath::Clamp<int32>(MipSubX1, 0, MipSubsectionSizeQuads);
|
|
int32 VertY1 = FMath::Clamp<int32>(MipSubY1, 0, MipSubsectionSizeQuads);
|
|
int32 VertX2 = FMath::Clamp<int32>(MipSubX2, 0, MipSubsectionSizeQuads);
|
|
int32 VertY2 = FMath::Clamp<int32>(MipSubY2, 0, MipSubsectionSizeQuads);
|
|
|
|
for (int32 VertY = VertY1; VertY <= VertY2; VertY++)
|
|
{
|
|
for (int32 VertX = VertX1; VertX <= VertX2; VertX++)
|
|
{
|
|
// Convert VertX/Y into previous mip's coords
|
|
float PrevMipVertX = (float)PrevMipSubsectionSizeQuads * (float)VertX * InvMipSubsectionSizeQuads;
|
|
float PrevMipVertY = (float)PrevMipSubsectionSizeQuads * (float)VertY * InvMipSubsectionSizeQuads;
|
|
|
|
// X/Y of the vertex we're looking indexed into the texture data
|
|
int32 TexX = (MipSubsectionSizeQuads + 1) * SubsectionX + VertX;
|
|
int32 TexY = (MipSubsectionSizeQuads + 1) * SubsectionY + VertY;
|
|
|
|
float fPrevMipTexX = (float)((PrevMipSubsectionSizeQuads + 1) * SubsectionX) + PrevMipVertX;
|
|
float fPrevMipTexY = (float)((PrevMipSubsectionSizeQuads + 1) * SubsectionY) + PrevMipVertY;
|
|
|
|
int32 PrevMipTexX = FMath::FloorToInt(fPrevMipTexX);
|
|
float fPrevMipTexFracX = FMath::Fractional(fPrevMipTexX);
|
|
int32 PrevMipTexY = FMath::FloorToInt(fPrevMipTexY);
|
|
float fPrevMipTexFracY = FMath::Fractional(fPrevMipTexY);
|
|
|
|
check(TexX >= 0 && TexX < MipSizeU);
|
|
check(TexY >= 0 && TexY < MipSizeV);
|
|
check(PrevMipTexX >= 0 && PrevMipTexX < PrevMipSizeU);
|
|
check(PrevMipTexY >= 0 && PrevMipTexY < PrevMipSizeV);
|
|
|
|
int32 PrevMipTexX1 = FMath::Min<int32>(PrevMipTexX + 1, PrevMipSizeU - 1);
|
|
int32 PrevMipTexY1 = FMath::Min<int32>(PrevMipTexY + 1, PrevMipSizeV - 1);
|
|
|
|
DataType* TexData = &(TextureMipData[Mip])[TexX + TexY * MipSizeU];
|
|
DataType *PreMipTexData00 = &(TextureMipData[Mip - 1])[PrevMipTexX + PrevMipTexY * PrevMipSizeU];
|
|
DataType *PreMipTexData01 = &(TextureMipData[Mip - 1])[PrevMipTexX + PrevMipTexY1 * PrevMipSizeU];
|
|
DataType *PreMipTexData10 = &(TextureMipData[Mip - 1])[PrevMipTexX1 + PrevMipTexY * PrevMipSizeU];
|
|
DataType *PreMipTexData11 = &(TextureMipData[Mip - 1])[PrevMipTexX1 + PrevMipTexY1 * PrevMipSizeU];
|
|
|
|
// Lerp weightmap data
|
|
BiLerpTextureData<DataType>(TexData, PreMipTexData00, PreMipTexData10, PreMipTexData01, PreMipTexData11, fPrevMipTexFracX, fPrevMipTexFracY);
|
|
}
|
|
}
|
|
|
|
// Record the areas we updated
|
|
if (TextureDataInfo)
|
|
{
|
|
int32 TexX1 = (MipSubsectionSizeQuads + 1) * SubsectionX + VertX1;
|
|
int32 TexY1 = (MipSubsectionSizeQuads + 1) * SubsectionY + VertY1;
|
|
int32 TexX2 = (MipSubsectionSizeQuads + 1) * SubsectionX + VertX2;
|
|
int32 TexY2 = (MipSubsectionSizeQuads + 1) * SubsectionY + VertY2;
|
|
TextureDataInfo->AddMipUpdateRegion(Mip, TexX1, TexY1, TexX2, TexY2);
|
|
}
|
|
|
|
// Copy current mip values to prev as we move to the next mip.
|
|
PrevMipSubsectionSizeQuads = MipSubsectionSizeQuads;
|
|
InvPrevMipSubsectionSizeQuads = InvMipSubsectionSizeQuads;
|
|
|
|
PrevMipSizeU = MipSizeU;
|
|
PrevMipSizeV = MipSizeV;
|
|
|
|
// Use this mip's area as we move to the next mip
|
|
PrevMipSubX1 = MipSubX1;
|
|
PrevMipSubY1 = MipSubY1;
|
|
PrevMipSubX2 = MipSubX2;
|
|
PrevMipSubY2 = MipSubY2;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle mips that have texels from multiple subsections
|
|
// not valid weight data, so just average the texels of the previous mip.
|
|
for (int32 Mip = MaxWholeSubsectionMip + 1;; ++Mip)
|
|
{
|
|
int32 MipSubsectionSizeQuads = ((InSubsectionSizeQuads + 1) >> Mip) - 1;
|
|
checkSlow(MipSubsectionSizeQuads <= 0);
|
|
|
|
int32 MipSizeU = FMath::Max<int32>(WeightmapSizeU >> Mip, 1);
|
|
int32 MipSizeV = FMath::Max<int32>(WeightmapSizeV >> Mip, 1);
|
|
|
|
int32 PrevMipSizeU = FMath::Max<int32>(WeightmapSizeU >> (Mip - 1), 1);
|
|
int32 PrevMipSizeV = FMath::Max<int32>(WeightmapSizeV >> (Mip - 1), 1);
|
|
|
|
for (int32 Y = 0; Y < MipSizeV; Y++)
|
|
{
|
|
for (int32 X = 0; X < MipSizeU; X++)
|
|
{
|
|
DataType* TexData = &(TextureMipData[Mip])[X + Y * MipSizeU];
|
|
|
|
DataType *PreMipTexData00 = &(TextureMipData[Mip - 1])[(X * 2 + 0) + (Y * 2 + 0) * PrevMipSizeU];
|
|
DataType *PreMipTexData01 = &(TextureMipData[Mip - 1])[(X * 2 + 0) + (Y * 2 + 1) * PrevMipSizeU];
|
|
DataType *PreMipTexData10 = &(TextureMipData[Mip - 1])[(X * 2 + 1) + (Y * 2 + 0) * PrevMipSizeU];
|
|
DataType *PreMipTexData11 = &(TextureMipData[Mip - 1])[(X * 2 + 1) + (Y * 2 + 1) * PrevMipSizeU];
|
|
|
|
AverageTexData<DataType>(TexData, PreMipTexData00, PreMipTexData10, PreMipTexData01, PreMipTexData11);
|
|
}
|
|
}
|
|
|
|
if (TextureDataInfo)
|
|
{
|
|
// These mip sizes are small enough that we may as well just update the whole mip.
|
|
TextureDataInfo->AddMipUpdateRegion(Mip, 0, 0, MipSizeU - 1, MipSizeV - 1);
|
|
}
|
|
|
|
if (MipSizeU == 1 && MipSizeV == 1)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ULandscapeComponent::UpdateWeightmapMips(int32 InNumSubsections, int32 InSubsectionSizeQuads, UTexture2D* WeightmapTexture, TArray<FColor*>& WeightmapTextureMipData, int32 ComponentX1/*=0*/, int32 ComponentY1/*=0*/, int32 ComponentX2/*=MAX_int32*/, int32 ComponentY2/*=MAX_int32*/, struct FLandscapeTextureDataInfo* TextureDataInfo/*=NULL*/)
|
|
{
|
|
UpdateMipsTempl<FColor>(InNumSubsections, InSubsectionSizeQuads, WeightmapTexture, WeightmapTextureMipData, ComponentX1, ComponentY1, ComponentX2, ComponentY2, TextureDataInfo);
|
|
}
|
|
|
|
void ULandscapeComponent::UpdateDataMips(int32 InNumSubsections, int32 InSubsectionSizeQuads, UTexture2D* Texture, TArray<uint8*>& TextureMipData, int32 ComponentX1/*=0*/, int32 ComponentY1/*=0*/, int32 ComponentX2/*=MAX_int32*/, int32 ComponentY2/*=MAX_int32*/, struct FLandscapeTextureDataInfo* TextureDataInfo/*=NULL*/)
|
|
{
|
|
UpdateMipsTempl<uint8>(InNumSubsections, InSubsectionSizeQuads, Texture, TextureMipData, ComponentX1, ComponentY1, ComponentX2, ComponentY2, TextureDataInfo);
|
|
}
|
|
|
|
float ULandscapeComponent::GetLayerWeightAtLocation(const FVector& InLocation, ULandscapeLayerInfoObject* LayerInfo, TArray<uint8>* LayerCache)
|
|
{
|
|
// Allocate and discard locally if no external cache is passed in.
|
|
TArray<uint8> LocalCache;
|
|
if (LayerCache == NULL)
|
|
{
|
|
LayerCache = &LocalCache;
|
|
}
|
|
|
|
// Fill the cache if necessary
|
|
if (LayerCache->Num() == 0)
|
|
{
|
|
FLandscapeComponentDataInterface CDI(this);
|
|
if (!CDI.GetWeightmapTextureData(LayerInfo, *LayerCache))
|
|
{
|
|
// no data for this layer for this component.
|
|
return 0.0f;
|
|
}
|
|
}
|
|
|
|
// Find location
|
|
// TODO: Root landscape isn't always loaded, would Proxy suffice?
|
|
if (ALandscape* Landscape = GetLandscapeActor())
|
|
{
|
|
const FVector DrawScale = Landscape->GetRootComponent()->RelativeScale3D;
|
|
float TestX = (InLocation.X - Landscape->GetActorLocation().X) / DrawScale.X - (float)GetSectionBase().X;
|
|
float TestY = (InLocation.Y - Landscape->GetActorLocation().Y) / DrawScale.Y - (float)GetSectionBase().Y;
|
|
|
|
// Find data
|
|
int32 X1 = FMath::FloorToInt(TestX);
|
|
int32 Y1 = FMath::FloorToInt(TestY);
|
|
int32 X2 = FMath::CeilToInt(TestX);
|
|
int32 Y2 = FMath::CeilToInt(TestY);
|
|
|
|
int32 Stride = (SubsectionSizeQuads + 1) * NumSubsections;
|
|
|
|
// Min is to prevent the sampling of the final column from overflowing
|
|
int32 IdxX1 = FMath::Min<int32>(((X1 / SubsectionSizeQuads) * (SubsectionSizeQuads + 1)) + (X1 % SubsectionSizeQuads), Stride - 1);
|
|
int32 IdxY1 = FMath::Min<int32>(((Y1 / SubsectionSizeQuads) * (SubsectionSizeQuads + 1)) + (Y1 % SubsectionSizeQuads), Stride - 1);
|
|
int32 IdxX2 = FMath::Min<int32>(((X2 / SubsectionSizeQuads) * (SubsectionSizeQuads + 1)) + (X2 % SubsectionSizeQuads), Stride - 1);
|
|
int32 IdxY2 = FMath::Min<int32>(((Y2 / SubsectionSizeQuads) * (SubsectionSizeQuads + 1)) + (Y2 % SubsectionSizeQuads), Stride - 1);
|
|
|
|
// sample
|
|
float Sample11 = (float)((*LayerCache)[IdxX1 + Stride*IdxY1]) / 255.0f;
|
|
float Sample21 = (float)((*LayerCache)[IdxX2 + Stride*IdxY1]) / 255.0f;
|
|
float Sample12 = (float)((*LayerCache)[IdxX1 + Stride*IdxY2]) / 255.0f;
|
|
float Sample22 = (float)((*LayerCache)[IdxX2 + Stride*IdxY2]) / 255.0f;
|
|
|
|
float LerpX = FMath::Fractional(TestX);
|
|
float LerpY = FMath::Fractional(TestY);
|
|
|
|
// Bilinear interpolate
|
|
return FMath::Lerp(
|
|
FMath::Lerp(Sample11, Sample21, LerpX),
|
|
FMath::Lerp(Sample12, Sample22, LerpX),
|
|
LerpY);
|
|
}
|
|
|
|
return 0.f; //if landscape is null we just return 0 instead of crashing. Seen cases where this happens, seems like a bug?
|
|
}
|
|
|
|
void ULandscapeComponent::GetComponentExtent(int32& MinX, int32& MinY, int32& MaxX, int32& MaxY) const
|
|
{
|
|
MinX = FMath::Min(SectionBaseX, MinX);
|
|
MinY = FMath::Min(SectionBaseY, MinY);
|
|
MaxX = FMath::Max(SectionBaseX + ComponentSizeQuads, MaxX);
|
|
MaxY = FMath::Max(SectionBaseY + ComponentSizeQuads, MaxY);
|
|
}
|
|
|
|
//
|
|
// ALandscape
|
|
//
|
|
|
|
#define MAX_LANDSCAPE_SUBSECTIONS 2
|
|
|
|
void ULandscapeInfo::GetComponentsInRegion(int32 X1, int32 Y1, int32 X2, int32 Y2, TSet<ULandscapeComponent*>& OutComponents)
|
|
{
|
|
// Find component range for this block of data
|
|
// X2/Y2 Coordinates are "inclusive" max values
|
|
int32 ComponentIndexX1, ComponentIndexY1, ComponentIndexX2, ComponentIndexY2;
|
|
ALandscape::CalcComponentIndicesOverlap(X1, Y1, X2, Y2, ComponentSizeQuads, ComponentIndexX1, ComponentIndexY1, ComponentIndexX2, ComponentIndexY2);
|
|
|
|
for (int32 ComponentIndexY = ComponentIndexY1; ComponentIndexY <= ComponentIndexY2; ComponentIndexY++)
|
|
{
|
|
for (int32 ComponentIndexX = ComponentIndexX1; ComponentIndexX <= ComponentIndexX2; ComponentIndexX++)
|
|
{
|
|
ULandscapeComponent* Component = XYtoComponentMap.FindRef(FIntPoint(ComponentIndexX, ComponentIndexY));
|
|
if (Component && !FLevelUtils::IsLevelLocked(Component->GetLandscapeProxy()->GetLevel()) && FLevelUtils::IsLevelVisible(Component->GetLandscapeProxy()->GetLevel()))
|
|
{
|
|
OutComponents.Add(Component);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// A struct to remember where we have spare texture channels.
|
|
struct FWeightmapTextureAllocation
|
|
{
|
|
int32 X;
|
|
int32 Y;
|
|
int32 ChannelsInUse;
|
|
UTexture2D* Texture;
|
|
FColor* TextureData;
|
|
|
|
FWeightmapTextureAllocation(int32 InX, int32 InY, int32 InChannels, UTexture2D* InTexture, FColor* InTextureData)
|
|
: X(InX)
|
|
, Y(InY)
|
|
, ChannelsInUse(InChannels)
|
|
, Texture(InTexture)
|
|
, TextureData(InTextureData)
|
|
{}
|
|
};
|
|
|
|
// A struct to hold the info about each texture chunk of the total heightmap
|
|
struct FHeightmapInfo
|
|
{
|
|
int32 HeightmapSizeU;
|
|
int32 HeightmapSizeV;
|
|
UTexture2D* HeightmapTexture;
|
|
TArray<FColor*> HeightmapTextureMipData;
|
|
};
|
|
|
|
TArray<FName> ALandscapeProxy::GetLayersFromMaterial(UMaterialInterface* Material)
|
|
{
|
|
TArray<FName> Result;
|
|
|
|
if (Material)
|
|
{
|
|
const TArray<UMaterialExpression*>& Expressions = Material->GetMaterial()->Expressions;
|
|
|
|
// TODO: *Unconnected* layer expressions?
|
|
for (UMaterialExpression* Expression : Expressions)
|
|
{
|
|
if (auto LayerWeightExpression = Cast<UMaterialExpressionLandscapeLayerWeight>(Expression))
|
|
{
|
|
Result.AddUnique(LayerWeightExpression->ParameterName);
|
|
}
|
|
else if (auto LayerSampleExpression = Cast<UMaterialExpressionLandscapeLayerSample>(Expression))
|
|
{
|
|
Result.AddUnique(LayerSampleExpression->ParameterName);
|
|
}
|
|
else if (auto LayerSwitchExpression = Cast<UMaterialExpressionLandscapeLayerSwitch>(Expression))
|
|
{
|
|
Result.AddUnique(LayerSwitchExpression->ParameterName);
|
|
}
|
|
else if (auto LayerBlendExpression = Cast<UMaterialExpressionLandscapeLayerBlend>(Expression))
|
|
{
|
|
for (const auto& ExpressionLayer : LayerBlendExpression->Layers)
|
|
{
|
|
Result.AddUnique(ExpressionLayer.LayerName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
TArray<FName> ALandscapeProxy::GetLayersFromMaterial() const
|
|
{
|
|
return GetLayersFromMaterial(LandscapeMaterial);
|
|
}
|
|
|
|
ULandscapeLayerInfoObject* ALandscapeProxy::CreateLayerInfo(const TCHAR* LayerName, ULevel* Level)
|
|
{
|
|
FName LayerObjectName = FName(*FString::Printf(TEXT("LayerInfoObject_%s"), LayerName));
|
|
FString Path = Level->GetOutermost()->GetName() + TEXT("_sharedassets/");
|
|
if (Path.StartsWith("/Temp/"))
|
|
{
|
|
Path = FString("/Game/") + Path.RightChop(FString("/Temp/").Len());
|
|
}
|
|
FString PackageName = Path + LayerObjectName.ToString();
|
|
FString PackageFilename;
|
|
int32 Suffix = 1;
|
|
while (FPackageName::DoesPackageExist(PackageName, NULL, &PackageFilename))
|
|
{
|
|
LayerObjectName = FName(*FString::Printf(TEXT("LayerInfoObject_%s_%d"), LayerName, Suffix));
|
|
PackageName = Path + LayerObjectName.ToString();
|
|
Suffix++;
|
|
}
|
|
UPackage* Package = CreatePackage(NULL, *PackageName);
|
|
ULandscapeLayerInfoObject* LayerInfo = NewObject<ULandscapeLayerInfoObject>(Package, LayerObjectName, RF_Public | RF_Standalone | RF_Transactional);
|
|
LayerInfo->LayerName = LayerName;
|
|
|
|
return LayerInfo;
|
|
}
|
|
|
|
ULandscapeLayerInfoObject* ALandscapeProxy::CreateLayerInfo(const TCHAR* LayerName)
|
|
{
|
|
ULandscapeLayerInfoObject* LayerInfo = ALandscapeProxy::CreateLayerInfo(LayerName, GetLevel());
|
|
|
|
check(LayerInfo);
|
|
|
|
ULandscapeInfo* LandscapeInfo = GetLandscapeInfo();
|
|
if (LandscapeInfo)
|
|
{
|
|
int32 Index = LandscapeInfo->GetLayerInfoIndex(LayerName, this);
|
|
if (Index == INDEX_NONE)
|
|
{
|
|
LandscapeInfo->Layers.Add(FLandscapeInfoLayerSettings(LayerInfo, this));
|
|
}
|
|
else
|
|
{
|
|
LandscapeInfo->Layers[Index].LayerInfoObj = LayerInfo;
|
|
}
|
|
}
|
|
|
|
return LayerInfo;
|
|
}
|
|
|
|
#define HEIGHTDATA(X,Y) (HeightData[ FMath::Clamp<int32>(Y,0,VertsY) * VertsX + FMath::Clamp<int32>(X,0,VertsX) ])
|
|
void ALandscapeProxy::Import(FGuid Guid, int32 VertsX, int32 VertsY,
|
|
int32 InComponentSizeQuads, int32 InNumSubsections, int32 InSubsectionSizeQuads,
|
|
const uint16* HeightData, const TCHAR* HeightmapFileName,
|
|
const TArray<FLandscapeImportLayerInfo>& ImportLayerInfos)
|
|
{
|
|
GWarn->BeginSlowTask(LOCTEXT("BeingImportingLandscapeTask", "Importing Landscape"), true);
|
|
|
|
ComponentSizeQuads = InComponentSizeQuads;
|
|
NumSubsections = InNumSubsections;
|
|
SubsectionSizeQuads = InSubsectionSizeQuads;
|
|
LandscapeGuid = Guid;
|
|
|
|
MarkPackageDirty();
|
|
|
|
// Create and initialize landscape info object
|
|
GetLandscapeInfo(true)->RegisterActor(this);
|
|
|
|
int32 NumPatchesX = (VertsX - 1);
|
|
int32 NumPatchesY = (VertsY - 1);
|
|
|
|
int32 NumSectionsX = NumPatchesX / ComponentSizeQuads;
|
|
int32 NumSectionsY = NumPatchesY / ComponentSizeQuads;
|
|
|
|
LandscapeComponents.Empty(NumSectionsX * NumSectionsY);
|
|
|
|
for (int32 Y = 0; Y < NumSectionsY; Y++)
|
|
{
|
|
for (int32 X = 0; X < NumSectionsX; X++)
|
|
{
|
|
// The number of quads
|
|
int32 NumQuadsX = NumPatchesX;
|
|
int32 NumQuadsY = NumPatchesY;
|
|
|
|
int32 BaseX = X * ComponentSizeQuads;
|
|
int32 BaseY = Y * ComponentSizeQuads;
|
|
|
|
ULandscapeComponent* LandscapeComponent = NewObject<ULandscapeComponent>(this, NAME_None, RF_Transactional);
|
|
LandscapeComponent->SetRelativeLocation(FVector(BaseX, BaseY, 0));
|
|
LandscapeComponent->AttachTo(GetRootComponent(), NAME_None);
|
|
LandscapeComponents.Add(LandscapeComponent);
|
|
LandscapeComponent->Init(
|
|
BaseX, BaseY,
|
|
ComponentSizeQuads,
|
|
NumSubsections,
|
|
SubsectionSizeQuads
|
|
);
|
|
|
|
// Assign shared properties
|
|
LandscapeComponent->bCastStaticShadow = bCastStaticShadow;
|
|
LandscapeComponent->bCastShadowAsTwoSided = bCastShadowAsTwoSided;
|
|
}
|
|
}
|
|
|
|
#define MAX_HEIGHTMAP_TEXTURE_SIZE 512
|
|
|
|
int32 ComponentSizeVerts = NumSubsections * (SubsectionSizeQuads + 1);
|
|
int32 ComponentsPerHeightmap = MAX_HEIGHTMAP_TEXTURE_SIZE / ComponentSizeVerts;
|
|
|
|
// Ensure that we don't pack so many heightmaps into a texture that their lowest LOD isn't guaranteed to be resident
|
|
ComponentsPerHeightmap = FMath::Min(ComponentsPerHeightmap, 1 << (UTexture2D::GetMinTextureResidentMipCount() - 2));
|
|
|
|
// Count how many heightmaps we need and the X dimension of the final heightmap
|
|
int32 NumHeightmapsX = 1;
|
|
int32 FinalComponentsX = NumSectionsX;
|
|
while (FinalComponentsX > ComponentsPerHeightmap)
|
|
{
|
|
FinalComponentsX -= ComponentsPerHeightmap;
|
|
NumHeightmapsX++;
|
|
}
|
|
// Count how many heightmaps we need and the Y dimension of the final heightmap
|
|
int32 NumHeightmapsY = 1;
|
|
int32 FinalComponentsY = NumSectionsY;
|
|
while (FinalComponentsY > ComponentsPerHeightmap)
|
|
{
|
|
FinalComponentsY -= ComponentsPerHeightmap;
|
|
NumHeightmapsY++;
|
|
}
|
|
|
|
TArray<FHeightmapInfo> HeightmapInfos;
|
|
|
|
for (int32 HmY = 0; HmY < NumHeightmapsY; HmY++)
|
|
{
|
|
for (int32 HmX = 0; HmX < NumHeightmapsX; HmX++)
|
|
{
|
|
FHeightmapInfo& HeightmapInfo = HeightmapInfos[HeightmapInfos.AddZeroed()];
|
|
|
|
// make sure the heightmap UVs are powers of two.
|
|
HeightmapInfo.HeightmapSizeU = (1 << FMath::CeilLogTwo(((HmX == NumHeightmapsX - 1) ? FinalComponentsX : ComponentsPerHeightmap)*ComponentSizeVerts));
|
|
HeightmapInfo.HeightmapSizeV = (1 << FMath::CeilLogTwo(((HmY == NumHeightmapsY - 1) ? FinalComponentsY : ComponentsPerHeightmap)*ComponentSizeVerts));
|
|
|
|
// Construct the heightmap textures
|
|
HeightmapInfo.HeightmapTexture = CreateLandscapeTexture(HeightmapInfo.HeightmapSizeU, HeightmapInfo.HeightmapSizeV, TEXTUREGROUP_Terrain_Heightmap, TSF_BGRA8);
|
|
|
|
int32 MipSubsectionSizeQuads = SubsectionSizeQuads;
|
|
int32 MipSizeU = HeightmapInfo.HeightmapSizeU;
|
|
int32 MipSizeV = HeightmapInfo.HeightmapSizeV;
|
|
while (MipSizeU > 1 && MipSizeV > 1 && MipSubsectionSizeQuads >= 1)
|
|
{
|
|
int32 MipIndex = HeightmapInfo.HeightmapTextureMipData.Num();
|
|
FColor* HeightmapTextureData = (FColor*)HeightmapInfo.HeightmapTexture->Source.LockMip(MipIndex);
|
|
FMemory::Memzero(HeightmapTextureData, MipSizeU*MipSizeV*sizeof(FColor));
|
|
HeightmapInfo.HeightmapTextureMipData.Add(HeightmapTextureData);
|
|
|
|
MipSizeU >>= 1;
|
|
MipSizeV >>= 1;
|
|
|
|
MipSubsectionSizeQuads = ((MipSubsectionSizeQuads + 1) >> 1) - 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
const FVector DrawScale3D = GetRootComponent()->RelativeScale3D;
|
|
|
|
// Calculate the normals for each of the two triangles per quad.
|
|
FVector* VertexNormals = new FVector[(NumPatchesX + 1)*(NumPatchesY + 1)];
|
|
FMemory::Memzero(VertexNormals, (NumPatchesX + 1)*(NumPatchesY + 1)*sizeof(FVector));
|
|
for (int32 QuadY = 0; QuadY < NumPatchesY; QuadY++)
|
|
{
|
|
for (int32 QuadX = 0; QuadX < NumPatchesX; QuadX++)
|
|
{
|
|
FVector Vert00 = FVector(0.0f, 0.0f, ((float)HEIGHTDATA(QuadX + 0, QuadY + 0) - 32768.0f)*LANDSCAPE_ZSCALE) * DrawScale3D;
|
|
FVector Vert01 = FVector(0.0f, 1.0f, ((float)HEIGHTDATA(QuadX + 0, QuadY + 1) - 32768.0f)*LANDSCAPE_ZSCALE) * DrawScale3D;
|
|
FVector Vert10 = FVector(1.0f, 0.0f, ((float)HEIGHTDATA(QuadX + 1, QuadY + 0) - 32768.0f)*LANDSCAPE_ZSCALE) * DrawScale3D;
|
|
FVector Vert11 = FVector(1.0f, 1.0f, ((float)HEIGHTDATA(QuadX + 1, QuadY + 1) - 32768.0f)*LANDSCAPE_ZSCALE) * DrawScale3D;
|
|
|
|
FVector FaceNormal1 = ((Vert00 - Vert10) ^ (Vert10 - Vert11)).GetSafeNormal();
|
|
FVector FaceNormal2 = ((Vert11 - Vert01) ^ (Vert01 - Vert00)).GetSafeNormal();
|
|
|
|
// contribute to the vertex normals.
|
|
VertexNormals[(QuadX + 1 + (NumPatchesX + 1)*(QuadY + 0))] += FaceNormal1;
|
|
VertexNormals[(QuadX + 0 + (NumPatchesX + 1)*(QuadY + 1))] += FaceNormal2;
|
|
VertexNormals[(QuadX + 0 + (NumPatchesX + 1)*(QuadY + 0))] += FaceNormal1 + FaceNormal2;
|
|
VertexNormals[(QuadX + 1 + (NumPatchesX + 1)*(QuadY + 1))] += FaceNormal1 + FaceNormal2;
|
|
}
|
|
}
|
|
|
|
// Weight values for each layer for each component.
|
|
TArray<TArray<TArray<uint8> > > ComponentWeightValues;
|
|
ComponentWeightValues.AddZeroed(NumSectionsX*NumSectionsY);
|
|
|
|
for (int32 ComponentY = 0; ComponentY < NumSectionsY; ComponentY++)
|
|
{
|
|
for (int32 ComponentX = 0; ComponentX < NumSectionsX; ComponentX++)
|
|
{
|
|
ULandscapeComponent* LandscapeComponent = LandscapeComponents[ComponentX + ComponentY*NumSectionsX];
|
|
TArray<TArray<uint8> >& WeightValues = ComponentWeightValues[ComponentX + ComponentY*NumSectionsX];
|
|
|
|
// Import alphamap data into local array and check for unused layers for this component.
|
|
TArray<FLandscapeComponentAlphaInfo> EditingAlphaLayerData;
|
|
for (int32 LayerIndex = 0; LayerIndex < ImportLayerInfos.Num(); LayerIndex++)
|
|
{
|
|
FLandscapeComponentAlphaInfo* NewAlphaInfo = new(EditingAlphaLayerData) FLandscapeComponentAlphaInfo(LandscapeComponent, LayerIndex);
|
|
|
|
if (ImportLayerInfos[LayerIndex].LayerData.Num())
|
|
{
|
|
for (int32 AlphaY = 0; AlphaY <= LandscapeComponent->ComponentSizeQuads; AlphaY++)
|
|
{
|
|
const uint8* OldAlphaRowStart = &ImportLayerInfos[LayerIndex].LayerData[(AlphaY + LandscapeComponent->GetSectionBase().Y) * VertsX + (LandscapeComponent->GetSectionBase().X)];
|
|
uint8* NewAlphaRowStart = &NewAlphaInfo->AlphaValues[AlphaY * (LandscapeComponent->ComponentSizeQuads + 1)];
|
|
FMemory::Memcpy(NewAlphaRowStart, OldAlphaRowStart, LandscapeComponent->ComponentSizeQuads + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int32 AlphaMapIndex = 0; AlphaMapIndex < EditingAlphaLayerData.Num(); AlphaMapIndex++)
|
|
{
|
|
if (EditingAlphaLayerData[AlphaMapIndex].IsLayerAllZero())
|
|
{
|
|
EditingAlphaLayerData.RemoveAt(AlphaMapIndex);
|
|
AlphaMapIndex--;
|
|
}
|
|
}
|
|
|
|
|
|
UE_LOG(LogLandscape, Log, TEXT("%s needs %d alphamaps"), *LandscapeComponent->GetName(), EditingAlphaLayerData.Num());
|
|
|
|
// Calculate weightmap weights for this component
|
|
WeightValues.Empty(EditingAlphaLayerData.Num());
|
|
WeightValues.AddZeroed(EditingAlphaLayerData.Num());
|
|
LandscapeComponent->WeightmapLayerAllocations.Empty(EditingAlphaLayerData.Num());
|
|
|
|
TArray<bool> IsNoBlendArray;
|
|
IsNoBlendArray.Empty(EditingAlphaLayerData.Num());
|
|
IsNoBlendArray.AddZeroed(EditingAlphaLayerData.Num());
|
|
|
|
for (int32 WeightLayerIndex = 0; WeightLayerIndex < WeightValues.Num(); WeightLayerIndex++)
|
|
{
|
|
// Lookup the original layer name
|
|
WeightValues[WeightLayerIndex] = EditingAlphaLayerData[WeightLayerIndex].AlphaValues;
|
|
new(LandscapeComponent->WeightmapLayerAllocations) FWeightmapLayerAllocationInfo(ImportLayerInfos[EditingAlphaLayerData[WeightLayerIndex].LayerIndex].LayerInfo);
|
|
IsNoBlendArray[WeightLayerIndex] = ImportLayerInfos[EditingAlphaLayerData[WeightLayerIndex].LayerIndex].LayerInfo->bNoWeightBlend;
|
|
}
|
|
|
|
// Discard the temporary alpha data
|
|
EditingAlphaLayerData.Empty();
|
|
|
|
// For each layer...
|
|
for (int32 WeightLayerIndex = WeightValues.Num() - 1; WeightLayerIndex >= 0; WeightLayerIndex--)
|
|
{
|
|
// ... multiply all lower layers'...
|
|
for (int32 BelowWeightLayerIndex = WeightLayerIndex - 1; BelowWeightLayerIndex >= 0; BelowWeightLayerIndex--)
|
|
{
|
|
int32 TotalWeight = 0;
|
|
|
|
if (IsNoBlendArray[BelowWeightLayerIndex])
|
|
{
|
|
continue; // skip no blend
|
|
}
|
|
|
|
// ... values by...
|
|
for (int32 Idx = 0; Idx < WeightValues[WeightLayerIndex].Num(); Idx++)
|
|
{
|
|
// ... one-minus the current layer's values
|
|
int32 NewValue = (int32)WeightValues[BelowWeightLayerIndex][Idx] * (int32)(255 - WeightValues[WeightLayerIndex][Idx]) / 255;
|
|
WeightValues[BelowWeightLayerIndex][Idx] = (uint8)NewValue;
|
|
TotalWeight += NewValue;
|
|
}
|
|
|
|
if (TotalWeight == 0)
|
|
{
|
|
// Remove the layer as it has no contribution
|
|
WeightValues.RemoveAt(BelowWeightLayerIndex);
|
|
LandscapeComponent->WeightmapLayerAllocations.RemoveAt(BelowWeightLayerIndex);
|
|
IsNoBlendArray.RemoveAt(BelowWeightLayerIndex);
|
|
|
|
// The current layer has been re-numbered
|
|
WeightLayerIndex--;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Weight normalization for total should be 255...
|
|
if (WeightValues.Num())
|
|
{
|
|
for (int32 Idx = 0; Idx < WeightValues[0].Num(); Idx++)
|
|
{
|
|
int32 TotalWeight = 0;
|
|
int32 MaxLayerIdx = -1;
|
|
int32 MaxWeight = INT_MIN;
|
|
|
|
for (int32 WeightLayerIndex = 0; WeightLayerIndex < WeightValues.Num(); WeightLayerIndex++)
|
|
{
|
|
if (!IsNoBlendArray[WeightLayerIndex])
|
|
{
|
|
int32 Weight = WeightValues[WeightLayerIndex][Idx];
|
|
TotalWeight += Weight;
|
|
if (MaxWeight < Weight)
|
|
{
|
|
MaxWeight = Weight;
|
|
MaxLayerIdx = WeightLayerIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (TotalWeight == 0)
|
|
{
|
|
if (MaxLayerIdx >= 0)
|
|
{
|
|
WeightValues[MaxLayerIdx][Idx] = 255;
|
|
}
|
|
}
|
|
else if (TotalWeight != 255)
|
|
{
|
|
// normalization...
|
|
float Factor = 255.0f / TotalWeight;
|
|
TotalWeight = 0;
|
|
for (int32 WeightLayerIndex = 0; WeightLayerIndex < WeightValues.Num(); WeightLayerIndex++)
|
|
{
|
|
if (!IsNoBlendArray[WeightLayerIndex])
|
|
{
|
|
WeightValues[WeightLayerIndex][Idx] = (uint8)(Factor * WeightValues[WeightLayerIndex][Idx]);
|
|
TotalWeight += WeightValues[WeightLayerIndex][Idx];
|
|
}
|
|
}
|
|
|
|
if (255 - TotalWeight && MaxLayerIdx >= 0)
|
|
{
|
|
WeightValues[MaxLayerIdx][Idx] += 255 - TotalWeight;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remember where we have spare texture channels.
|
|
TArray<FWeightmapTextureAllocation> TextureAllocations;
|
|
|
|
for (int32 ComponentY = 0; ComponentY < NumSectionsY; ComponentY++)
|
|
{
|
|
int32 HmY = ComponentY / ComponentsPerHeightmap;
|
|
int32 HeightmapOffsetY = (ComponentY - ComponentsPerHeightmap*HmY) * NumSubsections * (SubsectionSizeQuads + 1);
|
|
|
|
for (int32 ComponentX = 0; ComponentX < NumSectionsX; ComponentX++)
|
|
{
|
|
int32 HmX = ComponentX / ComponentsPerHeightmap;
|
|
FHeightmapInfo& HeightmapInfo = HeightmapInfos[HmX + HmY * NumHeightmapsX];
|
|
|
|
ULandscapeComponent* LandscapeComponent = LandscapeComponents[ComponentX + ComponentY*NumSectionsX];
|
|
|
|
// Lookup array of weight values for this component.
|
|
TArray<TArray<uint8> >& WeightValues = ComponentWeightValues[ComponentX + ComponentY*NumSectionsX];
|
|
|
|
// Heightmap offsets
|
|
int32 HeightmapOffsetX = (ComponentX - ComponentsPerHeightmap*HmX) * NumSubsections * (SubsectionSizeQuads + 1);
|
|
|
|
LandscapeComponent->HeightmapScaleBias = FVector4(1.0f / (float)HeightmapInfo.HeightmapSizeU, 1.0f / (float)HeightmapInfo.HeightmapSizeV, (float)((HeightmapOffsetX)) / (float)HeightmapInfo.HeightmapSizeU, ((float)(HeightmapOffsetY)) / (float)HeightmapInfo.HeightmapSizeV);
|
|
LandscapeComponent->HeightmapTexture = HeightmapInfo.HeightmapTexture;
|
|
|
|
// Weightmap is sized the same as the component
|
|
int32 WeightmapSize = (SubsectionSizeQuads + 1) * NumSubsections;
|
|
// Should be power of two
|
|
check(((WeightmapSize - 1) & WeightmapSize) == 0);
|
|
|
|
LandscapeComponent->WeightmapScaleBias = FVector4(1.0f / (float)WeightmapSize, 1.0f / (float)WeightmapSize, 0.5f / (float)WeightmapSize, 0.5f / (float)WeightmapSize);
|
|
LandscapeComponent->WeightmapSubsectionOffset = (float)(SubsectionSizeQuads + 1) / (float)WeightmapSize;
|
|
|
|
// Pointers to the texture data where we'll store each layer. Stride is 4 (FColor)
|
|
TArray<uint8*> WeightmapTextureDataPointers;
|
|
|
|
UE_LOG(LogLandscape, Log, TEXT("%s needs %d weightmap channels"), *LandscapeComponent->GetName(), WeightValues.Num());
|
|
|
|
// Find texture channels to store each layer.
|
|
int32 LayerIndex = 0;
|
|
while (LayerIndex < WeightValues.Num())
|
|
{
|
|
int32 RemainingLayers = WeightValues.Num() - LayerIndex;
|
|
|
|
int32 BestAllocationIndex = -1;
|
|
|
|
// if we need less than 4 channels, try to find them somewhere to put all of them
|
|
if (RemainingLayers < 4)
|
|
{
|
|
int32 BestDistSquared = MAX_int32;
|
|
for (int32 TryAllocIdx = 0; TryAllocIdx < TextureAllocations.Num(); TryAllocIdx++)
|
|
{
|
|
if (TextureAllocations[TryAllocIdx].ChannelsInUse + RemainingLayers <= 4)
|
|
{
|
|
FWeightmapTextureAllocation& TryAllocation = TextureAllocations[TryAllocIdx];
|
|
int32 TryDistSquared = FMath::Square(TryAllocation.X - ComponentX) + FMath::Square(TryAllocation.Y - ComponentY);
|
|
if (TryDistSquared < BestDistSquared)
|
|
{
|
|
BestDistSquared = TryDistSquared;
|
|
BestAllocationIndex = TryAllocIdx;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (BestAllocationIndex != -1)
|
|
{
|
|
FWeightmapTextureAllocation& Allocation = TextureAllocations[BestAllocationIndex];
|
|
FLandscapeWeightmapUsage& WeightmapUsage = WeightmapUsageMap.FindChecked(Allocation.Texture);
|
|
|
|
UE_LOG(LogLandscape, Log, TEXT(" ==> Storing %d channels starting at %s[%d]"), RemainingLayers, *Allocation.Texture->GetName(), Allocation.ChannelsInUse);
|
|
|
|
for (int32 i = 0; i < RemainingLayers; i++)
|
|
{
|
|
LandscapeComponent->WeightmapLayerAllocations[LayerIndex + i].WeightmapTextureIndex = LandscapeComponent->WeightmapTextures.Num();
|
|
LandscapeComponent->WeightmapLayerAllocations[LayerIndex + i].WeightmapTextureChannel = Allocation.ChannelsInUse;
|
|
WeightmapUsage.ChannelUsage[Allocation.ChannelsInUse] = LandscapeComponent;
|
|
switch (Allocation.ChannelsInUse)
|
|
{
|
|
case 1:
|
|
WeightmapTextureDataPointers.Add((uint8*)&Allocation.TextureData->G);
|
|
break;
|
|
case 2:
|
|
WeightmapTextureDataPointers.Add((uint8*)&Allocation.TextureData->B);
|
|
break;
|
|
case 3:
|
|
WeightmapTextureDataPointers.Add((uint8*)&Allocation.TextureData->A);
|
|
break;
|
|
default:
|
|
// this should not occur.
|
|
check(0);
|
|
|
|
}
|
|
Allocation.ChannelsInUse++;
|
|
}
|
|
|
|
LayerIndex += RemainingLayers;
|
|
LandscapeComponent->WeightmapTextures.Add(Allocation.Texture);
|
|
}
|
|
else
|
|
{
|
|
// We couldn't find a suitable place for these layers, so lets make a new one.
|
|
UTexture2D* WeightmapTexture = CreateLandscapeTexture(WeightmapSize, WeightmapSize, TEXTUREGROUP_Terrain_Weightmap, TSF_BGRA8);
|
|
FColor* MipData = (FColor*)WeightmapTexture->Source.LockMip(0);
|
|
|
|
int32 ThisAllocationLayers = FMath::Min<int32>(RemainingLayers, 4);
|
|
new(TextureAllocations)FWeightmapTextureAllocation(ComponentX, ComponentY, ThisAllocationLayers, WeightmapTexture, MipData);
|
|
FLandscapeWeightmapUsage& WeightmapUsage = WeightmapUsageMap.Add(WeightmapTexture, FLandscapeWeightmapUsage());
|
|
|
|
UE_LOG(LogLandscape, Log, TEXT(" ==> Storing %d channels in new texture %s"), ThisAllocationLayers, *WeightmapTexture->GetName());
|
|
|
|
WeightmapTextureDataPointers.Add((uint8*)&MipData->R);
|
|
LandscapeComponent->WeightmapLayerAllocations[LayerIndex + 0].WeightmapTextureIndex = LandscapeComponent->WeightmapTextures.Num();
|
|
LandscapeComponent->WeightmapLayerAllocations[LayerIndex + 0].WeightmapTextureChannel = 0;
|
|
WeightmapUsage.ChannelUsage[0] = LandscapeComponent;
|
|
|
|
if (ThisAllocationLayers > 1)
|
|
{
|
|
WeightmapTextureDataPointers.Add((uint8*)&MipData->G);
|
|
LandscapeComponent->WeightmapLayerAllocations[LayerIndex + 1].WeightmapTextureIndex = LandscapeComponent->WeightmapTextures.Num();
|
|
LandscapeComponent->WeightmapLayerAllocations[LayerIndex + 1].WeightmapTextureChannel = 1;
|
|
WeightmapUsage.ChannelUsage[1] = LandscapeComponent;
|
|
|
|
if (ThisAllocationLayers > 2)
|
|
{
|
|
WeightmapTextureDataPointers.Add((uint8*)&MipData->B);
|
|
LandscapeComponent->WeightmapLayerAllocations[LayerIndex + 2].WeightmapTextureIndex = LandscapeComponent->WeightmapTextures.Num();
|
|
LandscapeComponent->WeightmapLayerAllocations[LayerIndex + 2].WeightmapTextureChannel = 2;
|
|
WeightmapUsage.ChannelUsage[2] = LandscapeComponent;
|
|
|
|
if (ThisAllocationLayers > 3)
|
|
{
|
|
WeightmapTextureDataPointers.Add((uint8*)&MipData->A);
|
|
LandscapeComponent->WeightmapLayerAllocations[LayerIndex + 3].WeightmapTextureIndex = LandscapeComponent->WeightmapTextures.Num();
|
|
LandscapeComponent->WeightmapLayerAllocations[LayerIndex + 3].WeightmapTextureChannel = 3;
|
|
WeightmapUsage.ChannelUsage[3] = LandscapeComponent;
|
|
}
|
|
}
|
|
}
|
|
LandscapeComponent->WeightmapTextures.Add(WeightmapTexture);
|
|
|
|
LayerIndex += ThisAllocationLayers;
|
|
}
|
|
}
|
|
check(WeightmapTextureDataPointers.Num() == WeightValues.Num());
|
|
|
|
FVector* LocalVerts = new FVector[FMath::Square(ComponentSizeQuads + 1)];
|
|
|
|
for (int32 SubsectionY = 0; SubsectionY < NumSubsections; SubsectionY++)
|
|
{
|
|
for (int32 SubsectionX = 0; SubsectionX < NumSubsections; SubsectionX++)
|
|
{
|
|
for (int32 SubY = 0; SubY <= SubsectionSizeQuads; SubY++)
|
|
{
|
|
for (int32 SubX = 0; SubX <= SubsectionSizeQuads; SubX++)
|
|
{
|
|
// X/Y of the vertex we're looking at in component's coordinates.
|
|
int32 CompX = SubsectionSizeQuads * SubsectionX + SubX;
|
|
int32 CompY = SubsectionSizeQuads * SubsectionY + SubY;
|
|
|
|
// X/Y of the vertex we're looking indexed into the texture data
|
|
int32 TexX = (SubsectionSizeQuads + 1) * SubsectionX + SubX;
|
|
int32 TexY = (SubsectionSizeQuads + 1) * SubsectionY + SubY;
|
|
|
|
int32 WeightSrcDataIdx = CompY * (ComponentSizeQuads + 1) + CompX;
|
|
int32 HeightTexDataIdx = (HeightmapOffsetX + TexX) + (HeightmapOffsetY + TexY) * (HeightmapInfo.HeightmapSizeU);
|
|
|
|
int32 WeightTexDataIdx = (TexX)+(TexY)* (WeightmapSize);
|
|
|
|
// copy height and normal data
|
|
uint16 HeightValue = HEIGHTDATA(CompX + LandscapeComponent->GetSectionBase().X, CompY + LandscapeComponent->GetSectionBase().Y);
|
|
FVector Normal = VertexNormals[CompX + LandscapeComponent->GetSectionBase().X + (NumPatchesX + 1)*(CompY + LandscapeComponent->GetSectionBase().Y)].GetSafeNormal();
|
|
|
|
HeightmapInfo.HeightmapTextureMipData[0][HeightTexDataIdx].R = HeightValue >> 8;
|
|
HeightmapInfo.HeightmapTextureMipData[0][HeightTexDataIdx].G = HeightValue & 255;
|
|
HeightmapInfo.HeightmapTextureMipData[0][HeightTexDataIdx].B = FMath::RoundToInt(127.5f * (Normal.X + 1.0f));
|
|
HeightmapInfo.HeightmapTextureMipData[0][HeightTexDataIdx].A = FMath::RoundToInt(127.5f * (Normal.Y + 1.0f));
|
|
|
|
for (int32 WeightmapIndex = 0; WeightmapIndex < WeightValues.Num(); WeightmapIndex++)
|
|
{
|
|
WeightmapTextureDataPointers[WeightmapIndex][WeightTexDataIdx * 4] = WeightValues[WeightmapIndex][WeightSrcDataIdx];
|
|
}
|
|
|
|
// Get local space verts
|
|
FVector LocalVertex(CompX, CompY, LandscapeDataAccess::GetLocalHeight(HeightValue));
|
|
LocalVerts[(LandscapeComponent->ComponentSizeQuads + 1) * CompY + CompX] = LocalVertex;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
LandscapeComponent->CachedLocalBox = FBox(LocalVerts, FMath::Square(ComponentSizeQuads + 1));
|
|
delete[] LocalVerts;
|
|
|
|
// Update MaterialInstance
|
|
LandscapeComponent->UpdateMaterialInstances();
|
|
}
|
|
}
|
|
|
|
// Unlock the weightmaps' base mips
|
|
for (int32 AllocationIndex = 0; AllocationIndex < TextureAllocations.Num(); AllocationIndex++)
|
|
{
|
|
UTexture2D* WeightmapTexture = TextureAllocations[AllocationIndex].Texture;
|
|
FColor* BaseMipData = TextureAllocations[AllocationIndex].TextureData;
|
|
|
|
// Generate mips for weightmaps
|
|
ULandscapeComponent::GenerateWeightmapMips(NumSubsections, SubsectionSizeQuads, WeightmapTexture, BaseMipData);
|
|
|
|
WeightmapTexture->Source.UnlockMip(0);
|
|
WeightmapTexture->PostEditChange();
|
|
}
|
|
|
|
delete[] VertexNormals;
|
|
|
|
// Generate mipmaps for the components, and create the collision components
|
|
for (int32 ComponentY = 0; ComponentY < NumSectionsY; ComponentY++)
|
|
{
|
|
for (int32 ComponentX = 0; ComponentX < NumSectionsX; ComponentX++)
|
|
{
|
|
int32 HmX = ComponentX / ComponentsPerHeightmap;
|
|
int32 HmY = ComponentY / ComponentsPerHeightmap;
|
|
FHeightmapInfo& HeightmapInfo = HeightmapInfos[HmX + HmY * NumHeightmapsX];
|
|
|
|
ULandscapeComponent* LandscapeComponent = LandscapeComponents[ComponentX + ComponentY*NumSectionsX];
|
|
LandscapeComponent->GenerateHeightmapMips(HeightmapInfo.HeightmapTextureMipData, ComponentX == NumSectionsX - 1 ? MAX_int32 : 0, ComponentY == NumSectionsY - 1 ? MAX_int32 : 0);
|
|
LandscapeComponent->UpdateCollisionHeightData(HeightmapInfo.HeightmapTextureMipData[LandscapeComponent->CollisionMipLevel]);
|
|
LandscapeComponent->UpdateCollisionLayerData();
|
|
}
|
|
}
|
|
|
|
for (int32 HmIdx = 0; HmIdx < HeightmapInfos.Num(); HmIdx++)
|
|
{
|
|
FHeightmapInfo& HeightmapInfo = HeightmapInfos[HmIdx];
|
|
|
|
// Add remaining mips down to 1x1 to heightmap texture. These do not represent quads and are just a simple averages of the previous mipmaps.
|
|
// These mips are not used for sampling in the vertex shader but could be sampled in the pixel shader.
|
|
int32 Mip = HeightmapInfo.HeightmapTextureMipData.Num();
|
|
int32 MipSizeU = (HeightmapInfo.HeightmapTexture->Source.GetSizeX()) >> Mip;
|
|
int32 MipSizeV = (HeightmapInfo.HeightmapTexture->Source.GetSizeY()) >> Mip;
|
|
while (MipSizeU > 1 && MipSizeV > 1)
|
|
{
|
|
HeightmapInfo.HeightmapTextureMipData.Add((FColor*)HeightmapInfo.HeightmapTexture->Source.LockMip(Mip));
|
|
int32 PrevMipSizeU = (HeightmapInfo.HeightmapTexture->Source.GetSizeX()) >> (Mip - 1);
|
|
int32 PrevMipSizeV = (HeightmapInfo.HeightmapTexture->Source.GetSizeY()) >> (Mip - 1);
|
|
|
|
for (int32 Y = 0; Y < MipSizeV; Y++)
|
|
{
|
|
for (int32 X = 0; X < MipSizeU; X++)
|
|
{
|
|
FColor* TexData = &(HeightmapInfo.HeightmapTextureMipData[Mip])[X + Y * MipSizeU];
|
|
|
|
FColor *PreMipTexData00 = &(HeightmapInfo.HeightmapTextureMipData[Mip - 1])[(X * 2 + 0) + (Y * 2 + 0) * PrevMipSizeU];
|
|
FColor *PreMipTexData01 = &(HeightmapInfo.HeightmapTextureMipData[Mip - 1])[(X * 2 + 0) + (Y * 2 + 1) * PrevMipSizeU];
|
|
FColor *PreMipTexData10 = &(HeightmapInfo.HeightmapTextureMipData[Mip - 1])[(X * 2 + 1) + (Y * 2 + 0) * PrevMipSizeU];
|
|
FColor *PreMipTexData11 = &(HeightmapInfo.HeightmapTextureMipData[Mip - 1])[(X * 2 + 1) + (Y * 2 + 1) * PrevMipSizeU];
|
|
|
|
TexData->R = (((int32)PreMipTexData00->R + (int32)PreMipTexData01->R + (int32)PreMipTexData10->R + (int32)PreMipTexData11->R) >> 2);
|
|
TexData->G = (((int32)PreMipTexData00->G + (int32)PreMipTexData01->G + (int32)PreMipTexData10->G + (int32)PreMipTexData11->G) >> 2);
|
|
TexData->B = (((int32)PreMipTexData00->B + (int32)PreMipTexData01->B + (int32)PreMipTexData10->B + (int32)PreMipTexData11->B) >> 2);
|
|
TexData->A = (((int32)PreMipTexData00->A + (int32)PreMipTexData01->A + (int32)PreMipTexData10->A + (int32)PreMipTexData11->A) >> 2);
|
|
}
|
|
}
|
|
Mip++;
|
|
MipSizeU >>= 1;
|
|
MipSizeV >>= 1;
|
|
}
|
|
|
|
for (int32 i = 0; i < HeightmapInfo.HeightmapTextureMipData.Num(); i++)
|
|
{
|
|
HeightmapInfo.HeightmapTexture->Source.UnlockMip(i);
|
|
}
|
|
HeightmapInfo.HeightmapTexture->PostEditChange();
|
|
}
|
|
|
|
if (GetLevel()->bIsVisible)
|
|
{
|
|
// Update our new components
|
|
ReregisterAllComponents();
|
|
}
|
|
|
|
ReimportHeightmapFilePath = HeightmapFileName;
|
|
|
|
ULandscapeInfo::RecreateLandscapeInfo(GetWorld(), false);
|
|
|
|
GWarn->EndSlowTask();
|
|
}
|
|
|
|
bool ALandscapeProxy::ExportToRawMesh(int32 InExportLOD, FRawMesh& OutRawMesh) const
|
|
{
|
|
TInlineComponentArray<ULandscapeComponent*> RegisteredLandscapeComponents;
|
|
GetComponents<ULandscapeComponent>(RegisteredLandscapeComponents);
|
|
|
|
const FIntRect LandscapeSectionRect = GetBoundingRect();
|
|
const FVector2D LandscapeUVScale = FVector2D(1.0f, 1.0f) / FVector2D(LandscapeSectionRect.Size());
|
|
|
|
// User specified LOD to export
|
|
int32 LandscapeLODToExport = ExportLOD;
|
|
if (InExportLOD != INDEX_NONE)
|
|
{
|
|
LandscapeLODToExport = FMath::Clamp<int32>(InExportLOD, 0, FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1);
|
|
}
|
|
|
|
// Export data for each component
|
|
for (auto It = RegisteredLandscapeComponents.CreateConstIterator(); It; ++It)
|
|
{
|
|
ULandscapeComponent* Component = (*It);
|
|
FLandscapeComponentDataInterface CDI(Component, LandscapeLODToExport);
|
|
const int32 ComponentSizeQuadsLOD = ((Component->ComponentSizeQuads + 1) >> LandscapeLODToExport) - 1;
|
|
const int32 SubsectionSizeQuadsLOD = ((Component->SubsectionSizeQuads + 1) >> LandscapeLODToExport) - 1;
|
|
const FIntPoint ComponentOffsetQuads = Component->GetSectionBase() - LandscapeSectionOffset - LandscapeSectionRect.Min;
|
|
const FVector2D ComponentUVOffsetLOD = FVector2D(ComponentOffsetQuads)*((float)ComponentSizeQuadsLOD / ComponentSizeQuads);
|
|
const FVector2D ComponentUVScaleLOD = LandscapeUVScale*((float)ComponentSizeQuads / ComponentSizeQuadsLOD);
|
|
|
|
const int32 NumFaces = FMath::Square(ComponentSizeQuadsLOD) * 2;
|
|
const int32 NumVertices = NumFaces * 3;
|
|
const int32 VerticesOffset = OutRawMesh.VertexPositions.Num();
|
|
const int32 IndicesOffset = OutRawMesh.WedgeIndices.Num();
|
|
|
|
//
|
|
OutRawMesh.FaceMaterialIndices.AddZeroed(NumFaces);
|
|
OutRawMesh.FaceSmoothingMasks.AddZeroed(NumFaces);
|
|
|
|
OutRawMesh.VertexPositions.AddZeroed(NumVertices);
|
|
OutRawMesh.WedgeIndices.AddZeroed(NumVertices);
|
|
OutRawMesh.WedgeTangentX.AddZeroed(NumVertices);
|
|
OutRawMesh.WedgeTangentY.AddZeroed(NumVertices);
|
|
OutRawMesh.WedgeTangentZ.AddZeroed(NumVertices);
|
|
OutRawMesh.WedgeTexCoords[0].AddZeroed(NumVertices);
|
|
|
|
|
|
// Check if there are any holes
|
|
TArray<uint8> VisDataMap;
|
|
|
|
for (int32 AllocIdx = 0; AllocIdx < Component->WeightmapLayerAllocations.Num(); AllocIdx++)
|
|
{
|
|
FWeightmapLayerAllocationInfo& AllocInfo = Component->WeightmapLayerAllocations[AllocIdx];
|
|
if (AllocInfo.LayerInfo == ALandscapeProxy::VisibilityLayer)
|
|
{
|
|
CDI.GetWeightmapTextureData(AllocInfo.LayerInfo, VisDataMap);
|
|
}
|
|
}
|
|
|
|
const FIntPoint QuadPattern[6] =
|
|
{
|
|
//face 1
|
|
FIntPoint(0, 0),
|
|
FIntPoint(0, 1),
|
|
FIntPoint(1, 1),
|
|
//face 2
|
|
FIntPoint(0, 0),
|
|
FIntPoint(1, 1),
|
|
FIntPoint(1, 0),
|
|
};
|
|
|
|
const int32 VisThreshold = 170;
|
|
const int32 WeightMapSize = (SubsectionSizeQuadsLOD + 1) * Component->NumSubsections;
|
|
uint32* Faces = OutRawMesh.WedgeIndices.GetData() + IndicesOffset;
|
|
|
|
// Export verts
|
|
int32 VertexIdx = VerticesOffset;
|
|
for (int32 y = 0; y < ComponentSizeQuadsLOD; y++)
|
|
{
|
|
for (int32 x = 0; x < ComponentSizeQuadsLOD; x++)
|
|
{
|
|
// Fill indices
|
|
{
|
|
|
|
// Whether this vertex is in hole
|
|
bool bInvisible = false;
|
|
if (VisDataMap.Num())
|
|
{
|
|
int32 TexelX, TexelY;
|
|
CDI.VertexXYToTexelXY(x, y, TexelX, TexelY);
|
|
bInvisible = (VisDataMap[CDI.TexelXYToIndex(TexelX, TexelY)] >= VisThreshold);
|
|
}
|
|
|
|
// triangulation matches FLandscapeIndexBuffer constructor
|
|
Faces[0] = VertexIdx;
|
|
Faces[1] = bInvisible ? Faces[0] : VertexIdx + 1;
|
|
Faces[2] = bInvisible ? Faces[0] : VertexIdx + 2;
|
|
Faces += 3;
|
|
|
|
Faces[0] = VertexIdx + 3;
|
|
Faces[1] = bInvisible ? Faces[0] : VertexIdx + 4;
|
|
Faces[2] = bInvisible ? Faces[0] : VertexIdx + 5;
|
|
Faces += 3;
|
|
}
|
|
|
|
// Fill vertices
|
|
for (int32 i = 0; i < ARRAY_COUNT(QuadPattern); i++)
|
|
{
|
|
int32 VertexX = x + QuadPattern[i].X;
|
|
int32 VertexY = y + QuadPattern[i].Y;
|
|
FVector LocalVertexPos = CDI.GetWorldVertex(VertexX, VertexY);
|
|
|
|
FVector LocalTangentX, LocalTangentY, LocalTangentZ;
|
|
CDI.GetLocalTangentVectors(VertexX, VertexY, LocalTangentX, LocalTangentY, LocalTangentZ);
|
|
|
|
OutRawMesh.VertexPositions[VertexIdx] = LocalVertexPos;
|
|
OutRawMesh.WedgeTangentX[VertexIdx] = LocalTangentX;
|
|
OutRawMesh.WedgeTangentY[VertexIdx] = LocalTangentY;
|
|
OutRawMesh.WedgeTangentZ[VertexIdx] = LocalTangentZ;
|
|
|
|
OutRawMesh.WedgeTexCoords[0][VertexIdx] = (ComponentUVOffsetLOD + FVector2D(VertexX, VertexY))*ComponentUVScaleLOD;
|
|
|
|
VertexIdx++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add lightmap UVs
|
|
OutRawMesh.WedgeTexCoords[1].Append(OutRawMesh.WedgeTexCoords[0]);
|
|
|
|
return true;
|
|
}
|
|
|
|
FIntRect ALandscapeProxy::GetBoundingRect() const
|
|
{
|
|
FIntRect Rect(MAX_int32, MAX_int32, MIN_int32, MIN_int32);
|
|
|
|
for (int32 CompIdx = 0; CompIdx < LandscapeComponents.Num(); CompIdx++)
|
|
{
|
|
Rect.Include(LandscapeComponents[CompIdx]->GetSectionBase());
|
|
}
|
|
|
|
if (LandscapeComponents.Num() > 0)
|
|
{
|
|
Rect.Max += FIntPoint(ComponentSizeQuads, ComponentSizeQuads);
|
|
Rect -= LandscapeSectionOffset;
|
|
}
|
|
else
|
|
{
|
|
Rect = FIntRect();
|
|
}
|
|
|
|
return Rect;
|
|
}
|
|
|
|
bool ALandscape::HasAllComponent()
|
|
{
|
|
ULandscapeInfo* Info = GetLandscapeInfo();
|
|
if (Info && Info->XYtoComponentMap.Num() == LandscapeComponents.Num())
|
|
{
|
|
// all components are owned by this Landscape actor (no Landscape Proxies)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ULandscapeInfo::GetLandscapeExtent(int32& MinX, int32& MinY, int32& MaxX, int32& MaxY) const
|
|
{
|
|
MinX = MAX_int32;
|
|
MinY = MAX_int32;
|
|
MaxX = MIN_int32;
|
|
MaxY = MIN_int32;
|
|
|
|
// Find range of entire landscape
|
|
for (auto& XYComponentPair : XYtoComponentMap)
|
|
{
|
|
const ULandscapeComponent* Comp = XYComponentPair.Value;
|
|
Comp->GetComponentExtent(MinX, MinY, MaxX, MaxY);
|
|
}
|
|
return (MinX != MAX_int32);
|
|
}
|
|
|
|
bool ULandscapeInfo::GetSelectedExtent(int32& MinX, int32& MinY, int32& MaxX, int32& MaxY) const
|
|
{
|
|
MinX = MinY = MAX_int32;
|
|
MaxX = MaxY = MIN_int32;
|
|
for (auto& SelectedPointPair : SelectedRegion)
|
|
{
|
|
int32 X, Y;
|
|
ALandscape::UnpackKey(SelectedPointPair.Key, X, Y);
|
|
if (MinX > X) MinX = X;
|
|
if (MaxX < X) MaxX = X;
|
|
if (MinY > Y) MinY = Y;
|
|
if (MaxY < Y) MaxY = Y;
|
|
}
|
|
if (MinX != MAX_int32)
|
|
{
|
|
return true;
|
|
}
|
|
// if SelectedRegion is empty, try SelectedComponents
|
|
for (const ULandscapeComponent* Comp : SelectedComponents)
|
|
{
|
|
Comp->GetComponentExtent(MinX, MinY, MaxX, MaxY);
|
|
}
|
|
return MinX != MAX_int32;
|
|
}
|
|
|
|
FVector ULandscapeInfo::GetLandscapeCenterPos(float& LengthZ, int32 MinX /*= MAX_INT*/, int32 MinY /*= MAX_INT*/, int32 MaxX /*= MIN_INT*/, int32 MaxY /*= MIN_INT*/)
|
|
{
|
|
// MinZ, MaxZ is Local coordinate
|
|
float MaxZ = -HALF_WORLD_MAX, MinZ = HALF_WORLD_MAX;
|
|
const float ScaleZ = DrawScale.Z;
|
|
|
|
if (MinX == MAX_int32)
|
|
{
|
|
// Find range of entire landscape
|
|
for (auto It = XYtoComponentMap.CreateIterator(); It; ++It)
|
|
{
|
|
ULandscapeComponent* Comp = It.Value();
|
|
Comp->GetComponentExtent(MinX, MinY, MaxX, MaxY);
|
|
}
|
|
|
|
const int32 Dist = (ComponentSizeQuads + 1) >> 1; // Should be same in ALandscapeGizmoActiveActor::SetTargetLandscape
|
|
FVector2D MidPoint(((float)(MinX + MaxX)) / 2.0f, ((float)(MinY + MaxY)) / 2.0f);
|
|
MinX = FMath::FloorToInt(MidPoint.X) - Dist;
|
|
MaxX = FMath::CeilToInt(MidPoint.X) + Dist;
|
|
MinY = FMath::FloorToInt(MidPoint.Y) - Dist;
|
|
MaxY = FMath::CeilToInt(MidPoint.Y) + Dist;
|
|
check(MidPoint.X == ((float)(MinX + MaxX)) / 2.0f && MidPoint.Y == ((float)(MinY + MaxY)) / 2.0f);
|
|
}
|
|
|
|
check(MinX != MAX_int32);
|
|
//if (MinX != MAX_int32)
|
|
{
|
|
int32 CompX1, CompX2, CompY1, CompY2;
|
|
ALandscape::CalcComponentIndicesOverlap(MinX, MinY, MaxX, MaxY, ComponentSizeQuads, CompX1, CompY1, CompX2, CompY2);
|
|
for (int32 IndexY = CompY1; IndexY <= CompY2; ++IndexY)
|
|
{
|
|
for (int32 IndexX = CompX1; IndexX <= CompX2; ++IndexX)
|
|
{
|
|
ULandscapeComponent* Comp = XYtoComponentMap.FindRef(FIntPoint(IndexX, IndexY));
|
|
if (Comp)
|
|
{
|
|
ULandscapeHeightfieldCollisionComponent* CollisionComp = Comp->CollisionComponent.Get();
|
|
if (CollisionComp)
|
|
{
|
|
uint16* Heights = (uint16*)CollisionComp->CollisionHeightData.Lock(LOCK_READ_ONLY);
|
|
int32 CollisionSizeVerts = CollisionComp->CollisionSizeQuads + 1;
|
|
|
|
int32 StartX = FMath::Max(0, MinX - CollisionComp->GetSectionBase().X);
|
|
int32 StartY = FMath::Max(0, MinY - CollisionComp->GetSectionBase().Y);
|
|
int32 EndX = FMath::Min(CollisionSizeVerts, MaxX - CollisionComp->GetSectionBase().X + 1);
|
|
int32 EndY = FMath::Min(CollisionSizeVerts, MaxY - CollisionComp->GetSectionBase().Y + 1);
|
|
|
|
for (int32 Y = StartY; Y < EndY; ++Y)
|
|
{
|
|
for (int32 X = StartX; X < EndX; ++X)
|
|
{
|
|
float Height = LandscapeDataAccess::GetLocalHeight(Heights[X + Y*CollisionSizeVerts]);
|
|
MaxZ = FMath::Max(Height, MaxZ);
|
|
MinZ = FMath::Min(Height, MinZ);
|
|
}
|
|
}
|
|
CollisionComp->CollisionHeightData.Unlock();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const float MarginZ = 3;
|
|
if (MaxZ < MinZ)
|
|
{
|
|
MaxZ = +MarginZ;
|
|
MinZ = -MarginZ;
|
|
}
|
|
LengthZ = (MaxZ - MinZ + 2 * MarginZ) * ScaleZ;
|
|
|
|
const FVector LocalPosition(((float)(MinX + MaxX)) / 2.0f, ((float)(MinY + MaxY)) / 2.0f, MinZ - MarginZ);
|
|
//return GetLandscapeProxy()->TransformLandscapeLocationToWorld(LocalPosition);
|
|
return GetLandscapeProxy()->LandscapeActorToWorld().TransformPosition(LocalPosition);
|
|
}
|
|
|
|
bool ULandscapeInfo::IsValidPosition(int32 X, int32 Y)
|
|
{
|
|
int32 CompX1, CompX2, CompY1, CompY2;
|
|
ALandscape::CalcComponentIndicesOverlap(X, Y, X, Y, ComponentSizeQuads, CompX1, CompY1, CompX2, CompY2);
|
|
if (XYtoComponentMap.FindRef(FIntPoint(CompX1, CompY1)))
|
|
{
|
|
return true;
|
|
}
|
|
if (XYtoComponentMap.FindRef(FIntPoint(CompX2, CompY2)))
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ULandscapeInfo::Export(const TArray<ULandscapeLayerInfoObject*>& LayerInfos, const TArray<FString>& Filenames)
|
|
{
|
|
check(Filenames.Num() > 0);
|
|
|
|
int32 MinX = MAX_int32;
|
|
int32 MinY = MAX_int32;
|
|
int32 MaxX = -MAX_int32;
|
|
int32 MaxY = -MAX_int32;
|
|
|
|
if (!GetLandscapeExtent(MinX, MinY, MaxX, MaxY))
|
|
{
|
|
return;
|
|
}
|
|
|
|
GWarn->BeginSlowTask(LOCTEXT("BeginExportingLandscapeTask", "Exporting Landscape"), true);
|
|
|
|
FLandscapeEditDataInterface LandscapeEdit(this);
|
|
|
|
TArray<uint8> HeightData;
|
|
HeightData.AddZeroed((1 + MaxX - MinX)*(1 + MaxY - MinY)*sizeof(uint16));
|
|
LandscapeEdit.GetHeightDataFast(MinX, MinY, MaxX, MaxY, (uint16*)HeightData.GetData(), 0);
|
|
FFileHelper::SaveArrayToFile(HeightData, *Filenames[0]);
|
|
|
|
for (int32 i = 1; i < Filenames.Num(); i++)
|
|
{
|
|
if (i <= LayerInfos.Num())
|
|
{
|
|
TArray<uint8> WeightData;
|
|
WeightData.AddZeroed((1 + MaxX - MinX)*(1 + MaxY - MinY));
|
|
ULandscapeLayerInfoObject* LayerInfo = LayerInfos[i - 1];
|
|
if (LayerInfo)
|
|
{
|
|
LandscapeEdit.GetWeightDataFast(LayerInfo, MinX, MinY, MaxX, MaxY, WeightData.GetData(), 0);
|
|
}
|
|
FFileHelper::SaveArrayToFile(WeightData, *Filenames[i]);
|
|
}
|
|
}
|
|
|
|
GWarn->EndSlowTask();
|
|
}
|
|
|
|
void ULandscapeInfo::ExportHeightmap(const FString& Filename)
|
|
{
|
|
int32 MinX = MAX_int32;
|
|
int32 MinY = MAX_int32;
|
|
int32 MaxX = -MAX_int32;
|
|
int32 MaxY = -MAX_int32;
|
|
|
|
if (!GetLandscapeExtent(MinX, MinY, MaxX, MaxY))
|
|
{
|
|
return;
|
|
}
|
|
|
|
GWarn->BeginSlowTask(LOCTEXT("BeginExportingLandscapeHeightmapTask", "Exporting Landscape Heightmap"), true);
|
|
|
|
FLandscapeEditDataInterface LandscapeEdit(this);
|
|
|
|
TArray<uint8> HeightData;
|
|
HeightData.AddZeroed((MaxX - MinX + 1) * (MaxY - MinY + 1) * sizeof(uint16));
|
|
LandscapeEdit.GetHeightDataFast(MinX, MinY, MaxX, MaxY, (uint16*)HeightData.GetData(), 0);
|
|
|
|
if (Filename.EndsWith(".png"))
|
|
{
|
|
IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>("ImageWrapper");
|
|
IImageWrapperPtr ImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG);
|
|
|
|
const TArray<uint8>* RawData = NULL;
|
|
if (ImageWrapper->SetRaw(HeightData.GetData(), HeightData.Num(), (MaxX - MinX + 1), (MaxY - MinY + 1), ERGBFormat::Gray, 16))
|
|
{
|
|
HeightData = ImageWrapper->GetCompressed();
|
|
}
|
|
}
|
|
|
|
FFileHelper::SaveArrayToFile(HeightData, *Filename);
|
|
|
|
GWarn->EndSlowTask();
|
|
}
|
|
|
|
void ULandscapeInfo::ExportLayer(ULandscapeLayerInfoObject* LayerInfo, const FString& Filename)
|
|
{
|
|
int32 MinX = MAX_int32;
|
|
int32 MinY = MAX_int32;
|
|
int32 MaxX = -MAX_int32;
|
|
int32 MaxY = -MAX_int32;
|
|
|
|
if (!GetLandscapeExtent(MinX, MinY, MaxX, MaxY))
|
|
{
|
|
return;
|
|
}
|
|
|
|
GWarn->BeginSlowTask(LOCTEXT("BeginExportingLandscapeWeightmapTask", "Exporting Landscape Layer Weightmap"), true);
|
|
|
|
TArray<uint8> WeightData;
|
|
WeightData.AddZeroed((MaxX - MinX + 1) * (MaxY - MinY + 1));
|
|
if (LayerInfo)
|
|
{
|
|
FLandscapeEditDataInterface LandscapeEdit(this);
|
|
LandscapeEdit.GetWeightDataFast(LayerInfo, MinX, MinY, MaxX, MaxY, WeightData.GetData(), 0);
|
|
}
|
|
|
|
if (Filename.EndsWith(".png"))
|
|
{
|
|
IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>("ImageWrapper");
|
|
IImageWrapperPtr ImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG);
|
|
|
|
const TArray<uint8>* RawData = NULL;
|
|
if (ImageWrapper->SetRaw(WeightData.GetData(), WeightData.Num(), (MaxX - MinX + 1), (MaxY - MinY + 1), ERGBFormat::Gray, 8))
|
|
{
|
|
WeightData = ImageWrapper->GetCompressed();
|
|
}
|
|
}
|
|
|
|
FFileHelper::SaveArrayToFile(WeightData, *Filename);
|
|
|
|
GWarn->EndSlowTask();
|
|
}
|
|
|
|
void ULandscapeInfo::DeleteLayer(ULandscapeLayerInfoObject* LayerInfo)
|
|
{
|
|
GWarn->BeginSlowTask(LOCTEXT("BeginDeletingLayerTask", "Deleting Layer"), true);
|
|
|
|
// Remove data from all components
|
|
FLandscapeEditDataInterface LandscapeEdit(this);
|
|
LandscapeEdit.DeleteLayer(LayerInfo);
|
|
|
|
// Remove from array
|
|
for (int32 j = 0; j < Layers.Num(); j++)
|
|
{
|
|
if (Layers[j].LayerInfoObj && Layers[j].LayerInfoObj == LayerInfo)
|
|
{
|
|
Layers.RemoveAt(j);
|
|
break;
|
|
}
|
|
}
|
|
|
|
ALandscape* Landscape = LandscapeActor.Get();
|
|
if (Landscape != NULL)
|
|
{
|
|
Landscape->Modify();
|
|
Landscape->EditorLayerSettings.Remove(LayerInfo);
|
|
}
|
|
|
|
for (auto It = Proxies.CreateConstIterator(); It; ++It)
|
|
{
|
|
ALandscapeProxy* Proxy = *It;
|
|
Proxy->Modify();
|
|
Proxy->EditorLayerSettings.Remove(LayerInfo);
|
|
}
|
|
|
|
//UpdateLayerInfoMap();
|
|
|
|
GWarn->EndSlowTask();
|
|
}
|
|
|
|
void ULandscapeInfo::ReplaceLayer(ULandscapeLayerInfoObject* FromLayerInfo, ULandscapeLayerInfoObject* ToLayerInfo)
|
|
{
|
|
if (ensure(FromLayerInfo != ToLayerInfo))
|
|
{
|
|
GWarn->BeginSlowTask(LOCTEXT("BeginReplacingLayerTask", "Replacing Layer"), true);
|
|
|
|
// Remove data from all components
|
|
FLandscapeEditDataInterface LandscapeEdit(this);
|
|
LandscapeEdit.ReplaceLayer(FromLayerInfo, ToLayerInfo);
|
|
|
|
// Convert array
|
|
for (int32 j = 0; j < Layers.Num(); j++)
|
|
{
|
|
if (Layers[j].LayerInfoObj && Layers[j].LayerInfoObj == FromLayerInfo)
|
|
{
|
|
Layers[j].LayerInfoObj = ToLayerInfo;
|
|
}
|
|
}
|
|
|
|
ALandscape* Landscape = LandscapeActor.Get();
|
|
if (Landscape != NULL)
|
|
{
|
|
Landscape->Modify();
|
|
FLandscapeEditorLayerSettings* ToEditorLayerSettings = Landscape->EditorLayerSettings.FindByKey(ToLayerInfo);
|
|
if (ToEditorLayerSettings != NULL)
|
|
{
|
|
// If the new layer already exists, simple remove the old layer
|
|
Landscape->EditorLayerSettings.Remove(FromLayerInfo);
|
|
}
|
|
else
|
|
{
|
|
FLandscapeEditorLayerSettings* FromEditorLayerSettings = Landscape->EditorLayerSettings.FindByKey(FromLayerInfo);
|
|
if (FromEditorLayerSettings != NULL)
|
|
{
|
|
// If only the old layer exists (most common case), change it to point to the new layer info
|
|
FromEditorLayerSettings->LayerInfoObj = ToLayerInfo;
|
|
}
|
|
else
|
|
{
|
|
// If neither exists in the EditorLayerSettings cache, add it
|
|
Landscape->EditorLayerSettings.Add(ToLayerInfo);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (auto It = Proxies.CreateConstIterator(); It; ++It)
|
|
{
|
|
ALandscapeProxy* Proxy = *It;
|
|
Proxy->Modify();
|
|
FLandscapeEditorLayerSettings* ToEditorLayerSettings = Proxy->EditorLayerSettings.FindByKey(ToLayerInfo);
|
|
if (ToEditorLayerSettings != NULL)
|
|
{
|
|
// If the new layer already exists, simple remove the old layer
|
|
Proxy->EditorLayerSettings.Remove(FromLayerInfo);
|
|
}
|
|
else
|
|
{
|
|
FLandscapeEditorLayerSettings* FromEditorLayerSettings = Proxy->EditorLayerSettings.FindByKey(FromLayerInfo);
|
|
if (FromEditorLayerSettings != NULL)
|
|
{
|
|
// If only the old layer exists (most common case), change it to point to the new layer info
|
|
FromEditorLayerSettings->LayerInfoObj = ToLayerInfo;
|
|
}
|
|
else
|
|
{
|
|
// If neither exists in the EditorLayerSettings cache, add it
|
|
Proxy->EditorLayerSettings.Add(ToLayerInfo);
|
|
}
|
|
}
|
|
}
|
|
|
|
//UpdateLayerInfoMap();
|
|
|
|
GWarn->EndSlowTask();
|
|
}
|
|
}
|
|
|
|
void ALandscapeProxy::EditorApplyScale(const FVector& DeltaScale, const FVector* PivotLocation, bool bAltDown, bool bShiftDown, bool bCtrlDown)
|
|
{
|
|
FVector ModifiedScale = DeltaScale;
|
|
|
|
// Lock X and Y scaling to the same value
|
|
ModifiedScale.X = ModifiedScale.Y = (FMath::Abs(DeltaScale.X) > FMath::Abs(DeltaScale.Y)) ? DeltaScale.X : DeltaScale.Y;
|
|
|
|
// Correct for attempts to scale to 0 on any axis
|
|
FVector CurrentScale = GetRootComponent()->RelativeScale3D;
|
|
if (AActor::bUsePercentageBasedScaling)
|
|
{
|
|
if (ModifiedScale.X == -1)
|
|
{
|
|
ModifiedScale.X = ModifiedScale.Y = -(CurrentScale.X - 1) / CurrentScale.X;
|
|
}
|
|
if (ModifiedScale.Z == -1)
|
|
{
|
|
ModifiedScale.Z = -(CurrentScale.Z - 1) / CurrentScale.Z;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (ModifiedScale.X == -CurrentScale.X)
|
|
{
|
|
CurrentScale.X += 1;
|
|
CurrentScale.Y += 1;
|
|
}
|
|
if (ModifiedScale.Z == -CurrentScale.Z)
|
|
{
|
|
CurrentScale.Z += 1;
|
|
}
|
|
}
|
|
|
|
Super::EditorApplyScale(ModifiedScale, PivotLocation, bAltDown, bShiftDown, bCtrlDown);
|
|
|
|
// We need to regenerate collision objects, they depend on scale value
|
|
for (ULandscapeHeightfieldCollisionComponent* Comp : CollisionComponents)
|
|
{
|
|
if (Comp)
|
|
{
|
|
Comp->RecreateCollision(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ALandscapeProxy::EditorApplyMirror(const FVector& MirrorScale, const FVector& PivotLocation)
|
|
{
|
|
Super::EditorApplyMirror(MirrorScale, PivotLocation);
|
|
|
|
// We need to regenerate collision objects, they depend on scale value
|
|
for (ULandscapeHeightfieldCollisionComponent* Comp : CollisionComponents)
|
|
{
|
|
if (Comp)
|
|
{
|
|
Comp->RecreateCollision(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ALandscapeProxy::PostEditMove(bool bFinished)
|
|
{
|
|
// This point is only reached when Copy and Pasted
|
|
Super::PostEditMove(bFinished);
|
|
|
|
if (bFinished)
|
|
{
|
|
ULandscapeInfo::RecreateLandscapeInfo(GetWorld(), true);
|
|
RecreateComponentsState();
|
|
|
|
if (SplineComponent)
|
|
{
|
|
SplineComponent->CheckSplinesValid();
|
|
}
|
|
}
|
|
}
|
|
|
|
void ALandscapeProxy::PostEditImport()
|
|
{
|
|
Super::PostEditImport();
|
|
if (!bIsProxy && GetWorld()) // For Landscape
|
|
{
|
|
for (TActorIterator<ALandscape> It(GetWorld()); It; ++It)
|
|
{
|
|
ALandscape* Landscape = *It;
|
|
if (Landscape != this && !Landscape->HasAnyFlags(RF_BeginDestroyed) && Landscape->LandscapeGuid == LandscapeGuid)
|
|
{
|
|
// Copy/Paste case, need to generate new GUID
|
|
LandscapeGuid = FGuid::NewGuid();
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int32 ComponentIndex = 0; ComponentIndex < LandscapeComponents.Num(); ++ComponentIndex)
|
|
{
|
|
ULandscapeComponent* Comp = LandscapeComponents[ComponentIndex];
|
|
if (Comp)
|
|
{
|
|
// Update the MIC
|
|
Comp->UpdateMaterialInstances();
|
|
}
|
|
}
|
|
|
|
GEngine->DeferredCommands.AddUnique(TEXT("UpdateLandscapeEditorData"));
|
|
}
|
|
|
|
void ALandscape::PostEditMove(bool bFinished)
|
|
{
|
|
if (bFinished)
|
|
{
|
|
// align all proxies to landscape actor
|
|
GetLandscapeInfo()->FixupProxiesTransform();
|
|
}
|
|
|
|
Super::PostEditMove(bFinished);
|
|
}
|
|
#endif //WITH_EDITOR
|
|
|
|
ULandscapeLayerInfoObject::ULandscapeLayerInfoObject(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
{
|
|
Hardness = 0.5f;
|
|
#if WITH_EDITORONLY_DATA
|
|
bNoWeightBlend = false;
|
|
#endif // WITH_EDITORONLY_DATA
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
void ULandscapeLayerInfoObject::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
|
|
{
|
|
static const FName NAME_Hardness = FName(TEXT("Hardness"));
|
|
static const FName NAME_PhysMaterial = FName(TEXT("PhysMaterial"));
|
|
|
|
Super::PostEditChangeProperty(PropertyChangedEvent);
|
|
|
|
const FName PropertyName = PropertyChangedEvent.Property ? PropertyChangedEvent.Property->GetFName() : NAME_None;
|
|
|
|
if (GIsEditor)
|
|
{
|
|
if (PropertyName == NAME_Hardness)
|
|
{
|
|
Hardness = FMath::Clamp<float>(Hardness, 0.0f, 1.0f);
|
|
}
|
|
else if (PropertyName == NAME_PhysMaterial)
|
|
{
|
|
// Only care current world object
|
|
for (TActorIterator<ALandscapeProxy> It(GWorld); It; ++It)
|
|
{
|
|
ALandscapeProxy* Proxy = *It;
|
|
ULandscapeInfo* Info = Proxy->GetLandscapeInfo(false);
|
|
if (Info)
|
|
{
|
|
for (int32 i = 0; i < Info->Layers.Num(); ++i)
|
|
{
|
|
if (Info->Layers[i].LayerInfoObj == this)
|
|
{
|
|
Proxy->ChangedPhysMaterial();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ULandscapeLayerInfoObject::PostLoad()
|
|
{
|
|
Super::PostLoad();
|
|
if (GIsEditor)
|
|
{
|
|
if (!HasAnyFlags(RF_Standalone))
|
|
{
|
|
SetFlags(RF_Standalone);
|
|
}
|
|
Hardness = FMath::Clamp<float>(Hardness, 0.0f, 1.0f);
|
|
}
|
|
}
|
|
|
|
void ALandscapeProxy::RemoveXYOffsets()
|
|
{
|
|
bool bFoundXYOffset = false;
|
|
|
|
for (int32 i = 0; i < LandscapeComponents.Num(); ++i)
|
|
{
|
|
ULandscapeComponent* Comp = LandscapeComponents[i];
|
|
if (Comp && Comp->XYOffsetmapTexture)
|
|
{
|
|
Comp->XYOffsetmapTexture->SetFlags(RF_Transactional);
|
|
Comp->XYOffsetmapTexture->Modify();
|
|
Comp->XYOffsetmapTexture->MarkPackageDirty();
|
|
Comp->XYOffsetmapTexture->ClearFlags(RF_Standalone);
|
|
Comp->Modify();
|
|
Comp->MarkPackageDirty();
|
|
Comp->XYOffsetmapTexture = NULL;
|
|
Comp->MarkRenderStateDirty();
|
|
bFoundXYOffset = true;
|
|
}
|
|
}
|
|
|
|
if (bFoundXYOffset)
|
|
{
|
|
RecreateCollisionComponents();
|
|
}
|
|
}
|
|
|
|
void ALandscapeProxy::RecreateCollisionComponents()
|
|
{
|
|
// We can assume these are all junk; they recreate as needed
|
|
FlushGrassComponents();
|
|
|
|
// Clear old CollisionComponent containers
|
|
CollisionComponents.Empty();
|
|
|
|
// Destroy any owned collision components
|
|
TInlineComponentArray<ULandscapeHeightfieldCollisionComponent*> CollisionComps;
|
|
GetComponents(CollisionComps);
|
|
for (ULandscapeHeightfieldCollisionComponent* Component : CollisionComps)
|
|
{
|
|
Component->DestroyComponent();
|
|
}
|
|
|
|
TArray<USceneComponent*> AttachedCollisionComponents = RootComponent->AttachChildren.FilterByPredicate(
|
|
[](USceneComponent* Component)
|
|
{
|
|
return Cast<ULandscapeHeightfieldCollisionComponent>(Component);
|
|
});
|
|
|
|
// Destroy any attached but un-owned collision components
|
|
for (USceneComponent* Component : AttachedCollisionComponents)
|
|
{
|
|
Component->DestroyComponent();
|
|
}
|
|
|
|
// Recreate collision
|
|
CollisionMipLevel = FMath::Clamp<int32>(CollisionMipLevel, 0, FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1);
|
|
for (int32 i = 0; i < LandscapeComponents.Num(); ++i)
|
|
{
|
|
ULandscapeComponent* Comp = LandscapeComponents[i];
|
|
if (Comp)
|
|
{
|
|
Comp->CollisionMipLevel = CollisionMipLevel;
|
|
TArray<uint8> CollisionMipData;
|
|
Comp->HeightmapTexture->Source.GetMipData(CollisionMipData, CollisionMipLevel);
|
|
Comp->UpdateCollisionHeightData((FColor*)CollisionMipData.GetData(), 0, 0, MAX_int32, MAX_int32, true, NULL, true); // Rebuild for new CollisionMipLevel
|
|
}
|
|
}
|
|
}
|
|
|
|
void ULandscapeInfo::RecreateCollisionComponents()
|
|
{
|
|
if (LandscapeActor.IsValid())
|
|
{
|
|
LandscapeActor->RecreateCollisionComponents();
|
|
}
|
|
|
|
for (auto It = Proxies.CreateConstIterator(); It; ++It)
|
|
{
|
|
ALandscapeProxy* Proxy = (*It);
|
|
Proxy->RecreateCollisionComponents();
|
|
}
|
|
}
|
|
|
|
void ULandscapeInfo::RemoveXYOffsets()
|
|
{
|
|
if (LandscapeActor.IsValid())
|
|
{
|
|
LandscapeActor->RemoveXYOffsets();
|
|
}
|
|
|
|
for (auto It = Proxies.CreateConstIterator(); It; ++It)
|
|
{
|
|
ALandscapeProxy* Proxy = (*It);
|
|
Proxy->RemoveXYOffsets();
|
|
}
|
|
}
|
|
|
|
void ULandscapeInfo::PostponeTextureBaking()
|
|
{
|
|
const int32 PostponeValue = 60; //frames
|
|
|
|
ALandscape* Landscape = LandscapeActor.Get();
|
|
if (Landscape)
|
|
{
|
|
Landscape->UpdateBakedTexturesCountdown = PostponeValue;
|
|
}
|
|
|
|
for (ALandscapeProxy* Proxy : Proxies)
|
|
{
|
|
Proxy->UpdateBakedTexturesCountdown = PostponeValue;
|
|
}
|
|
}
|
|
|
|
namespace
|
|
{
|
|
inline float AdjustStaticLightingResolution(float StaticLightingResolution, int32 NumSubsections, int32 SubsectionSizeQuads, int32 ComponentSizeQuads)
|
|
{
|
|
// Change Lighting resolution to proper one...
|
|
if (StaticLightingResolution > 1.0f)
|
|
{
|
|
StaticLightingResolution = (int32)StaticLightingResolution;
|
|
}
|
|
else if (StaticLightingResolution < 1.0f)
|
|
{
|
|
// Restrict to 1/16
|
|
if (StaticLightingResolution < 0.0625)
|
|
{
|
|
StaticLightingResolution = 0.0625;
|
|
}
|
|
|
|
// Adjust to 1/2^n
|
|
int32 i = 2;
|
|
int32 LightmapSize = (NumSubsections * (SubsectionSizeQuads + 1)) >> 1;
|
|
while (StaticLightingResolution < (1.0f / i) && LightmapSize > 4)
|
|
{
|
|
i <<= 1;
|
|
LightmapSize >>= 1;
|
|
}
|
|
StaticLightingResolution = 1.0f / i;
|
|
|
|
int32 PixelPaddingX = GPixelFormats[PF_DXT1].BlockSizeX;
|
|
|
|
int32 DestSize = (int32)((2 * PixelPaddingX + ComponentSizeQuads + 1) * StaticLightingResolution);
|
|
StaticLightingResolution = (float)DestSize / (2 * PixelPaddingX + ComponentSizeQuads + 1);
|
|
}
|
|
|
|
return StaticLightingResolution;
|
|
}
|
|
};
|
|
|
|
void ALandscapeProxy::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
|
|
{
|
|
Super::PostEditChangeProperty(PropertyChangedEvent);
|
|
const FName PropertyName = PropertyChangedEvent.MemberProperty ? PropertyChangedEvent.MemberProperty->GetFName() : NAME_None;
|
|
if (bIsProxy)
|
|
{
|
|
if (PropertyName == FName(TEXT("LandscapeActor")))
|
|
{
|
|
if (LandscapeActor && IsValidLandscapeActor(LandscapeActor.Get()))
|
|
{
|
|
LandscapeGuid = LandscapeActor->LandscapeGuid;
|
|
// defer LandscapeInfo setup
|
|
if (GIsEditor && GetWorld() && !GetWorld()->IsPlayInEditor())
|
|
{
|
|
GEngine->DeferredCommands.AddUnique(TEXT("UpdateLandscapeEditorData"));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LandscapeActor = nullptr;
|
|
}
|
|
}
|
|
else if (PropertyName == FName(TEXT("LandscapeMaterial")) || PropertyName == FName(TEXT("LandscapeHoleMaterial")))
|
|
{
|
|
FMaterialUpdateContext MaterialUpdateContext;
|
|
GetLandscapeInfo()->UpdateLayerInfoMap(/*this*/);
|
|
|
|
// Clear the parents out of combination material instances
|
|
for (TMap<FString, UMaterialInstanceConstant*>::TIterator It(MaterialInstanceConstantMap); It; ++It)
|
|
{
|
|
It.Value()->SetParentEditorOnly(NULL);
|
|
MaterialUpdateContext.AddMaterial(It.Value()->GetMaterial());
|
|
}
|
|
|
|
// Remove our references to any material instances
|
|
MaterialInstanceConstantMap.Empty();
|
|
|
|
for (int32 ComponentIndex = 0; ComponentIndex < LandscapeComponents.Num(); ComponentIndex++)
|
|
{
|
|
ULandscapeComponent* Comp = LandscapeComponents[ComponentIndex];
|
|
if (Comp)
|
|
{
|
|
// Update the MIC
|
|
Comp->UpdateMaterialInstances();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (GIsEditor && PropertyName == FName(TEXT("StreamingDistanceMultiplier")))
|
|
{
|
|
// Recalculate in a few seconds.
|
|
GetWorld()->TriggerStreamingDataRebuild();
|
|
}
|
|
else if (GIsEditor && PropertyName == FName(TEXT("DefaultPhysMaterial")))
|
|
{
|
|
ChangedPhysMaterial();
|
|
}
|
|
else if (GIsEditor && (PropertyName == FName(TEXT("CollisionMipLevel"))))
|
|
{
|
|
RecreateCollisionComponents();
|
|
}
|
|
else
|
|
if(PropertyName == FName(TEXT("bCastStaticShadow"))
|
|
|| PropertyName == FName(TEXT("bCastShadowAsTwoSided"))
|
|
|| PropertyName == FName(TEXT("bCastFarShadow"))
|
|
)
|
|
{
|
|
// Replicate shared properties to all components.
|
|
for (int32 ComponentIndex = 0; ComponentIndex < LandscapeComponents.Num(); ComponentIndex++)
|
|
{
|
|
ULandscapeComponent* Comp = LandscapeComponents[ComponentIndex];
|
|
if (Comp)
|
|
{
|
|
Comp->bCastStaticShadow = bCastStaticShadow;
|
|
Comp->bCastShadowAsTwoSided = bCastShadowAsTwoSided;
|
|
Comp->bCastFarShadow = bCastFarShadow;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ALandscapeProxy::PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent)
|
|
{
|
|
const FName MemberPropertyName = PropertyChangedEvent.PropertyChain.GetActiveMemberNode()->GetValue()->GetFName();
|
|
const FName PropertyName = PropertyChangedEvent.PropertyChain.GetActiveNode()->GetValue()->GetFName();
|
|
|
|
if (MemberPropertyName == FName(TEXT("RelativeScale3D")))
|
|
{
|
|
// RelativeScale3D isn't even a property of ALandscapeProxy, it's a property of the root component
|
|
if (RootComponent)
|
|
{
|
|
FVector ModifiedScale = RootComponent->RelativeScale3D;
|
|
|
|
// Lock X and Y scaling to the same value
|
|
if (PropertyName == FName("Y"))
|
|
{
|
|
ModifiedScale.X = FMath::Abs(RootComponent->RelativeScale3D.Y)*FMath::Sign(ModifiedScale.X);
|
|
}
|
|
else
|
|
{
|
|
// There's no "if name == X" here so that if we can't tell which has changed out of X and Y, we just use X
|
|
ModifiedScale.Y = FMath::Abs(RootComponent->RelativeScale3D.X)*FMath::Sign(ModifiedScale.Y);
|
|
}
|
|
|
|
ULandscapeInfo* Info = GetLandscapeInfo(false);
|
|
|
|
// Correct for attempts to scale to 0 on any axis
|
|
if (ModifiedScale.X == 0)
|
|
{
|
|
if (Info && Info->DrawScale.X < 0)
|
|
{
|
|
ModifiedScale.Y = ModifiedScale.X = -1;
|
|
}
|
|
else
|
|
{
|
|
ModifiedScale.Y = ModifiedScale.X = 1;
|
|
}
|
|
}
|
|
if (ModifiedScale.Z == 0)
|
|
{
|
|
if (Info && Info->DrawScale.Z < 0)
|
|
{
|
|
ModifiedScale.Z = -1;
|
|
}
|
|
else
|
|
{
|
|
ModifiedScale.Z = 1;
|
|
}
|
|
}
|
|
|
|
RootComponent->SetRelativeScale3D(ModifiedScale);
|
|
|
|
// Update ULandscapeInfo cached DrawScale
|
|
if (Info)
|
|
{
|
|
Info->DrawScale = ModifiedScale;
|
|
}
|
|
|
|
// We need to regenerate collision objects, they depend on scale value
|
|
for (int32 ComponentIndex = 0; ComponentIndex < CollisionComponents.Num(); ComponentIndex++)
|
|
{
|
|
ULandscapeHeightfieldCollisionComponent* Comp = CollisionComponents[ComponentIndex];
|
|
if (Comp)
|
|
{
|
|
Comp->RecreateCollision(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Must do this *after* correcting the scale or reattaching the landscape components will crash!
|
|
Super::PostEditChangeChainProperty(PropertyChangedEvent);
|
|
}
|
|
|
|
void ALandscape::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
|
|
{
|
|
Super::PostEditChangeProperty(PropertyChangedEvent);
|
|
|
|
const FName PropertyName = PropertyChangedEvent.Property ? PropertyChangedEvent.Property->GetFName() : NAME_None;
|
|
|
|
bool ChangedMaterial = false;
|
|
bool bNeedsRecalcBoundingBox = false;
|
|
bool bChangedLighting = false;
|
|
bool bChangedNavRelevance = false;
|
|
bool bPropagateToProxies = false;
|
|
|
|
ULandscapeInfo* Info = GetLandscapeInfo();
|
|
|
|
if (PropertyName == FName(TEXT("LandscapeMaterial")) || PropertyName == FName(TEXT("LandscapeHoleMaterial")))
|
|
{
|
|
FMaterialUpdateContext MaterialUpdateContext;
|
|
GetLandscapeInfo()->UpdateLayerInfoMap(/*this*/);
|
|
|
|
ChangedMaterial = true;
|
|
|
|
// Clear the parents out of combination material instances
|
|
for (TMap<FString, UMaterialInstanceConstant*>::TIterator It(MaterialInstanceConstantMap); It; ++It)
|
|
{
|
|
It.Value()->SetParentEditorOnly(NULL);
|
|
MaterialUpdateContext.AddMaterial(It.Value()->GetMaterial());
|
|
}
|
|
|
|
// Remove our references to any material instances
|
|
MaterialInstanceConstantMap.Empty();
|
|
}
|
|
else if (PropertyName == FName(TEXT("RelativeScale3D")) ||
|
|
PropertyName == FName(TEXT("RelativeLocation")) ||
|
|
PropertyName == FName(TEXT("RelativeRotation")))
|
|
{
|
|
// update transformations for all linked proxies
|
|
Info->FixupProxiesTransform();
|
|
bNeedsRecalcBoundingBox = true;
|
|
}
|
|
else if (GIsEditor && PropertyName == FName(TEXT("MaxLODLevel")))
|
|
{
|
|
MaxLODLevel = FMath::Clamp<int32>(MaxLODLevel, -1, FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1);
|
|
bPropagateToProxies = true;
|
|
}
|
|
else if (PropertyName == FName(TEXT("LODDistanceFactor")))
|
|
{
|
|
LODDistanceFactor = FMath::Clamp<float>(LODDistanceFactor, 0.1f, MAX_LANDSCAPE_LOD_DISTANCE_FACTOR); // limit because LOD transition became too popping...
|
|
bPropagateToProxies = true;
|
|
}
|
|
else if (PropertyName == FName(TEXT("CollisionMipLevel")))
|
|
{
|
|
CollisionMipLevel = FMath::Clamp<int32>(CollisionMipLevel, 0, FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1);
|
|
bPropagateToProxies = true;
|
|
}
|
|
else if (PropertyName == FName(TEXT("LODFalloff")))
|
|
{
|
|
bPropagateToProxies = true;
|
|
}
|
|
else if (GIsEditor && PropertyName == FName(TEXT("StaticLightingResolution")))
|
|
{
|
|
StaticLightingResolution = ::AdjustStaticLightingResolution(StaticLightingResolution, NumSubsections, SubsectionSizeQuads, ComponentSizeQuads);
|
|
bChangedLighting = true;
|
|
}
|
|
else if (GIsEditor && PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, StaticLightingLOD))
|
|
{
|
|
StaticLightingLOD = FMath::Clamp<int32>(StaticLightingLOD, 0, FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1);
|
|
bChangedLighting = true;
|
|
}
|
|
else if (GIsEditor && PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, ExportLOD))
|
|
{
|
|
ExportLOD = FMath::Clamp<int32>(ExportLOD, 0, FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1);
|
|
}
|
|
else if (GIsEditor && PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, bUsedForNavigation))
|
|
{
|
|
bChangedNavRelevance = true;
|
|
}
|
|
|
|
bPropagateToProxies = bPropagateToProxies || bNeedsRecalcBoundingBox || bChangedLighting;
|
|
|
|
if (Info)
|
|
{
|
|
if (bPropagateToProxies)
|
|
{
|
|
// Propagate Event to Proxies...
|
|
for (TSet<ALandscapeProxy*>::TIterator It(Info->Proxies); It; ++It)
|
|
{
|
|
(*It)->GetSharedProperties(this);
|
|
(*It)->PostEditChangeProperty(PropertyChangedEvent);
|
|
}
|
|
}
|
|
|
|
// Update normals if DrawScale3D is changed
|
|
if (PropertyName == FName(TEXT("RelativeScale3D")))
|
|
{
|
|
FLandscapeEditDataInterface LandscapeEdit(Info);
|
|
LandscapeEdit.RecalculateNormals();
|
|
}
|
|
|
|
TArray<ULandscapeComponent*> AllComponents;
|
|
Info->XYtoComponentMap.GenerateValueArray(AllComponents);
|
|
|
|
// We cannot iterate the XYtoComponentMap directly because reregistering components modifies the array.
|
|
for (auto It = AllComponents.CreateIterator(); It; ++It)
|
|
{
|
|
ULandscapeComponent* Comp = *It;
|
|
if (Comp)
|
|
{
|
|
if (bNeedsRecalcBoundingBox)
|
|
{
|
|
Comp->UpdateCachedBounds();
|
|
Comp->UpdateBounds();
|
|
}
|
|
|
|
if (ChangedMaterial)
|
|
{
|
|
// Update the MIC
|
|
Comp->UpdateMaterialInstances();
|
|
}
|
|
|
|
if (bChangedLighting)
|
|
{
|
|
Comp->InvalidateLightingCache();
|
|
}
|
|
|
|
if (bChangedNavRelevance)
|
|
{
|
|
Comp->UpdateNavigationRelevance();
|
|
}
|
|
|
|
// Reattach all components
|
|
FComponentReregisterContext ReregisterContext(Comp);
|
|
}
|
|
}
|
|
|
|
// Need to update Gizmo scene proxy
|
|
if (bNeedsRecalcBoundingBox && GetWorld())
|
|
{
|
|
for (TActorIterator<ALandscapeGizmoActiveActor> It(GetWorld()); It; ++It)
|
|
{
|
|
It->ReregisterAllComponents();
|
|
}
|
|
}
|
|
|
|
if (ChangedMaterial)
|
|
{
|
|
if (GIsEditor && GetWorld() && !GetWorld()->IsPlayInEditor())
|
|
{
|
|
GEngine->DeferredCommands.AddUnique(TEXT("UpdateLandscapeMIC"));
|
|
}
|
|
|
|
// Update all the proxies...
|
|
for (TSet<ALandscapeProxy*>::TIterator It(Info->Proxies); It; ++It)
|
|
{
|
|
(*It)->MarkComponentsRenderStateDirty();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ALandscapeProxy::ChangedPhysMaterial()
|
|
{
|
|
ULandscapeInfo* LandscapeInfo = GetLandscapeInfo();
|
|
if (!LandscapeInfo) return;
|
|
for (auto It = LandscapeInfo->XYtoComponentMap.CreateIterator(); It; ++It)
|
|
{
|
|
ULandscapeComponent* Comp = It.Value();
|
|
if (Comp)
|
|
{
|
|
ULandscapeHeightfieldCollisionComponent* CollisionComponent = Comp->CollisionComponent.Get();
|
|
if (CollisionComponent)
|
|
{
|
|
Comp->UpdateCollisionLayerData();
|
|
// Physical materials cooked into collision object, so we need to recreate it
|
|
CollisionComponent->RecreateCollision(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ULandscapeComponent::SetLOD(bool bForcedLODChanged, int32 InLODValue)
|
|
{
|
|
if (bForcedLODChanged)
|
|
{
|
|
ForcedLOD = InLODValue;
|
|
if (ForcedLOD >= 0)
|
|
{
|
|
ForcedLOD = FMath::Clamp<int32>(ForcedLOD, 0, FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1);
|
|
}
|
|
else
|
|
{
|
|
ForcedLOD = -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int32 MaxLOD = FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1;
|
|
LODBias = FMath::Clamp<int32>(InLODValue, -MaxLOD, MaxLOD);
|
|
}
|
|
|
|
InvalidateLightingCache();
|
|
|
|
// Update neighbor components
|
|
ULandscapeInfo* Info = GetLandscapeInfo(false);
|
|
if (Info)
|
|
{
|
|
FIntPoint ComponentBase = GetSectionBase() / ComponentSizeQuads;
|
|
FIntPoint LandscapeKey[8] =
|
|
{
|
|
ComponentBase + FIntPoint(-1, -1),
|
|
ComponentBase + FIntPoint(+0, -1),
|
|
ComponentBase + FIntPoint(+1, -1),
|
|
ComponentBase + FIntPoint(-1, +0),
|
|
ComponentBase + FIntPoint(+1, +0),
|
|
ComponentBase + FIntPoint(-1, +1),
|
|
ComponentBase + FIntPoint(+0, +1),
|
|
ComponentBase + FIntPoint(+1, +1)
|
|
};
|
|
|
|
for (int32 Idx = 0; Idx < 8; ++Idx)
|
|
{
|
|
ULandscapeComponent* Comp = Info->XYtoComponentMap.FindRef(LandscapeKey[Idx]);
|
|
if (Comp)
|
|
{
|
|
Comp->Modify();
|
|
Comp->InvalidateLightingCache();
|
|
FComponentReregisterContext ReregisterContext(Comp);
|
|
}
|
|
}
|
|
}
|
|
FComponentReregisterContext ReregisterContext(this);
|
|
}
|
|
|
|
void ULandscapeComponent::PreEditChange(UProperty* PropertyThatWillChange)
|
|
{
|
|
Super::PreEditChange(PropertyThatWillChange);
|
|
if (GIsEditor && PropertyThatWillChange && (PropertyThatWillChange->GetFName() == GET_MEMBER_NAME_CHECKED(ULandscapeComponent, ForcedLOD) || PropertyThatWillChange->GetFName() == GET_MEMBER_NAME_CHECKED(ULandscapeComponent, LODBias)))
|
|
{
|
|
// PreEdit unregister component and re-register after PostEdit so we will lose XYtoComponentMap for this component
|
|
ULandscapeInfo* Info = GetLandscapeInfo(false);
|
|
if (Info)
|
|
{
|
|
FIntPoint ComponentKey = GetSectionBase() / ComponentSizeQuads;
|
|
auto RegisteredComponent = Info->XYtoComponentMap.FindRef(ComponentKey);
|
|
|
|
if (RegisteredComponent == NULL)
|
|
{
|
|
Info->XYtoComponentMap.Add(ComponentKey, this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ULandscapeComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
|
|
{
|
|
Super::PostEditChangeProperty(PropertyChangedEvent);
|
|
|
|
const FName PropertyName = PropertyChangedEvent.Property ? PropertyChangedEvent.Property->GetFName() : NAME_None;
|
|
if (PropertyName == FName(TEXT("OverrideMaterial")))
|
|
{
|
|
UpdateMaterialInstances();
|
|
// Reregister all components
|
|
FComponentReregisterContext ReregisterContext(this);
|
|
}
|
|
else if (GIsEditor && (PropertyName == FName(TEXT("ForcedLOD")) || PropertyName == FName(TEXT("LODBias"))))
|
|
{
|
|
bool bForcedLODChanged = PropertyName == FName(TEXT("ForcedLOD"));
|
|
SetLOD(bForcedLODChanged, bForcedLODChanged ? ForcedLOD : LODBias);
|
|
}
|
|
else if (GIsEditor && PropertyName == FName(TEXT("StaticLightingResolution")))
|
|
{
|
|
if (StaticLightingResolution > 0.0f)
|
|
{
|
|
StaticLightingResolution = ::AdjustStaticLightingResolution(StaticLightingResolution, NumSubsections, SubsectionSizeQuads, ComponentSizeQuads);
|
|
}
|
|
else
|
|
{
|
|
StaticLightingResolution = 0;
|
|
}
|
|
InvalidateLightingCache();
|
|
}
|
|
else if (GIsEditor && PropertyName == FName(TEXT("LightingLODBias")))
|
|
{
|
|
int32 MaxLOD = FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1;
|
|
LightingLODBias = FMath::Clamp<int32>(LightingLODBias, -1, MaxLOD);
|
|
InvalidateLightingCache();
|
|
}
|
|
else if (GIsEditor && (PropertyName == FName(TEXT("CollisionMipLevel"))))
|
|
{
|
|
CollisionMipLevel = FMath::Clamp<int32>(CollisionMipLevel, 0, FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1);
|
|
TArray<uint8> CollisionMipData;
|
|
HeightmapTexture->Source.GetMipData(CollisionMipData, CollisionMipLevel);
|
|
UpdateCollisionHeightData((FColor*)CollisionMipData.GetData(), 0, 0, MAX_int32, MAX_int32, true, NULL, true); // Rebuild for new CollisionMipLevel
|
|
}
|
|
}
|
|
|
|
TSet<class ULandscapeComponent*> ULandscapeInfo::GetSelectedComponents() const
|
|
{
|
|
return SelectedComponents;
|
|
}
|
|
|
|
TSet<class ULandscapeComponent*> ULandscapeInfo::GetSelectedRegionComponents() const
|
|
{
|
|
return SelectedRegionComponents;
|
|
}
|
|
|
|
void ULandscapeInfo::UpdateSelectedComponents(TSet<ULandscapeComponent*>& NewComponents, bool bIsComponentwise /*=true*/)
|
|
{
|
|
int32 InSelectType = bIsComponentwise ? FLandscapeEditToolRenderData::ST_COMPONENT : FLandscapeEditToolRenderData::ST_REGION;
|
|
|
|
if (bIsComponentwise)
|
|
{
|
|
for (TSet<ULandscapeComponent*>::TIterator It(NewComponents); It; ++It)
|
|
{
|
|
ULandscapeComponent* Comp = *It;
|
|
if (Comp->EditToolRenderData != NULL && (Comp->EditToolRenderData->SelectedType & InSelectType) == 0)
|
|
{
|
|
Comp->Modify();
|
|
int32 SelectedType = Comp->EditToolRenderData->SelectedType;
|
|
SelectedType |= InSelectType;
|
|
Comp->EditToolRenderData->UpdateSelectionMaterial(SelectedType);
|
|
}
|
|
}
|
|
|
|
// Remove the material from any old components that are no longer in the region
|
|
TSet<ULandscapeComponent*> RemovedComponents = SelectedComponents.Difference(NewComponents);
|
|
for (TSet<ULandscapeComponent*>::TIterator It(RemovedComponents); It; ++It)
|
|
{
|
|
ULandscapeComponent* Comp = *It;
|
|
if (Comp->EditToolRenderData != NULL)
|
|
{
|
|
Comp->Modify();
|
|
int32 SelectedType = Comp->EditToolRenderData->SelectedType;
|
|
SelectedType &= ~InSelectType;
|
|
Comp->EditToolRenderData->UpdateSelectionMaterial(SelectedType);
|
|
}
|
|
}
|
|
SelectedComponents = NewComponents;
|
|
}
|
|
else
|
|
{
|
|
// Only add components...
|
|
if (NewComponents.Num())
|
|
{
|
|
for (TSet<ULandscapeComponent*>::TIterator It(NewComponents); It; ++It)
|
|
{
|
|
ULandscapeComponent* Comp = *It;
|
|
if (Comp->EditToolRenderData != NULL && (Comp->EditToolRenderData->SelectedType & InSelectType) == 0)
|
|
{
|
|
Comp->Modify();
|
|
int32 SelectedType = Comp->EditToolRenderData->SelectedType;
|
|
SelectedType |= InSelectType;
|
|
Comp->EditToolRenderData->UpdateSelectionMaterial(SelectedType);
|
|
}
|
|
|
|
SelectedRegionComponents.Add(*It);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Remove the material from any old components that are no longer in the region
|
|
for (TSet<ULandscapeComponent*>::TIterator It(SelectedRegionComponents); It; ++It)
|
|
{
|
|
ULandscapeComponent* Comp = *It;
|
|
if (Comp->EditToolRenderData != NULL)
|
|
{
|
|
Comp->Modify();
|
|
int32 SelectedType = Comp->EditToolRenderData->SelectedType;
|
|
SelectedType &= ~InSelectType;
|
|
Comp->EditToolRenderData->UpdateSelectionMaterial(SelectedType);
|
|
}
|
|
}
|
|
SelectedRegionComponents = NewComponents;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ULandscapeInfo::SortSelectedComponents()
|
|
{
|
|
struct FCompareULandscapeComponentBySectionBase
|
|
{
|
|
FORCEINLINE bool operator()(const ULandscapeComponent& A, const ULandscapeComponent& B) const
|
|
{
|
|
return (A.GetSectionBase().X == B.GetSectionBase().X) ? (A.GetSectionBase().Y < B.GetSectionBase().Y) : (A.GetSectionBase().X < B.GetSectionBase().X);
|
|
}
|
|
};
|
|
SelectedComponents.Sort(FCompareULandscapeComponentBySectionBase());
|
|
}
|
|
|
|
void ULandscapeInfo::ClearSelectedRegion(bool bIsComponentwise /*= true*/)
|
|
{
|
|
TSet<ULandscapeComponent*> NewComponents;
|
|
UpdateSelectedComponents(NewComponents, bIsComponentwise);
|
|
if (!bIsComponentwise)
|
|
{
|
|
SelectedRegion.Empty();
|
|
}
|
|
}
|
|
|
|
struct FLandscapeDataInterface* ULandscapeInfo::GetDataInterface()
|
|
{
|
|
if (DataInterface == NULL)
|
|
{
|
|
DataInterface = new FLandscapeDataInterface();
|
|
}
|
|
|
|
return DataInterface;
|
|
}
|
|
|
|
void ULandscapeComponent::ReallocateWeightmaps(FLandscapeEditDataInterface* DataInterface)
|
|
{
|
|
ALandscapeProxy* Proxy = GetLandscapeProxy();
|
|
|
|
int32 NeededNewChannels = 0;
|
|
for (int32 LayerIdx = 0; LayerIdx < WeightmapLayerAllocations.Num(); LayerIdx++)
|
|
{
|
|
if (WeightmapLayerAllocations[LayerIdx].WeightmapTextureIndex == 255)
|
|
{
|
|
NeededNewChannels++;
|
|
}
|
|
}
|
|
|
|
// All channels allocated!
|
|
if (NeededNewChannels == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Modify();
|
|
//Landscape->Modify();
|
|
Proxy->Modify();
|
|
|
|
// UE_LOG(LogLandscape, Log, TEXT("----------------------"));
|
|
// UE_LOG(LogLandscape, Log, TEXT("Component %s needs %d layers (%d new)"), *GetName(), WeightmapLayerAllocations.Num(), NeededNewChannels);
|
|
|
|
// See if our existing textures have sufficient space
|
|
int32 ExistingTexAvailableChannels = 0;
|
|
for (int32 TexIdx = 0; TexIdx < WeightmapTextures.Num(); TexIdx++)
|
|
{
|
|
FLandscapeWeightmapUsage* Usage = Proxy->WeightmapUsageMap.Find(WeightmapTextures[TexIdx]);
|
|
check(Usage);
|
|
|
|
ExistingTexAvailableChannels += Usage->FreeChannelCount();
|
|
|
|
if (ExistingTexAvailableChannels >= NeededNewChannels)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ExistingTexAvailableChannels >= NeededNewChannels)
|
|
{
|
|
// UE_LOG(LogLandscape, Log, TEXT("Existing texture has available channels"));
|
|
|
|
// Allocate using our existing textures' spare channels.
|
|
for (int32 TexIdx = 0; TexIdx < WeightmapTextures.Num(); TexIdx++)
|
|
{
|
|
FLandscapeWeightmapUsage* Usage = Proxy->WeightmapUsageMap.Find(WeightmapTextures[TexIdx]);
|
|
|
|
for (int32 ChanIdx = 0; ChanIdx < 4; ChanIdx++)
|
|
{
|
|
if (Usage->ChannelUsage[ChanIdx] == NULL)
|
|
{
|
|
for (int32 LayerIdx = 0; LayerIdx < WeightmapLayerAllocations.Num(); LayerIdx++)
|
|
{
|
|
FWeightmapLayerAllocationInfo& AllocInfo = WeightmapLayerAllocations[LayerIdx];
|
|
if (AllocInfo.WeightmapTextureIndex == 255)
|
|
{
|
|
// Zero out the data for this texture channel
|
|
if (DataInterface)
|
|
{
|
|
DataInterface->ZeroTextureChannel(WeightmapTextures[TexIdx], ChanIdx);
|
|
}
|
|
|
|
AllocInfo.WeightmapTextureIndex = TexIdx;
|
|
AllocInfo.WeightmapTextureChannel = ChanIdx;
|
|
Usage->ChannelUsage[ChanIdx] = this;
|
|
NeededNewChannels--;
|
|
|
|
if (NeededNewChannels == 0)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// we should never get here.
|
|
check(false);
|
|
}
|
|
|
|
// UE_LOG(LogLandscape, Log, TEXT("Reallocating."));
|
|
|
|
// We are totally reallocating the weightmap
|
|
int32 TotalNeededChannels = WeightmapLayerAllocations.Num();
|
|
int32 CurrentLayer = 0;
|
|
TArray<UTexture2D*> NewWeightmapTextures;
|
|
while (TotalNeededChannels > 0)
|
|
{
|
|
// UE_LOG(LogLandscape, Log, TEXT("Still need %d channels"), TotalNeededChannels);
|
|
|
|
UTexture2D* CurrentWeightmapTexture = NULL;
|
|
FLandscapeWeightmapUsage* CurrentWeightmapUsage = NULL;
|
|
|
|
if (TotalNeededChannels < 4)
|
|
{
|
|
// UE_LOG(LogLandscape, Log, TEXT("Looking for nearest"));
|
|
|
|
// see if we can find a suitable existing weightmap texture with sufficient channels
|
|
int32 BestDistanceSquared = MAX_int32;
|
|
for (TMap<UTexture2D*, struct FLandscapeWeightmapUsage>::TIterator It(Proxy->WeightmapUsageMap); It; ++It)
|
|
{
|
|
FLandscapeWeightmapUsage* TryWeightmapUsage = &It.Value();
|
|
if (TryWeightmapUsage->FreeChannelCount() >= TotalNeededChannels)
|
|
{
|
|
// See if this candidate is closer than any others we've found
|
|
for (int32 ChanIdx = 0; ChanIdx < 4; ChanIdx++)
|
|
{
|
|
if (TryWeightmapUsage->ChannelUsage[ChanIdx] != NULL)
|
|
{
|
|
int32 TryDistanceSquared = (TryWeightmapUsage->ChannelUsage[ChanIdx]->GetSectionBase() - GetSectionBase()).SizeSquared();
|
|
if (TryDistanceSquared < BestDistanceSquared)
|
|
{
|
|
CurrentWeightmapTexture = It.Key();
|
|
CurrentWeightmapUsage = TryWeightmapUsage;
|
|
BestDistanceSquared = TryDistanceSquared;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool NeedsUpdateResource = false;
|
|
// No suitable weightmap texture
|
|
if (CurrentWeightmapTexture == NULL)
|
|
{
|
|
MarkPackageDirty();
|
|
|
|
// Weightmap is sized the same as the component
|
|
int32 WeightmapSize = (SubsectionSizeQuads + 1) * NumSubsections;
|
|
|
|
// We need a new weightmap texture
|
|
CurrentWeightmapTexture = GetLandscapeProxy()->CreateLandscapeTexture(WeightmapSize, WeightmapSize, TEXTUREGROUP_Terrain_Weightmap, TSF_BGRA8);
|
|
// Alloc dummy mips
|
|
CreateEmptyTextureMips(CurrentWeightmapTexture);
|
|
CurrentWeightmapTexture->PostEditChange();
|
|
|
|
// Store it in the usage map
|
|
CurrentWeightmapUsage = &Proxy->WeightmapUsageMap.Add(CurrentWeightmapTexture, FLandscapeWeightmapUsage());
|
|
|
|
// UE_LOG(LogLandscape, Log, TEXT("Making a new texture %s"), *CurrentWeightmapTexture->GetName());
|
|
}
|
|
|
|
NewWeightmapTextures.Add(CurrentWeightmapTexture);
|
|
|
|
for (int32 ChanIdx = 0; ChanIdx < 4 && TotalNeededChannels > 0; ChanIdx++)
|
|
{
|
|
// UE_LOG(LogLandscape, Log, TEXT("Finding allocation for layer %d"), CurrentLayer);
|
|
|
|
if (CurrentWeightmapUsage->ChannelUsage[ChanIdx] == NULL)
|
|
{
|
|
// Use this allocation
|
|
FWeightmapLayerAllocationInfo& AllocInfo = WeightmapLayerAllocations[CurrentLayer];
|
|
|
|
if (AllocInfo.WeightmapTextureIndex == 255)
|
|
{
|
|
// New layer - zero out the data for this texture channel
|
|
if (DataInterface)
|
|
{
|
|
DataInterface->ZeroTextureChannel(CurrentWeightmapTexture, ChanIdx);
|
|
// UE_LOG(LogLandscape, Log, TEXT("Zeroing out channel %s.%d"), *CurrentWeightmapTexture->GetName(), ChanIdx);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UTexture2D* OldWeightmapTexture = WeightmapTextures[AllocInfo.WeightmapTextureIndex];
|
|
|
|
// Copy the data
|
|
if (ensure(DataInterface != NULL)) // it's not safe to skip the copy
|
|
{
|
|
DataInterface->CopyTextureChannel(CurrentWeightmapTexture, ChanIdx, OldWeightmapTexture, AllocInfo.WeightmapTextureChannel);
|
|
DataInterface->ZeroTextureChannel(OldWeightmapTexture, AllocInfo.WeightmapTextureChannel);
|
|
// UE_LOG(LogLandscape, Log, TEXT("Copying old channel (%s).%d to new channel (%s).%d"), *OldWeightmapTexture->GetName(), AllocInfo.WeightmapTextureChannel, *CurrentWeightmapTexture->GetName(), ChanIdx);
|
|
}
|
|
|
|
// Remove the old allocation
|
|
FLandscapeWeightmapUsage* OldWeightmapUsage = Proxy->WeightmapUsageMap.Find(OldWeightmapTexture);
|
|
OldWeightmapUsage->ChannelUsage[AllocInfo.WeightmapTextureChannel] = NULL;
|
|
}
|
|
|
|
// Assign the new allocation
|
|
CurrentWeightmapUsage->ChannelUsage[ChanIdx] = this;
|
|
AllocInfo.WeightmapTextureIndex = NewWeightmapTextures.Num() - 1;
|
|
AllocInfo.WeightmapTextureChannel = ChanIdx;
|
|
CurrentLayer++;
|
|
TotalNeededChannels--;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Replace the weightmap textures
|
|
WeightmapTextures = MoveTemp(NewWeightmapTextures);
|
|
|
|
if (DataInterface)
|
|
{
|
|
// Update the mipmaps for the textures we edited
|
|
for (int32 Idx = 0; Idx < WeightmapTextures.Num(); Idx++)
|
|
{
|
|
UTexture2D* WeightmapTexture = WeightmapTextures[Idx];
|
|
FLandscapeTextureDataInfo* WeightmapDataInfo = DataInterface->GetTextureDataInfo(WeightmapTexture);
|
|
|
|
int32 NumMips = WeightmapTexture->Source.GetNumMips();
|
|
TArray<FColor*> WeightmapTextureMipData;
|
|
WeightmapTextureMipData.AddUninitialized(NumMips);
|
|
for (int32 MipIdx = 0; MipIdx < NumMips; MipIdx++)
|
|
{
|
|
WeightmapTextureMipData[MipIdx] = (FColor*)WeightmapDataInfo->GetMipData(MipIdx);
|
|
}
|
|
|
|
ULandscapeComponent::UpdateWeightmapMips(NumSubsections, SubsectionSizeQuads, WeightmapTexture, WeightmapTextureMipData, 0, 0, MAX_int32, MAX_int32, WeightmapDataInfo);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ALandscapeProxy::RemoveInvalidWeightmaps()
|
|
{
|
|
if (GIsEditor)
|
|
{
|
|
for (TMap< UTexture2D*, struct FLandscapeWeightmapUsage >::TIterator It(WeightmapUsageMap); It; ++It)
|
|
{
|
|
UTexture2D* Tex = It.Key();
|
|
FLandscapeWeightmapUsage& Usage = It.Value();
|
|
if (Usage.FreeChannelCount() == 4) // Invalid Weight-map
|
|
{
|
|
if (Tex)
|
|
{
|
|
Tex->SetFlags(RF_Transactional);
|
|
Tex->Modify();
|
|
Tex->MarkPackageDirty();
|
|
Tex->ClearFlags(RF_Standalone);
|
|
}
|
|
WeightmapUsageMap.Remove(Tex);
|
|
}
|
|
}
|
|
|
|
// Remove Unused Weightmaps...
|
|
for (int32 Idx = 0; Idx < LandscapeComponents.Num(); ++Idx)
|
|
{
|
|
ULandscapeComponent* Component = LandscapeComponents[Idx];
|
|
Component->RemoveInvalidWeightmaps();
|
|
}
|
|
}
|
|
}
|
|
|
|
void ULandscapeComponent::RemoveInvalidWeightmaps()
|
|
{
|
|
// Adjust WeightmapTextureIndex index for other layers
|
|
TSet<int32> UsedTextureIndices;
|
|
TSet<int32> AllTextureIndices;
|
|
for (int32 LayerIdx = 0; LayerIdx < WeightmapLayerAllocations.Num(); LayerIdx++)
|
|
{
|
|
UsedTextureIndices.Add(WeightmapLayerAllocations[LayerIdx].WeightmapTextureIndex);
|
|
}
|
|
|
|
for (int32 WeightIdx = 0; WeightIdx < WeightmapTextures.Num(); ++WeightIdx)
|
|
{
|
|
AllTextureIndices.Add(WeightIdx);
|
|
}
|
|
|
|
TSet<int32> UnUsedTextureIndices = AllTextureIndices.Difference(UsedTextureIndices);
|
|
|
|
int32 DeletedLayers = 0;
|
|
for (TSet<int32>::TIterator It(UnUsedTextureIndices); It; ++It)
|
|
{
|
|
int32 DeleteLayerWeightmapTextureIndex = *It - DeletedLayers;
|
|
WeightmapTextures[DeleteLayerWeightmapTextureIndex]->SetFlags(RF_Transactional);
|
|
WeightmapTextures[DeleteLayerWeightmapTextureIndex]->Modify();
|
|
WeightmapTextures[DeleteLayerWeightmapTextureIndex]->MarkPackageDirty();
|
|
WeightmapTextures[DeleteLayerWeightmapTextureIndex]->ClearFlags(RF_Standalone);
|
|
WeightmapTextures.RemoveAt(DeleteLayerWeightmapTextureIndex);
|
|
|
|
// Adjust WeightmapTextureIndex index for other layers
|
|
for (int32 LayerIdx = 0; LayerIdx < WeightmapLayerAllocations.Num(); LayerIdx++)
|
|
{
|
|
FWeightmapLayerAllocationInfo& Allocation = WeightmapLayerAllocations[LayerIdx];
|
|
|
|
if (Allocation.WeightmapTextureIndex > DeleteLayerWeightmapTextureIndex)
|
|
{
|
|
Allocation.WeightmapTextureIndex--;
|
|
}
|
|
|
|
check(Allocation.WeightmapTextureIndex < WeightmapTextures.Num());
|
|
}
|
|
DeletedLayers++;
|
|
}
|
|
}
|
|
|
|
void ULandscapeComponent::InitHeightmapData(TArray<FColor>& Heights, bool bUpdateCollision)
|
|
{
|
|
int32 ComponentSizeVerts = NumSubsections * (SubsectionSizeQuads + 1);
|
|
|
|
if (Heights.Num() != FMath::Square(ComponentSizeVerts))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Handling old Height map....
|
|
if (HeightmapTexture && HeightmapTexture->GetOutermost() != GetTransientPackage()
|
|
&& HeightmapTexture->GetOutermost() == GetOutermost()
|
|
&& HeightmapTexture->Source.GetSizeX() >= ComponentSizeVerts) // if Height map is not valid...
|
|
{
|
|
HeightmapTexture->SetFlags(RF_Transactional);
|
|
HeightmapTexture->Modify();
|
|
HeightmapTexture->MarkPackageDirty();
|
|
HeightmapTexture->ClearFlags(RF_Standalone); // Delete if no reference...
|
|
}
|
|
|
|
// New Height map
|
|
TArray<FColor*> HeightmapTextureMipData;
|
|
// make sure the heightmap UVs are powers of two.
|
|
int32 HeightmapSizeU = (1 << FMath::CeilLogTwo(ComponentSizeVerts));
|
|
int32 HeightmapSizeV = (1 << FMath::CeilLogTwo(ComponentSizeVerts));
|
|
|
|
// Height map construction
|
|
HeightmapTexture = GetLandscapeProxy()->CreateLandscapeTexture(HeightmapSizeU, HeightmapSizeV, TEXTUREGROUP_Terrain_Heightmap, TSF_BGRA8);
|
|
|
|
int32 MipSubsectionSizeQuads = SubsectionSizeQuads;
|
|
int32 MipSizeU = HeightmapSizeU;
|
|
int32 MipSizeV = HeightmapSizeV;
|
|
|
|
HeightmapScaleBias = FVector4(1.0f / (float)HeightmapSizeU, 1.0f / (float)HeightmapSizeV, 0.0f, 0.0f);
|
|
|
|
int32 Mip = 0;
|
|
while (MipSizeU > 1 && MipSizeV > 1 && MipSubsectionSizeQuads >= 1)
|
|
{
|
|
FColor* HeightmapTextureData = (FColor*)HeightmapTexture->Source.LockMip(Mip);
|
|
if (Mip == 0)
|
|
{
|
|
FMemory::Memcpy(HeightmapTextureData, Heights.GetData(), MipSizeU*MipSizeV*sizeof(FColor));
|
|
}
|
|
else
|
|
{
|
|
FMemory::Memzero(HeightmapTextureData, MipSizeU*MipSizeV*sizeof(FColor));
|
|
}
|
|
HeightmapTextureMipData.Add(HeightmapTextureData);
|
|
|
|
MipSizeU >>= 1;
|
|
MipSizeV >>= 1;
|
|
Mip++;
|
|
|
|
MipSubsectionSizeQuads = ((MipSubsectionSizeQuads + 1) >> 1) - 1;
|
|
}
|
|
ULandscapeComponent::GenerateHeightmapMips(HeightmapTextureMipData);
|
|
|
|
if (bUpdateCollision)
|
|
{
|
|
ULandscapeComponent::UpdateCollisionHeightData(HeightmapTextureMipData[CollisionMipLevel]);
|
|
}
|
|
|
|
for (int32 i = 0; i < HeightmapTextureMipData.Num(); i++)
|
|
{
|
|
HeightmapTexture->Source.UnlockMip(i);
|
|
}
|
|
HeightmapTexture->PostEditChange();
|
|
}
|
|
|
|
void ULandscapeComponent::InitWeightmapData(TArray<ULandscapeLayerInfoObject*>& LayerInfos, TArray<TArray<uint8> >& WeightmapData)
|
|
{
|
|
if (LayerInfos.Num() != WeightmapData.Num() || LayerInfos.Num() <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int32 ComponentSizeVerts = NumSubsections * (SubsectionSizeQuads + 1);
|
|
|
|
// Validation..
|
|
for (int32 Idx = 0; Idx < WeightmapData.Num(); ++Idx)
|
|
{
|
|
if (WeightmapData[Idx].Num() != FMath::Square(ComponentSizeVerts))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (int32 Idx = 0; Idx < WeightmapTextures.Num(); ++Idx)
|
|
{
|
|
if (WeightmapTextures[Idx] && WeightmapTextures[Idx]->GetOutermost() != GetTransientPackage()
|
|
&& WeightmapTextures[Idx]->GetOutermost() == GetOutermost()
|
|
&& WeightmapTextures[Idx]->Source.GetSizeX() == ComponentSizeVerts)
|
|
{
|
|
WeightmapTextures[Idx]->SetFlags(RF_Transactional);
|
|
WeightmapTextures[Idx]->Modify();
|
|
WeightmapTextures[Idx]->MarkPackageDirty();
|
|
WeightmapTextures[Idx]->ClearFlags(RF_Standalone); // Delete if no reference...
|
|
}
|
|
}
|
|
WeightmapTextures.Empty();
|
|
|
|
WeightmapLayerAllocations.Empty(LayerInfos.Num());
|
|
for (int32 Idx = 0; Idx < LayerInfos.Num(); ++Idx)
|
|
{
|
|
new (WeightmapLayerAllocations)FWeightmapLayerAllocationInfo(LayerInfos[Idx]);
|
|
}
|
|
|
|
ReallocateWeightmaps(NULL);
|
|
|
|
check(WeightmapLayerAllocations.Num() > 0 && WeightmapTextures.Num() > 0);
|
|
|
|
int32 WeightmapSize = ComponentSizeVerts;
|
|
WeightmapScaleBias = FVector4(1.0f / (float)WeightmapSize, 1.0f / (float)WeightmapSize, 0.5f / (float)WeightmapSize, 0.5f / (float)WeightmapSize);
|
|
WeightmapSubsectionOffset = (float)(SubsectionSizeQuads + 1) / (float)WeightmapSize;
|
|
|
|
// Channel remapping
|
|
int32 ChannelOffsets[4] = { (int32)STRUCT_OFFSET(FColor, R), (int32)STRUCT_OFFSET(FColor, G), (int32)STRUCT_OFFSET(FColor, B), (int32)STRUCT_OFFSET(FColor, A) };
|
|
|
|
TArray<void*> WeightmapDataPtrs;
|
|
WeightmapDataPtrs.AddUninitialized(WeightmapTextures.Num());
|
|
for (int32 WeightmapIdx = 0; WeightmapIdx < WeightmapTextures.Num(); ++WeightmapIdx)
|
|
{
|
|
WeightmapDataPtrs[WeightmapIdx] = WeightmapTextures[WeightmapIdx]->Source.LockMip(0);
|
|
}
|
|
|
|
for (int32 LayerIdx = 0; LayerIdx < WeightmapLayerAllocations.Num(); ++LayerIdx)
|
|
{
|
|
void* DestDataPtr = WeightmapDataPtrs[WeightmapLayerAllocations[LayerIdx].WeightmapTextureIndex];
|
|
uint8* DestTextureData = (uint8*)DestDataPtr + ChannelOffsets[WeightmapLayerAllocations[LayerIdx].WeightmapTextureChannel];
|
|
uint8* SrcTextureData = (uint8*)&WeightmapData[LayerIdx][0];
|
|
|
|
for (int32 i = 0; i < WeightmapData[LayerIdx].Num(); i++)
|
|
{
|
|
DestTextureData[i * 4] = SrcTextureData[i];
|
|
}
|
|
}
|
|
|
|
for (int32 Idx = 0; Idx < WeightmapTextures.Num(); Idx++)
|
|
{
|
|
UTexture2D* WeightmapTexture = WeightmapTextures[Idx];
|
|
WeightmapTexture->Source.UnlockMip(0);
|
|
}
|
|
|
|
for (int32 Idx = 0; Idx < WeightmapTextures.Num(); Idx++)
|
|
{
|
|
UTexture2D* WeightmapTexture = WeightmapTextures[Idx];
|
|
{
|
|
FLandscapeTextureDataInfo WeightmapDataInfo(WeightmapTexture);
|
|
|
|
int32 NumMips = WeightmapTexture->Source.GetNumMips();
|
|
TArray<FColor*> WeightmapTextureMipData;
|
|
WeightmapTextureMipData.AddUninitialized(NumMips);
|
|
for (int32 MipIdx = 0; MipIdx < NumMips; MipIdx++)
|
|
{
|
|
WeightmapTextureMipData[MipIdx] = (FColor*)WeightmapDataInfo.GetMipData(MipIdx);
|
|
}
|
|
|
|
ULandscapeComponent::UpdateWeightmapMips(NumSubsections, SubsectionSizeQuads, WeightmapTexture, WeightmapTextureMipData, 0, 0, MAX_int32, MAX_int32, &WeightmapDataInfo);
|
|
}
|
|
|
|
WeightmapTexture->PostEditChange();
|
|
}
|
|
|
|
FlushRenderingCommands();
|
|
|
|
MaterialInstance = NULL;
|
|
|
|
}
|
|
|
|
#define MAX_LANDSCAPE_EXPORT_COMPONENTS_NUM 16
|
|
#define MAX_LANDSCAPE_PROP_TEXT_LENGTH 1024*1024*16
|
|
|
|
|
|
bool ALandscapeProxy::ShouldExport()
|
|
{
|
|
if (!bIsMovingToLevel && LandscapeComponents.Num() > MAX_LANDSCAPE_EXPORT_COMPONENTS_NUM)
|
|
{
|
|
// Prompt to save startup packages
|
|
if (EAppReturnType::Yes == FMessageDialog::Open(EAppMsgType::YesNo, FText::Format(
|
|
NSLOCTEXT("UnrealEd", "LandscapeExport_Warning", "Landscape has large number({0}) of components, so it will use large amount memory to copy it to the clipboard. Do you want to proceed?"), FText::AsNumber(LandscapeComponents.Num()))))
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ALandscapeProxy::ShouldImport(FString* ActorPropString, bool IsMovingToLevel)
|
|
{
|
|
bIsMovingToLevel = IsMovingToLevel;
|
|
if (!bIsMovingToLevel && ActorPropString && ActorPropString->Len() > MAX_LANDSCAPE_PROP_TEXT_LENGTH)
|
|
{
|
|
// Prompt to save startup packages
|
|
if (EAppReturnType::Yes == FMessageDialog::Open(EAppMsgType::YesNo, FText::Format(
|
|
NSLOCTEXT("UnrealEd", "LandscapeImport_Warning", "Landscape is about to import large amount memory ({0}MB) from the clipboard, which will take some time. Do you want to proceed?"), FText::AsNumber(ActorPropString->Len() >> 20))))
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void ULandscapeComponent::ExportCustomProperties(FOutputDevice& Out, uint32 Indent)
|
|
{
|
|
if (HasAnyFlags(RF_ClassDefaultObject))
|
|
{
|
|
return;
|
|
}
|
|
// Height map
|
|
int32 NumVertices = FMath::Square(NumSubsections*(SubsectionSizeQuads + 1));
|
|
FLandscapeComponentDataInterface DataInterface(this);
|
|
TArray<FColor> Heightmap;
|
|
DataInterface.GetHeightmapTextureData(Heightmap);
|
|
check(Heightmap.Num() == NumVertices);
|
|
|
|
Out.Logf(TEXT("%sCustomProperties LandscapeHeightData "), FCString::Spc(Indent));
|
|
for (int32 i = 0; i < NumVertices; i++)
|
|
{
|
|
Out.Logf(TEXT("%x "), Heightmap[i].DWColor());
|
|
}
|
|
|
|
TArray<uint8> Weightmap;
|
|
// Weight map
|
|
Out.Logf(TEXT("LayerNum=%d "), WeightmapLayerAllocations.Num());
|
|
for (int32 i = 0; i < WeightmapLayerAllocations.Num(); i++)
|
|
{
|
|
if (DataInterface.GetWeightmapTextureData(WeightmapLayerAllocations[i].LayerInfo, Weightmap))
|
|
{
|
|
Out.Logf(TEXT("LayerInfo=%s "), *WeightmapLayerAllocations[i].LayerInfo->GetPathName());
|
|
for (int32 VertexIndex = 0; VertexIndex < NumVertices; VertexIndex++)
|
|
{
|
|
Out.Logf(TEXT("%x "), Weightmap[VertexIndex]);
|
|
}
|
|
}
|
|
}
|
|
|
|
Out.Logf(TEXT("\r\n"));
|
|
}
|
|
|
|
|
|
void ULandscapeComponent::ImportCustomProperties(const TCHAR* SourceText, FFeedbackContext* Warn)
|
|
{
|
|
if (FParse::Command(&SourceText, TEXT("LandscapeHeightData")))
|
|
{
|
|
int32 NumVertices = FMath::Square(NumSubsections*(SubsectionSizeQuads + 1));
|
|
|
|
TArray<FColor> Heights;
|
|
Heights.Empty(NumVertices);
|
|
Heights.AddZeroed(NumVertices);
|
|
|
|
FParse::Next(&SourceText);
|
|
int32 i = 0;
|
|
TCHAR* StopStr;
|
|
while (FChar::IsHexDigit(*SourceText))
|
|
{
|
|
if (i < NumVertices)
|
|
{
|
|
Heights[i++].DWColor() = FCString::Strtoi(SourceText, &StopStr, 16);
|
|
while (FChar::IsHexDigit(*SourceText))
|
|
{
|
|
SourceText++;
|
|
}
|
|
}
|
|
|
|
FParse::Next(&SourceText);
|
|
}
|
|
|
|
if (i != NumVertices)
|
|
{
|
|
Warn->Logf(*NSLOCTEXT("Core", "SyntaxError", "Syntax Error").ToString());
|
|
}
|
|
|
|
int32 ComponentSizeVerts = NumSubsections * (SubsectionSizeQuads + 1);
|
|
|
|
InitHeightmapData(Heights, false);
|
|
|
|
// Weight maps
|
|
int32 LayerNum = 0;
|
|
if (FParse::Value(SourceText, TEXT("LayerNum="), LayerNum))
|
|
{
|
|
while (*SourceText && (!FChar::IsWhitespace(*SourceText)))
|
|
{
|
|
++SourceText;
|
|
}
|
|
FParse::Next(&SourceText);
|
|
}
|
|
|
|
if (LayerNum <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Init memory
|
|
TArray<ULandscapeLayerInfoObject*> LayerInfos;
|
|
LayerInfos.Empty(LayerNum);
|
|
TArray<TArray<uint8>> WeightmapData;
|
|
for (int32 LayerIndex = 0; LayerIndex < LayerNum; ++LayerIndex)
|
|
{
|
|
TArray<uint8> Weights;
|
|
Weights.Empty(NumVertices);
|
|
Weights.AddUninitialized(NumVertices);
|
|
WeightmapData.Add(Weights);
|
|
}
|
|
|
|
int32 LayerIdx = 0;
|
|
FString LayerInfoPath;
|
|
while (*SourceText)
|
|
{
|
|
if (FParse::Value(SourceText, TEXT("LayerInfo="), LayerInfoPath))
|
|
{
|
|
LayerInfos.Add(LoadObject<ULandscapeLayerInfoObject>(NULL, *LayerInfoPath));
|
|
|
|
while (*SourceText && (!FChar::IsWhitespace(*SourceText)))
|
|
{
|
|
++SourceText;
|
|
}
|
|
FParse::Next(&SourceText);
|
|
check(*SourceText);
|
|
|
|
i = 0;
|
|
while (FChar::IsHexDigit(*SourceText))
|
|
{
|
|
if (i < NumVertices)
|
|
{
|
|
(WeightmapData[LayerIdx])[i++] = (uint8)FCString::Strtoi(SourceText, &StopStr, 16);
|
|
while (FChar::IsHexDigit(*SourceText))
|
|
{
|
|
SourceText++;
|
|
}
|
|
}
|
|
FParse::Next(&SourceText);
|
|
}
|
|
|
|
if (i != NumVertices)
|
|
{
|
|
Warn->Logf(*NSLOCTEXT("Core", "SyntaxError", "Syntax Error").ToString());
|
|
}
|
|
LayerIdx++;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
InitWeightmapData(LayerInfos, WeightmapData);
|
|
}
|
|
}
|
|
|
|
bool ALandscapeProxy::IsValidLandscapeActor(ALandscape* Landscape)
|
|
{
|
|
if (bIsProxy && Landscape)
|
|
{
|
|
if (!Landscape->HasAnyFlags(RF_BeginDestroyed))
|
|
{
|
|
if (LandscapeActor.IsNull() && !LandscapeGuid.IsValid())
|
|
{
|
|
return true; // always valid for newly created Proxy
|
|
}
|
|
if (((LandscapeActor && LandscapeActor == Landscape)
|
|
|| (LandscapeActor.IsNull() && LandscapeGuid.IsValid() && LandscapeGuid == Landscape->LandscapeGuid))
|
|
&& ComponentSizeQuads == Landscape->ComponentSizeQuads
|
|
&& NumSubsections == Landscape->NumSubsections
|
|
&& SubsectionSizeQuads == Landscape->SubsectionSizeQuads)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
struct FMobileLayerAllocation
|
|
{
|
|
FWeightmapLayerAllocationInfo Allocation;
|
|
|
|
FMobileLayerAllocation(const FWeightmapLayerAllocationInfo& InAllocation)
|
|
: Allocation(InAllocation)
|
|
{
|
|
}
|
|
|
|
friend bool operator<(const FMobileLayerAllocation& lhs, const FMobileLayerAllocation& rhs)
|
|
{
|
|
if (!lhs.Allocation.LayerInfo && !rhs.Allocation.LayerInfo) return false; // equally broken :P
|
|
if (!lhs.Allocation.LayerInfo && rhs.Allocation.LayerInfo) return false; // broken layers sort to the end
|
|
if (!rhs.Allocation.LayerInfo && lhs.Allocation.LayerInfo) return true;
|
|
|
|
if (lhs.Allocation.LayerInfo == ALandscapeProxy::VisibilityLayer && rhs.Allocation.LayerInfo != ALandscapeProxy::VisibilityLayer) return true; // visibility layer to the front
|
|
if (rhs.Allocation.LayerInfo == ALandscapeProxy::VisibilityLayer && lhs.Allocation.LayerInfo != ALandscapeProxy::VisibilityLayer) return false;
|
|
|
|
if (lhs.Allocation.LayerInfo->bNoWeightBlend && !rhs.Allocation.LayerInfo->bNoWeightBlend) return false; // non-blended layers sort to the end
|
|
if (rhs.Allocation.LayerInfo->bNoWeightBlend && !lhs.Allocation.LayerInfo->bNoWeightBlend) return true;
|
|
|
|
// TODO: If we want to support cleanly decaying a pc landscape for mobile
|
|
// we should probably add other sort criteria, e.g. coverage
|
|
// or e.g. add an "importance" to layerinfos and sort on that
|
|
|
|
return false; // equal, preserve order
|
|
}
|
|
};
|
|
|
|
void ULandscapeComponent::GeneratePlatformPixelData(bool bIsCooking)
|
|
{
|
|
check(!IsTemplate())
|
|
|
|
if (!bIsCooking)
|
|
{
|
|
// Calculate hash of source data and skip generation if the data we have in memory is unchanged
|
|
FBufferArchive ComponentStateAr;
|
|
SerializeStateHashes(ComponentStateAr);
|
|
|
|
uint32 Hash[5];
|
|
FSHA1::HashBuffer(ComponentStateAr.GetData(), ComponentStateAr.Num(), (uint8*)Hash);
|
|
FGuid NewSourceHash = FGuid(Hash[0] ^ Hash[4], Hash[1], Hash[2], Hash[3]);
|
|
|
|
// Skip generation if the source hash matches
|
|
if (MobilePixelDataSourceHash.IsValid() &&
|
|
MobilePixelDataSourceHash == NewSourceHash &&
|
|
MobileMaterialInterface != nullptr &&
|
|
MobileWeightNormalmapTexture != nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
MobilePixelDataSourceHash = NewSourceHash;
|
|
}
|
|
|
|
TArray<FMobileLayerAllocation> MobileLayerAllocations;
|
|
MobileLayerAllocations.Reserve(WeightmapLayerAllocations.Num());
|
|
for (const auto& Allocation : WeightmapLayerAllocations)
|
|
{
|
|
MobileLayerAllocations.Emplace(Allocation);
|
|
}
|
|
MobileLayerAllocations.StableSort();
|
|
|
|
// in the current mobile shader only 3 layers are supported (the 3rd only as a blended layer)
|
|
// so make sure we have a blended layer for layer 3 if possible
|
|
if (MobileLayerAllocations.Num() >= 3 &&
|
|
MobileLayerAllocations[2].Allocation.LayerInfo && MobileLayerAllocations[2].Allocation.LayerInfo->bNoWeightBlend)
|
|
{
|
|
int32 BlendedLayerToMove = INDEX_NONE;
|
|
|
|
// First try to swap layer 3 with an earlier blended layer
|
|
// this will allow both to work
|
|
for (int32 i = 1; i >= 0; --i)
|
|
{
|
|
if (MobileLayerAllocations[i].Allocation.LayerInfo && !MobileLayerAllocations[i].Allocation.LayerInfo->bNoWeightBlend)
|
|
{
|
|
BlendedLayerToMove = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// otherwise swap layer 3 with the first weight-blended layer found
|
|
// as non-blended layers aren't supported for layer 3 it wasn't going to work anyway, might as well swap it out for one that will work
|
|
if (BlendedLayerToMove == INDEX_NONE)
|
|
{
|
|
// I wish I could specify a start index here, but it doesn't affect the result
|
|
BlendedLayerToMove = MobileLayerAllocations.IndexOfByPredicate([](const FMobileLayerAllocation& MobileAllocation){ return MobileAllocation.Allocation.LayerInfo && !MobileAllocation.Allocation.LayerInfo->bNoWeightBlend; });
|
|
}
|
|
|
|
if (BlendedLayerToMove != INDEX_NONE)
|
|
{
|
|
// Preserve order of all but the blended layer we're moving into slot 3
|
|
FMobileLayerAllocation TempAllocation = MoveTemp(MobileLayerAllocations[BlendedLayerToMove]);
|
|
MobileLayerAllocations.RemoveAt(BlendedLayerToMove, 1, false);
|
|
MobileLayerAllocations.Insert(MoveTemp(TempAllocation), 2);
|
|
}
|
|
}
|
|
|
|
int32 WeightmapSize = (SubsectionSizeQuads + 1) * NumSubsections;
|
|
UTexture2D* NewWeightNormalmapTexture = GetLandscapeProxy()->CreateLandscapeTexture(WeightmapSize, WeightmapSize, TEXTUREGROUP_Terrain_Weightmap, TSF_BGRA8);
|
|
CreateEmptyTextureMips(NewWeightNormalmapTexture);
|
|
|
|
{
|
|
FLandscapeEditDataInterface LandscapeEdit(GetLandscapeInfo(false));
|
|
|
|
if (WeightmapTextures.Num() > 0)
|
|
{
|
|
int32 CurrentIdx = 0;
|
|
for (const auto& MobileAllocation : MobileLayerAllocations)
|
|
{
|
|
// Only for valid Layers
|
|
if (MobileAllocation.Allocation.LayerInfo)
|
|
{
|
|
LandscapeEdit.CopyTextureFromWeightmap(NewWeightNormalmapTexture, CurrentIdx, this, MobileAllocation.Allocation.LayerInfo);
|
|
CurrentIdx++;
|
|
if (CurrentIdx >= 2) // Only support 2 layers in texture
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// copy normals into B/A channels.
|
|
LandscapeEdit.CopyTextureFromHeightmap(NewWeightNormalmapTexture, 2, this, 2);
|
|
LandscapeEdit.CopyTextureFromHeightmap(NewWeightNormalmapTexture, 3, this, 3);
|
|
}
|
|
|
|
NewWeightNormalmapTexture->PostEditChange();
|
|
|
|
MobileWeightNormalmapTexture = NewWeightNormalmapTexture;
|
|
|
|
|
|
FLinearColor Masks[5];
|
|
Masks[0] = FLinearColor(1, 0, 0, 0);
|
|
Masks[1] = FLinearColor(0, 1, 0, 0);
|
|
Masks[2] = FLinearColor(0, 0, 1, 0);
|
|
Masks[3] = FLinearColor(0, 0, 0, 1);
|
|
Masks[4] = FLinearColor(0, 0, 0, 0); // mask out layers 4+ altogether
|
|
|
|
if (!bIsCooking)
|
|
{
|
|
UMaterialInstanceDynamic* NewMobileMaterialInstance = UMaterialInstanceDynamic::Create(MaterialInstance, GetOutermost());
|
|
|
|
MobileBlendableLayerMask = 0;
|
|
|
|
// Set the layer mask
|
|
int32 CurrentIdx = 0;
|
|
for (const auto& MobileAllocation : MobileLayerAllocations)
|
|
{
|
|
const FWeightmapLayerAllocationInfo& Allocation = MobileAllocation.Allocation;
|
|
if (Allocation.LayerInfo)
|
|
{
|
|
FName LayerName = Allocation.LayerInfo == ALandscapeProxy::VisibilityLayer ? UMaterialExpressionLandscapeVisibilityMask::ParameterName : Allocation.LayerInfo->LayerName;
|
|
NewMobileMaterialInstance->SetVectorParameterValue(FName(*FString::Printf(TEXT("LayerMask_%s"), *LayerName.ToString())), Masks[FMath::Min(4, CurrentIdx)]);
|
|
MobileBlendableLayerMask |= (!Allocation.LayerInfo->bNoWeightBlend ? (1 << CurrentIdx) : 0);
|
|
CurrentIdx++;
|
|
}
|
|
}
|
|
MobileMaterialInterface = NewMobileMaterialInstance;
|
|
}
|
|
else // for cooking
|
|
{
|
|
UMaterialInstanceConstant* CombinationMaterialInstance = GetCombinationMaterial(true);
|
|
UMaterialInstanceConstant* NewMobileMaterialInstance = NewObject<ULandscapeMaterialInstanceConstant>(GetOutermost());
|
|
|
|
NewMobileMaterialInstance->SetParentEditorOnly(CombinationMaterialInstance);
|
|
|
|
MobileBlendableLayerMask = 0;
|
|
|
|
// Set the layer mask
|
|
int32 CurrentIdx = 0;
|
|
for (const auto& MobileAllocation : MobileLayerAllocations)
|
|
{
|
|
const FWeightmapLayerAllocationInfo& Allocation = MobileAllocation.Allocation;
|
|
if (Allocation.LayerInfo)
|
|
{
|
|
FName LayerName = Allocation.LayerInfo == ALandscapeProxy::VisibilityLayer ? UMaterialExpressionLandscapeVisibilityMask::ParameterName : Allocation.LayerInfo->LayerName;
|
|
NewMobileMaterialInstance->SetVectorParameterValueEditorOnly(FName(*FString::Printf(TEXT("LayerMask_%s"), *LayerName.ToString())), Masks[FMath::Min(4, CurrentIdx)]);
|
|
MobileBlendableLayerMask |= (!Allocation.LayerInfo->bNoWeightBlend ? (1 << CurrentIdx) : 0);
|
|
CurrentIdx++;
|
|
}
|
|
}
|
|
|
|
NewMobileMaterialInstance->PostEditChange();
|
|
|
|
MobileMaterialInterface = NewMobileMaterialInstance;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Generates vertex buffer data from the component's heightmap texture, for use on platforms without vertex texture fetch
|
|
//
|
|
void ULandscapeComponent::GeneratePlatformVertexData()
|
|
{
|
|
if (IsTemplate())
|
|
{
|
|
return;
|
|
}
|
|
check(HeightmapTexture);
|
|
check(HeightmapTexture->Source.GetFormat() == TSF_BGRA8);
|
|
|
|
TArray<uint8> NewPlatformData;
|
|
int32 NewPlatformDataSize = 0;
|
|
|
|
int32 SubsectionSizeVerts = SubsectionSizeQuads + 1;
|
|
int32 MaxLOD = FMath::CeilLogTwo(SubsectionSizeVerts) - 1;
|
|
|
|
float HeightmapSubsectionOffsetU = (float)(SubsectionSizeVerts) / (float)HeightmapTexture->Source.GetSizeX();
|
|
float HeightmapSubsectionOffsetV = (float)(SubsectionSizeVerts) / (float)HeightmapTexture->Source.GetSizeY();
|
|
|
|
NewPlatformDataSize += sizeof(FLandscapeMobileVertex) * FMath::Square(SubsectionSizeVerts * NumSubsections);
|
|
NewPlatformData.AddZeroed(NewPlatformDataSize);
|
|
|
|
// Get the required mip data
|
|
TArray<FColor*> HeightmapMipData;
|
|
for (int32 MipIdx = 0; MipIdx < FMath::Min(LANDSCAPE_MAX_ES_LOD, HeightmapTexture->Source.GetNumMips()); MipIdx++)
|
|
{
|
|
int32 MipSubsectionSizeVerts = (SubsectionSizeVerts) >> MipIdx;
|
|
if (MipSubsectionSizeVerts > 1)
|
|
{
|
|
HeightmapMipData.Add((FColor*)HeightmapTexture->Source.LockMip(MipIdx));
|
|
}
|
|
}
|
|
|
|
TMap<uint64, int32> VertexMap;
|
|
TArray<FLandscapeVertexRef> VertexOrder;
|
|
VertexOrder.Empty(FMath::Square(SubsectionSizeVerts * NumSubsections));
|
|
|
|
// Layout index buffer to determine best vertex order
|
|
for (int32 Mip = MaxLOD; Mip >= 0; Mip--)
|
|
{
|
|
int32 LodSubsectionSizeQuads = (SubsectionSizeVerts >> Mip) - 1;
|
|
float MipRatio = (float)SubsectionSizeQuads / (float)LodSubsectionSizeQuads; // Morph current MIP to base MIP
|
|
|
|
for (int32 SubY = 0; SubY < NumSubsections; SubY++)
|
|
{
|
|
for (int32 SubX = 0; SubX < NumSubsections; SubX++)
|
|
{
|
|
for (int32 y = 0; y < LodSubsectionSizeQuads; y++)
|
|
{
|
|
for (int32 x = 0; x < LodSubsectionSizeQuads; x++)
|
|
{
|
|
int32 x0 = FMath::RoundToInt((float)x * MipRatio);
|
|
int32 y0 = FMath::RoundToInt((float)y * MipRatio);
|
|
int32 x1 = FMath::RoundToInt((float)(x + 1) * MipRatio);
|
|
int32 y1 = FMath::RoundToInt((float)(y + 1) * MipRatio);
|
|
|
|
FLandscapeVertexRef V1(x0, y0, SubX, SubY);
|
|
FLandscapeVertexRef V2(x1, y0, SubX, SubY);
|
|
FLandscapeVertexRef V3(x1, y1, SubX, SubY);
|
|
FLandscapeVertexRef V4(x0, y1, SubX, SubY);
|
|
|
|
uint64 Key1 = V1.MakeKey();
|
|
if (VertexMap.Find(Key1) == NULL)
|
|
{
|
|
VertexMap.Add(Key1, VertexOrder.Num());
|
|
VertexOrder.Add(V1);
|
|
}
|
|
uint64 Key2 = V2.MakeKey();
|
|
if (VertexMap.Find(Key2) == NULL)
|
|
{
|
|
VertexMap.Add(Key2, VertexOrder.Num());
|
|
VertexOrder.Add(V2);
|
|
}
|
|
uint64 Key3 = V3.MakeKey();
|
|
if (VertexMap.Find(Key3) == NULL)
|
|
{
|
|
VertexMap.Add(Key3, VertexOrder.Num());
|
|
VertexOrder.Add(V3);
|
|
}
|
|
uint64 Key4 = V4.MakeKey();
|
|
if (VertexMap.Find(Key4) == NULL)
|
|
{
|
|
VertexMap.Add(Key4, VertexOrder.Num());
|
|
VertexOrder.Add(V4);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
check(VertexOrder.Num() == FMath::Square(SubsectionSizeVerts) * FMath::Square(NumSubsections));
|
|
|
|
// Fill in the vertices in the specified order
|
|
FLandscapeMobileVertex* DstVert = (FLandscapeMobileVertex*)NewPlatformData.GetData();
|
|
for (int32 Idx = 0; Idx < VertexOrder.Num(); Idx++)
|
|
{
|
|
int32 X = VertexOrder[Idx].X;
|
|
int32 Y = VertexOrder[Idx].Y;
|
|
int32 SubX = VertexOrder[Idx].SubX;
|
|
int32 SubY = VertexOrder[Idx].SubY;
|
|
|
|
float HeightmapScaleBiasZ = HeightmapScaleBias.Z + HeightmapSubsectionOffsetU * (float)SubX;
|
|
float HeightmapScaleBiasW = HeightmapScaleBias.W + HeightmapSubsectionOffsetV * (float)SubY;
|
|
int32 BaseMipOfsX = FMath::RoundToInt(HeightmapScaleBiasZ * (float)HeightmapTexture->Source.GetSizeX());
|
|
int32 BaseMipOfsY = FMath::RoundToInt(HeightmapScaleBiasW * (float)HeightmapTexture->Source.GetSizeY());
|
|
|
|
DstVert->Position[0] = X;
|
|
DstVert->Position[1] = Y;
|
|
DstVert->Position[2] = SubX;
|
|
DstVert->Position[3] = SubY;
|
|
|
|
TArray<int32> MipHeights;
|
|
MipHeights.AddZeroed(HeightmapMipData.Num());
|
|
int32 LastIndex = 0;
|
|
uint16 MaxHeight = 0, MinHeight = 65535;
|
|
|
|
for (int32 Mip = 0; Mip < HeightmapMipData.Num(); ++Mip)
|
|
{
|
|
int32 MipSizeX = HeightmapTexture->Source.GetSizeX() >> Mip;
|
|
|
|
int32 CurrentMipOfsX = BaseMipOfsX >> Mip;
|
|
int32 CurrentMipOfsY = BaseMipOfsY >> Mip;
|
|
|
|
int32 MipX = X >> Mip;
|
|
int32 MipY = Y >> Mip;
|
|
|
|
FColor* CurrentMipSrcRow = HeightmapMipData[Mip] + (CurrentMipOfsY + MipY) * MipSizeX + CurrentMipOfsX;
|
|
uint16 Height = CurrentMipSrcRow[MipX].R << 8 | CurrentMipSrcRow[MipX].G;
|
|
|
|
MipHeights[Mip] = Height;
|
|
MaxHeight = FMath::Max(MaxHeight, Height);
|
|
MinHeight = FMath::Min(MinHeight, Height);
|
|
}
|
|
|
|
DstVert->LODHeights[0] = MinHeight >> 8;
|
|
DstVert->LODHeights[1] = (MinHeight & 255);
|
|
DstVert->LODHeights[2] = MaxHeight >> 8;
|
|
DstVert->LODHeights[3] = (MaxHeight & 255);
|
|
|
|
for (int32 Mip = 0; Mip < HeightmapMipData.Num(); ++Mip)
|
|
{
|
|
if (Mip < 4)
|
|
{
|
|
DstVert->LODHeights[4 + Mip] = FMath::RoundToInt(float(MipHeights[Mip] - MinHeight) / (MaxHeight - MinHeight) * 255);
|
|
}
|
|
else // Mip 4 5 packed into SubX, SubY
|
|
{
|
|
DstVert->Position[Mip - 2] += (FMath::RoundToInt(float(MipHeights[Mip] - MinHeight) / (MaxHeight - MinHeight) * 255)) & (0xfffe);
|
|
}
|
|
}
|
|
|
|
DstVert++;
|
|
}
|
|
|
|
for (int32 MipIdx = 0; MipIdx < HeightmapTexture->Source.GetNumMips(); MipIdx++)
|
|
{
|
|
HeightmapTexture->Source.UnlockMip(MipIdx);
|
|
}
|
|
|
|
// Copy to PlatformData as Compressed
|
|
PlatformData.InitializeFromUncompressedData(NewPlatformData);
|
|
}
|
|
|
|
UTexture2D* ALandscapeProxy::CreateLandscapeTexture(int32 InSizeX, int32 InSizeY, TextureGroup InLODGroup, ETextureSourceFormat InFormat, UObject* OptionalOverrideOuter) const
|
|
{
|
|
UObject* TexOuter = OptionalOverrideOuter ? OptionalOverrideOuter : GetOutermost();
|
|
UTexture2D* NewTexture = NewObject<UTexture2D>(TexOuter);
|
|
NewTexture->Source.Init2DWithMipChain(InSizeX, InSizeY, InFormat);
|
|
NewTexture->SRGB = false;
|
|
NewTexture->CompressionNone = true;
|
|
NewTexture->MipGenSettings = TMGS_LeaveExistingMips;
|
|
NewTexture->AddressX = TA_Clamp;
|
|
NewTexture->AddressY = TA_Clamp;
|
|
NewTexture->LODGroup = InLODGroup;
|
|
|
|
return NewTexture;
|
|
}
|
|
|
|
void ALandscapeProxy::RemoveOverlappingComponent(ULandscapeComponent* Component)
|
|
{
|
|
Modify();
|
|
Component->Modify();
|
|
if (Component->CollisionComponent.IsValid() && (Component->CollisionComponent->RenderComponent.Get() == Component || Component->CollisionComponent->RenderComponent.IsNull()))
|
|
{
|
|
Component->CollisionComponent->Modify();
|
|
CollisionComponents.Remove(Component->CollisionComponent.Get());
|
|
Component->CollisionComponent.Get()->DestroyComponent();
|
|
}
|
|
LandscapeComponents.Remove(Component);
|
|
Component->DestroyComponent();
|
|
}
|
|
|
|
#endif //WITH_EDITOR
|
|
|
|
#undef LOCTEXT_NAMESPACE
|