Wondering what this is as I’ve not seen it in the engine before? Is it in there somewhere? A plugin? In return I’ll bump this post up the list
.
If you’d have to implement it yourself, that can be pretty complex. Assuming we’d convert the spline points to vertices there (just point position vectors), you could use the Procedural Mesh Component to build a vertice based mesh out of that. The tris can be calculated with Delaunay
Math in Delaunay triangulation algorithm, too many triangles appearing - #4 by Roy_Wierer.Seda145
Although I am not sure how that handles the corner part. Ages ago I was researching something in procedural interior generation. I might at some point have used a ray trace technique or “floor area” position tests.. or floodfill test to see what areas should actually be floor or “nothing” while building things procedurally. Based on complexity (like a rocket at times on architecture) try to keep things as simple as possible. Or use what you have.
Snippets:
Getting a spline point position, what is going to be the vertex:
MySplineComponent->GetWorldLocationAtSplinePoint(PointIndex);
Or over “time”. USplineComponent has a duration property from 0 to X. This is “duration” reflects somewhere between its start and end point, by default 0 is start and 1 is the end. So you could create vertices along its entire path and specify the detail level at once:
MySplineComponent->GetWorldLocationAtTime(InFloatWithinDuration);
See my Delaunay code linked for this. This generates triangle indexes for point positions.
FDelaunay Delaunay = FDelaunay();
const TArray<FIntVector> Triangles = Delaunay.Triangulate2D(Points);
This generates a mesh. Use the delaunay data.
AMyActor::AMyActor() {
ProceduralMeshComponent = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("ProceduralMeshComponent"), true);
ProceduralMeshComponent->SetupAttachment(RootComponent);
}
AMyActor::DoStuff() {
ProceduralMeshComponent->ClearAllMeshSections();
// Use the delaunay data, can probably copy paste it here on the arrays.
// TArray<FVector> MyVerts;
// TArray<int32> MyIndices;
ProceduralMeshComponent->CreateMeshSection_LinearColor(0, MyVerts, MyIndices, TArray<FVector>(), TArray<FVector2D>(), TArray<FLinearColor>(), TArray<FProcMeshTangent>(), true);
}
If you are sure tiles of a fixed size are all you need, you could also go with a floodfill and spawn static mesh (pre modeled plane of fixed size). You will probably get the best performance if using the instanced static mesh actor to spawn them.
AMyActor::AMyActor() {
RootComponent->SetMobility(EComponentMobility::Type::Static);
InstancedStaticMeshComponent = CreateDefaultSubobject<UInstancedStaticMeshComponent>(TEXT("InstancedStaticMeshComponent"), true);
InstancedStaticMeshComponent->SetupAttachment(RootComponent);
// Other optimizations
InstancedStaticMeshComponent->SetCastShadow(false);
#if WITH_EDITORONLY_DATA
UBlueprint* Blueprint = Cast<UBlueprint>(GetClass()->ClassGeneratedBy);
if (IsValid(Blueprint)) {
Blueprint->bRunConstructionScriptOnDrag = false;
}
#endif // WITH_EDITORONLY_DATA
//InstancedStaticMeshComponent->SetCollisionEnabled(ECollisionEnabled::Type::NoCollision);
InstancedStaticMeshComponent->SetCanEverAffectNavigation(false);
PrimaryActorTick.bCanEverTick = false;
PrimaryActorTick.bStartWithTickEnabled = false;
}
void AMyActor::DoStuff() {
// Clean
InstancedStaticMeshComponent->SetStaticMesh(nullptr);
InstancedStaticMeshComponent->ClearInstances();
// build
InstancedStaticMeshComponent->SetStaticMesh(TileMesh);
// optimize
InstancedStaticMeshComponent->SetCullDistances(StartCullDistance, EndCullDistance);
}
A floodfill test is simple to program, there are many variants as well (find them and the algo on wikipedia), but optimally you set up some utilities to work with it like I’ve done. The grid argument on this method provides spaces that are accessable (and those that are not), basically your floor design surface fit within a square grid of 0 and 1 cells.
TArray<FS_FloodFillArea> UFloodFillUtils::GetAllAreasXY_FourWay(const UGrid* InGrid) {
TArray<FIntPoint> Flooded;
TArray<FS_FloodFillArea> Areas;
if (!IsValid(InGrid)) {
UE_LOG(LogAlgorithmsPlugin, Error, TEXT("Invalid InGrid."));
return Areas;
}
for(int x = 0; x < InGrid->GetSize().X; x++) {
for(int y = 0; y < InGrid->GetSize().Y; y++) {
/*
If cell at(x, y) is a passage, and it has not already been flooded (i.e.is not in a passage that's already been found)
, Then find the passage it's connected to.
*/
if (InGrid->HasPassage(FIntVector(x, y, 0)) && !Flooded.Contains(FIntPoint(x, y))) {
FS_FloodFillArea Area;
Area.Container = GetAreaAtXY_FourWay(InGrid, FIntPoint(x, y));
// If there is a passage at (x, y), then update flooded list and add to passages list.
if(Area.Container.Num() > 0) {
Flooded += Area.Container;
Areas.Add(Area);
}
}
}
}
return Areas;
}
I hope that those examples gave some insight in the possibilities, but of course there are plenty of ways to do things. I can’t tell you what behavior your spline tool is supposed to have (I do not recognize it), but perhaps more important is to know what results you want regardless of tools used. And I expect this is something architecture like, who should be able to edit it (some techniques run great as scripts or tools, but people not used to them can’t use them.)