-
-
Save MineClever/81f01a4e59c76ebcac1cea460a619bd2 to your computer and use it in GitHub Desktop.
Unreal Engine 5 - Programatically create a landscape with spline points and segments
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "LandscapeSubsystem.h" | |
#include "LandscapeComponent.h" | |
#include "LandscapeSplineActor.h" | |
#include "LandscapeSplinesComponent.h" | |
#include "LandscapeSplineControlPoint.h" | |
#include "Editor/LandscapeEditor/Private/LandscapeEdMode.h" | |
// Source: https://forums.unrealengine.com/t/creating-a-lanscape-using-c-or-the-python-api/504997/4 | |
ALandscape* CreateLandscape(const FTransform& LandscapeTransform, const int32& SectionSize, const int32& SectionsPerComponent, const int32& ComponentCountX, const int32& ComponentCountY) | |
{ | |
int32 QuadsPerComponent = SectionSize * SectionsPerComponent; | |
int32 SizeX = ComponentCountX * QuadsPerComponent + 1; | |
int32 SizeY = ComponentCountY * QuadsPerComponent + 1; | |
TArray<FLandscapeImportLayerInfo> MaterialImportLayers; | |
MaterialImportLayers.Reserve(0); | |
TMap<FGuid, TArray<uint16>> HeightDataPerLayers; | |
TMap<FGuid, TArray<FLandscapeImportLayerInfo>> MaterialLayerDataPerLayers; | |
TArray<uint16> HeightData; | |
HeightData.SetNum(SizeX * SizeY); | |
for (int32 i = 0; i < HeightData.Num(); i++) | |
{ | |
HeightData[i] = 32768; | |
} | |
HeightDataPerLayers.Add(FGuid(), MoveTemp(HeightData)); /*ENewLandscapePreviewMode.NewLandscape*/ | |
// ComputeHeightData will also modify/expand material layers data, which is why we create MaterialLayerDataPerLayers after calling ComputeHeightData | |
MaterialLayerDataPerLayers.Add(FGuid(), MoveTemp(MaterialImportLayers)); | |
//FScopedTransaction Transaction(TEXT("Undo", "Creating New Landscape")); | |
UWorld* World = GEditor->GetEditorWorldContext().World(); | |
ALandscape* Landscape = World->SpawnActor<ALandscape>(); | |
Landscape->bCanHaveLayersContent = false; | |
Landscape->LandscapeMaterial = nullptr; | |
Landscape->SetActorTransform(LandscapeTransform); | |
// automatically calculate a lighting LOD that won't crash lightmass (hopefully) | |
// < 2048x2048 -> LOD0 | |
// >=2048x2048 -> LOD1 | |
// >= 4096x4096 -> LOD2 | |
// >= 8192x8192 -> LOD3 | |
//const FGuid& InGuid, int32 InMinX, int32 InMinY, int32 InMaxX, int32 InMaxY, int32 InNumSubsections, int32 InSubsectionSizeQuads, const TMap<FGuid, TArray<uint16>>& InImportHeightData, | |
// const TCHAR* const InHeightmapFileName, const TMap<FGuid, TArray<FLandscapeImportLayerInfo>>& InImportMaterialLayerInfos, ELandscapeImportAlphamapType InImportMaterialLayerType, const TArray<struct FLandscapeLayer>* InImportLayers = nullptr | |
Landscape->Import(FGuid::NewGuid(), 0, 0, SizeX - 1, SizeY - 1, SectionsPerComponent, QuadsPerComponent, HeightDataPerLayers, nullptr, MaterialLayerDataPerLayers, ELandscapeImportAlphamapType::Additive); | |
Landscape->StaticLightingLOD = FMath::DivideAndRoundUp(FMath::CeilLogTwo((SizeX * SizeY) / (2048 * 2048) + 1), (uint32)2); | |
// Register all the landscape components | |
ULandscapeInfo* LandscapeInfo = Landscape->GetLandscapeInfo(); | |
LandscapeInfo->UpdateLayerInfoMap(Landscape); | |
Landscape->RegisterAllComponents(); | |
// Need to explicitly call PostEditChange on the LandscapeMaterial property or the landscape proxy won't update its material | |
FPropertyChangedEvent MaterialPropertyChangedEvent(FindFieldChecked< FProperty >(Landscape->GetClass(), FName("LandscapeMaterial"))); | |
Landscape->PostEditChangeProperty(MaterialPropertyChangedEvent); | |
Landscape->PostEditChange(); | |
return Landscape; | |
} | |
// Copied mostly from <UE 5.3 Source>\Engine\Source\Editor\LandscapeEditor\Private\LandscapeEdModeSplineTools.cpp | |
void AddSegment(ULandscapeSplineControlPoint* Start, ULandscapeSplineControlPoint* End, bool bAutoRotateStart, bool bAutoRotateEnd) | |
{ | |
if (Start == End) | |
{ | |
//UE_LOG( TEXT("Can't join spline control point to itself.") ); | |
return; | |
} | |
if (Start->GetOuterULandscapeSplinesComponent() != End->GetOuterULandscapeSplinesComponent()) | |
{ | |
//UE_LOG( TEXT("Can't join spline control points across different terrains.") ); | |
return; | |
} | |
for (const FLandscapeSplineConnection& Connection : Start->ConnectedSegments) | |
{ | |
// if the *other* end on the connected segment connects to the "end" control point... | |
if (Connection.GetFarConnection().ControlPoint == End) | |
{ | |
//UE_LOG( TEXT("Spline control points already joined connected!") ); | |
return; | |
} | |
} | |
ULandscapeSplinesComponent* SplinesComponent = Start->GetOuterULandscapeSplinesComponent(); | |
SplinesComponent->Modify(); | |
Start->Modify(); | |
End->Modify(); | |
ULandscapeSplineSegment* NewSegment = NewObject<ULandscapeSplineSegment>(SplinesComponent, NAME_None, RF_Transactional); | |
TArray<TObjectPtr<ULandscapeSplineSegment>>& Segments = SplinesComponent->GetSegments(); | |
Segments.Add(NewSegment); | |
NewSegment->Connections[0].ControlPoint = Start; | |
NewSegment->Connections[1].ControlPoint = End; | |
NewSegment->Connections[0].SocketName = Start->GetBestConnectionTo(End->Location); | |
NewSegment->Connections[1].SocketName = End->GetBestConnectionTo(Start->Location); | |
FVector StartLocation; FRotator StartRotation; | |
Start->GetConnectionLocationAndRotation(NewSegment->Connections[0].SocketName, StartLocation, StartRotation); | |
FVector EndLocation; FRotator EndRotation; | |
End->GetConnectionLocationAndRotation(NewSegment->Connections[1].SocketName, EndLocation, EndRotation); | |
// Set up tangent lengths | |
NewSegment->Connections[0].TangentLen = static_cast<float>((EndLocation - StartLocation).Size()); | |
NewSegment->Connections[1].TangentLen = NewSegment->Connections[0].TangentLen; | |
NewSegment->AutoFlipTangents(); | |
// set up other segment options | |
ULandscapeSplineSegment* CopyFromSegment = nullptr; | |
if (Start->ConnectedSegments.Num() > 0) | |
{ | |
CopyFromSegment = Start->ConnectedSegments[0].Segment; | |
} | |
else if (End->ConnectedSegments.Num() > 0) | |
{ | |
CopyFromSegment = End->ConnectedSegments[0].Segment; | |
} | |
else | |
{ | |
// Use defaults | |
} | |
if (CopyFromSegment != nullptr) | |
{ | |
NewSegment->LayerName = CopyFromSegment->LayerName; | |
NewSegment->SplineMeshes = CopyFromSegment->SplineMeshes; | |
NewSegment->LDMaxDrawDistance = CopyFromSegment->LDMaxDrawDistance; | |
NewSegment->bRaiseTerrain = CopyFromSegment->bRaiseTerrain; | |
NewSegment->bLowerTerrain = CopyFromSegment->bLowerTerrain; | |
NewSegment->bPlaceSplineMeshesInStreamingLevels = CopyFromSegment->bPlaceSplineMeshesInStreamingLevels; | |
NewSegment->BodyInstance = CopyFromSegment->BodyInstance; | |
NewSegment->bCastShadow = CopyFromSegment->bCastShadow; | |
NewSegment->TranslucencySortPriority = CopyFromSegment->TranslucencySortPriority; | |
NewSegment->RuntimeVirtualTextures = CopyFromSegment->RuntimeVirtualTextures; | |
NewSegment->VirtualTextureLodBias = CopyFromSegment->VirtualTextureLodBias; | |
NewSegment->VirtualTextureCullMips = CopyFromSegment->VirtualTextureCullMips; | |
NewSegment->VirtualTextureRenderPassType = CopyFromSegment->VirtualTextureRenderPassType; | |
NewSegment->bRenderCustomDepth = CopyFromSegment->bRenderCustomDepth; | |
NewSegment->CustomDepthStencilWriteMask = CopyFromSegment->CustomDepthStencilWriteMask; | |
NewSegment->CustomDepthStencilValue = CopyFromSegment->CustomDepthStencilValue; | |
} | |
Start->ConnectedSegments.Add(FLandscapeSplineConnection(NewSegment, 0)); | |
End->ConnectedSegments.Add(FLandscapeSplineConnection(NewSegment, 1)); | |
bool bUpdatedStart = false; | |
bool bUpdatedEnd = false; | |
if (bAutoRotateStart) | |
{ | |
Start->AutoCalcRotation(); | |
Start->UpdateSplinePoints(); | |
bUpdatedStart = true; | |
} | |
if (bAutoRotateEnd) | |
{ | |
End->AutoCalcRotation(); | |
End->UpdateSplinePoints(); | |
bUpdatedEnd = true; | |
} | |
// Control points' points are currently based on connected segments, so need to be updated. | |
if (!bUpdatedStart && Start->Mesh) | |
{ | |
Start->UpdateSplinePoints(); | |
} | |
if (!bUpdatedEnd && End->Mesh) | |
{ | |
End->UpdateSplinePoints(); | |
} | |
// If we've called UpdateSplinePoints on either control point it will already have called UpdateSplinePoints on the new segment | |
if (!(bUpdatedStart || bUpdatedEnd)) | |
{ | |
NewSegment->UpdateSplinePoints(); | |
} | |
} | |
// Copied mostly from <UE 5.3 Source>\Engine\Source\Editor\LandscapeEditor\Private\LandscapeEdModeSplineTools.cpp | |
void AddControlPoint(ALandscape* landscape, ULandscapeSplinesComponent* SplinesComponent, const FVector& LocalLocation) | |
{ | |
bool bCopyMeshToNewControlPoint = false; | |
bool bDuplicatingControlPoint = false; | |
bool bAutoRotateOnJoin = true; | |
SplinesComponent->Modify(); | |
ULandscapeSplineControlPoint* NewControlPoint = NewObject<ULandscapeSplineControlPoint>(SplinesComponent, NAME_None, RF_Transactional); | |
TArray<TObjectPtr<ULandscapeSplineControlPoint>>& ControlPoints = SplinesComponent->GetControlPoints(); | |
int32 ControlPointCount = ControlPoints.Num(); | |
ControlPoints.Add(NewControlPoint); | |
NewControlPoint->Location = LocalLocation; | |
if (ControlPointCount > 0) | |
{ | |
ULandscapeSplineControlPoint* PreviousPoint = ControlPoints[ControlPointCount - 1]; | |
FVector NewSegmentDirection = (NewControlPoint->Location - PreviousPoint->Location) * (PreviousPoint->ConnectedSegments.Num() == 0 || PreviousPoint->ConnectedSegments[0].End ? 1.0f : -1.0f); | |
NewControlPoint->Rotation = NewSegmentDirection.Rotation(); | |
NewControlPoint->Width = PreviousPoint->Width; | |
NewControlPoint->LayerWidthRatio = PreviousPoint->LayerWidthRatio; | |
NewControlPoint->SideFalloff = PreviousPoint->SideFalloff; | |
NewControlPoint->LeftSideFalloffFactor = PreviousPoint->LeftSideFalloffFactor; | |
NewControlPoint->RightSideFalloffFactor = PreviousPoint->RightSideFalloffFactor; | |
NewControlPoint->LeftSideLayerFalloffFactor = PreviousPoint->LeftSideLayerFalloffFactor; | |
NewControlPoint->RightSideLayerFalloffFactor = PreviousPoint->RightSideLayerFalloffFactor; | |
NewControlPoint->EndFalloff = PreviousPoint->EndFalloff; | |
if (bCopyMeshToNewControlPoint) | |
{ | |
NewControlPoint->Mesh = PreviousPoint->Mesh; | |
NewControlPoint->MeshScale = PreviousPoint->MeshScale; | |
NewControlPoint->bPlaceSplineMeshesInStreamingLevels = PreviousPoint->bPlaceSplineMeshesInStreamingLevels; | |
NewControlPoint->BodyInstance = PreviousPoint->BodyInstance; | |
NewControlPoint->bCastShadow = PreviousPoint->bCastShadow; | |
} | |
// Get the last control point and add a segment between it and the new control point | |
AddSegment(PreviousPoint, NewControlPoint, bAutoRotateOnJoin, !bDuplicatingControlPoint); | |
} | |
else | |
{ | |
// required to make control point visible | |
NewControlPoint->UpdateSplinePoints(); | |
} | |
if (!SplinesComponent->IsRegistered()) | |
{ | |
SplinesComponent->RegisterComponent(); | |
} | |
else | |
{ | |
SplinesComponent->MarkRenderStateDirty(); | |
} | |
if (landscape && landscape->GetLandscapeSplinesReservedLayer()) | |
{ | |
// TODO: Only update dirty regions | |
landscape->RequestSplineLayerUpdate(); | |
} | |
} | |
/* | |
* This function will create a landscape with spline points. | |
*/ | |
void CreateLandscapeWithSplinePoints() | |
{ | |
UWorld* world = GEditor->GetEditorWorldContext().World(); | |
int32 sectionSize = 1; | |
TArray<FVector> splinePoints; | |
splinePoints.Add(FVector(0, 0, 0)); | |
splinePoints.Add(FVector(0, 1000, 0)); | |
splinePoints.Add(FVector(1000, 1000, 0)); | |
splinePoints.Add(FVector(1000, 0, 0)); | |
ALandscape* landscape = CreateLandscape( | |
FTransform3d(), | |
63, // Section size and count | |
sectionSize, | |
8, // Component counts X and Y | |
8 | |
); | |
ULandscapeInfo* landscapeInfo = landscape->GetLandscapeInfo(); | |
uint32 worldPartitionGridSize = sectionSize; | |
// Triggers the setup of streaming proxies, except when WorldPartition is not enabled | |
world->GetSubsystem<ULandscapeSubsystem>()->ChangeGridSize(landscapeInfo, worldPartitionGridSize); | |
// Add the splines to the first landscapecomponent | |
TSet<ULandscapeComponent*> landscapeComponents; | |
landscapeInfo->GetComponentsInRegion(0, 0, 1, 1, landscapeComponents); | |
// The first landscape component | |
ULandscapeComponent* landscapeComponent = *landscapeComponents.CreateIterator(); | |
ULandscapeSplinesComponent* landscapeSplinesComponent = landscape->GetSplinesComponent(); | |
// Check if the splines component exists, if not create it | |
if (landscapeSplinesComponent == nullptr) | |
{ | |
landscape->CreateSplineComponent(); | |
landscapeSplinesComponent = landscape->GetSplinesComponent(); | |
} | |
// Create the spline points in the first landscape component | |
for (int32 i = 0; i < splinePoints.Num(); i++) | |
{ | |
AddControlPoint(landscape, landscapeSplinesComponent, splinePoints[i]); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnrealBuildTool; | |
/* | |
* | |
* Make sure you add the needed Landscape, LandscapeEditor and Foliage dependencies | |
* | |
*/ | |
public class YourLandscapeModule : ModuleRules | |
{ | |
public YourLandscapeModule(ReadOnlyTargetRules Target) : base(Target) | |
{ | |
PublicDependencyModuleNames.AddRange( | |
new string[] | |
{ | |
// ... | |
"Core", | |
"LandscapeEditor", | |
} | |
); | |
PrivateDependencyModuleNames.AddRange( | |
new string[] | |
{ | |
// ... | |
"Landscape", | |
"Foliage", | |
} | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment