Help with Spawning Mesh Along Spline in c++

Hey there I am trying to convert a blueprint that spawns meshes ( in this case a fence) along a spline.

I have a working blueprint but I would like to convert to c++ for optimization.

Spline Tool posted by anonymous | blueprintUE | PasteBin For Unreal Engine

That is my current blueprint code.

Here is my rough translation to c++

Header


#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Components/SplineComponent.h"
#include "Components/SplineMeshComponent.h"
#include "Engine/StaticMesh.h"
#include "MeshSplineTool.generated.h"

UCLASS()
class PHAROS_API AMeshSplineTool : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AMeshSplineTool();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;



			// Components //

	
	// Spline Component
	UPROPERTY(VisibleAnywhere,BlueprintReadOnly,Blueprintable,Category="Spline")
	class USplineComponent* MySplineComponent;

	// Static Mesh To Spawn Along Spline
	UPROPERTY(EditAnywhere,Blueprintable,Category="PharosSettings")
	class UStaticMesh* MeshAsset;


			// Functions //
	
	// Get Length of MeshAsset //
	UFUNCTION(BlueprintPure,Category="SplineMesh")
	float GetMeshLength();
	
	// Get Length to Loop //
	UFUNCTION(BlueprintPure,Category="SplineMesh")
	int GetLoopLength();
	
	// Spawn Mesh //
	UFUNCTION(BlueprintCallable,Category="SplineMesh")
	void SpawnMeshAlongSpline();

	UPROPERTY(BlueprintReadWrite,EditAnywhere,Category="PharosSettings")
	UMaterial *MeshAssetMaterial;

};

Source

// Fill out your copyright notice in the Description page of Project Settings.

#include "Engine/StaticMesh.h"
#include "MeshSplineTool.h"
#include "Math/UnrealMathUtility.h"
#include "Components/SplineComponent.h"
#include "Components/SplineMeshComponent.h"

// Sets default values
AMeshSplineTool::AMeshSplineTool()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

	// Create Spline Component
	MySplineComponent = CreateDefaultSubobject<USplineComponent>(TEXT("SplineComponent"));
	
	// Attach Spline Component To The Root Component
	SetRootComponent(MySplineComponent);

}


// Called when the game starts or when spawned
void AMeshSplineTool::BeginPlay()
{
	Super::BeginPlay();
	
}

// Called every frame
void AMeshSplineTool::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}


	// Get Mesh Length //
float AMeshSplineTool::GetMeshLength()
{
	if (MeshAsset)
	{
		FBoxSphereBounds Bounds = MeshAsset->GetBounds();
		return Bounds.BoxExtent.X * 2.0f;
	}
	else
	{
		return 0.0f;
	}
}

	// Get Loop Length //
int AMeshSplineTool::GetLoopLength()
{	

	float LocalMeshLength = GetMeshLength();
	float LocalSplineLength = MySplineComponent->GetSplineLength();
	
	return FMath::CeilToInt(LocalSplineLength / LocalMeshLength);
}



// Spawn Mesh Along Spline  //
void AMeshSplineTool::SpawnMeshAlongSpline()
{
	int NumberOfLoops = GetLoopLength(); // Set Length Of Loop
	float MeshSize = GetMeshLength(); // Set Mesh Size
	
	for (int i = 0; i < NumberOfLoops; ++i) // Loop 
	{
		if (i * MeshSize + MeshSize > MySplineComponent->GetSplineLength())
		{
			
			UE_LOG(LogTemp, Warning, TEXT("True"));
		}
		else
		{
			
			USplineMeshComponent* NewSplineMeshComponent = NewObject<USplineMeshComponent>(this,USplineMeshComponent::StaticClass(),TEXT("MySpline"));
			
				NewSplineMeshComponent->RegisterComponent();
				NewSplineMeshComponent->CreationMethod = EComponentCreationMethod::UserConstructionScript;
			
				FVector MStartLocation = MySplineComponent->GetLocationAtDistanceAlongSpline(i * MeshSize,ESplineCoordinateSpace::Local);
				FVector MStartTangent = MySplineComponent->GetLocationAtDistanceAlongSpline(i * MeshSize,ESplineCoordinateSpace::Local).GetClampedToSize(0.0f,MeshSize);
				FVector MEndLocation = MySplineComponent->GetLocationAtDistanceAlongSpline(i * MeshSize+MeshSize,ESplineCoordinateSpace::Local);
				FVector MEndTangent = MySplineComponent->GetLocationAtDistanceAlongSpline(i * MeshSize+MeshSize,ESplineCoordinateSpace::Local).GetClampedToSize(0.0f,MeshSize);

				NewSplineMeshComponent->SetMobility(EComponentMobility::Movable);
				NewSplineMeshComponent->SetStaticMesh(MeshAsset);
				NewSplineMeshComponent->SetMaterial(0,MeshAssetMaterial);
				NewSplineMeshComponent->SetStartAndEnd(MStartLocation,MStartTangent,MEndLocation,MEndTangent,true);
				NewSplineMeshComponent->SetCollisionEnabled(ECollisionEnabled::QueryAndProbe);
				NewSplineMeshComponent->AttachToComponent(MySplineComponent,FAttachmentTransformRules::FAttachmentTransformRules(EAttachmentRule::KeepRelative, true));
				UE_LOG(LogTemp, Warning, TEXT("False"));
	
		}
		
	}
	
	
}

When I compile this and run the function in editor it crashes the editor and in my IDE Log i see its an infinite loop out putting the test False from my loop.

How can i fix this and make it work as desired.


The blueprint code works as shown in this picture but its just heavy so wanting to convert to c++.

Any help would be great thank you.

There are a couple of pitfalls in your code that can cause problems.

There is no nullptr check for:

  • MeshAssetMaterial
  • MeshAsset

If this fails it’s a cascade of problems as LocalMeshLength will return 0

As you should know you should never divide by 0. It’s a problem waiting to happen.
Still looking further into the code to check what else can be problematic, will update with an edit.

2 Likes

I made some changes to the code but I didn’t test them, they ended up being similar to what @3dRaven said:

void AMeshSplineTool::SpawnMeshAlongSpline()
{
	const float SplineLength = MySplineComponent->GetSplineLength();
	const float MeshSize = 10.0f; // Using Blueprint as example, dont know where the value comes from.

	const int LastIndex = FMath::TruncToInt(SplineLength / MeshSize);

	UE_LOG(LogTemp, Warning, TEXT("LastIndex = %d"), LastIndex);

	for (int Index = 0; Index < LastIndex; ++Index)
	{
		if (Index * MeshSize + MeshSize <= MySplineComponent->GetSplineLength())
		{
			FVector StartPos = MySplineComponent->GetLocationAtDistanceAlongSpline(Index * MeshSize, ESplineCoordinateSpace::Local);
			FVector EndPos = MySplineComponent->GetLocationAtDistanceAlongSpline(Index * MeshSize + MeshSize, ESplineCoordinateSpace::Local);
			FVector StartTangent = StartPos.GetClampedToSize(0.0f, MeshSize);
			FVector EndTangent = EndPos.GetClampedToSize(0.0f, MeshSize);
			
			FString Name = FString::Printf(TEXT("MySpline_%d"), Index);
			
			USplineMeshComponent* Spline = NewObject<USplineMeshComponent>(this, *Name);
			
			if (Spline && MeshAsset && MeshAssetMaterial)
			{
				Spline->SetMobility(EComponentMobility::Movable);
				Spline->SetCollisionEnabled(ECollisionEnabled::QueryAndProbe);
				Spline->SetStaticMesh(MeshAsset);
				Spline->SetMaterial(0, MeshAssetMaterial);
				Spline->SetStartAndEnd(StartPos, StartTangent, EndPos, EndTangent, true);
				Spline->RegisterComponent();
				Spline->AttachToComponent(RootComponent, FAttachmentTransformRules(EAttachmentRule::KeepRelative, true));
			}
			else
			{
				UE_LOG(LogTemp, Warning, TEXT("Error creating component OR MeshAsset NULL or MeshAssetMaterial NULL"));
			}
		}
	}
}

Please, post the error too.

The updated cpp file

// Fill out your copyright notice in the Description page of Project Settings.
#include "MeshSplineTool.h"
#include "Engine/StaticMesh.h"
#include "MeshSplineTool.h"
#include "Math/UnrealMathUtility.h"
#include "Components/SplineComponent.h"
#include "Components/SplineMeshComponent.h"
#include "DrawDebugHelpers.h" 

// Sets default values
AMeshSplineTool::AMeshSplineTool()
{
	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

	// Create Spline Component
	MySplineComponent = CreateDefaultSubobject<USplineComponent>(TEXT("SplineComponent"));

	// Attach Spline Component To The Root Component
	SetRootComponent(MySplineComponent);

}


// Called when the game starts or when spawned
void AMeshSplineTool::BeginPlay()
{
	Super::BeginPlay();

}

// Called every frame
void AMeshSplineTool::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}


// Get Mesh Length //
float AMeshSplineTool::GetMeshLength()
{
	if (MeshAsset != nullptr)
	{
		FBoxSphereBounds Bounds = MeshAsset->GetBounds();		
		return Bounds.BoxExtent.X * 2.0f;
	}
	else
	{
		return 0.0f;
	}
}

// Get Loop Length //
int AMeshSplineTool::GetLoopLength()
{

	float LocalMeshLength = GetMeshLength();
	float LocalSplineLength = MySplineComponent->GetSplineLength();
	if (LocalMeshLength == 0)
		return 0;
	return FMath::CeilToInt(LocalSplineLength / LocalMeshLength);
	
}



// Spawn Mesh Along Spline  //
void AMeshSplineTool::SpawnMeshAlongSpline()
{
	int NumberOfLoops = GetLoopLength(); // Set Length Of Loop
	float MeshSize = GetMeshLength(); // Set Mesh Size
	
	
	
	for (int i = 0; i < NumberOfLoops; ++i) // Loop 
	{
		if (i * MeshSize + MeshSize > MySplineComponent->GetSplineLength())
		{

			UE_LOG(LogTemp, Warning, TEXT("True"));
		}
		else
		{

			FString SegmentName = "MySpline" + FString::SanitizeFloat(i);
			USplineMeshComponent* NewSplineMeshComponent = NewObject<USplineMeshComponent>(this, USplineMeshComponent::StaticClass(), *SegmentName);
									
			NewSplineMeshComponent->RegisterComponent();
			NewSplineMeshComponent->CreationMethod = EComponentCreationMethod::UserConstructionScript;
			NewSplineMeshComponent->SetMobility(EComponentMobility::Movable);
			NewSplineMeshComponent->AttachToComponent(MySplineComponent, FAttachmentTransformRules::FAttachmentTransformRules(EAttachmentRule::KeepRelative, true));
			

			FVector MStartLocation = MySplineComponent->GetLocationAtDistanceAlongSpline(i * MeshSize, ESplineCoordinateSpace::Local);			
			FVector MEndLocation = MySplineComponent->GetLocationAtDistanceAlongSpline((i * MeshSize) + MeshSize, ESplineCoordinateSpace::Local);
			
			FVector MStartTangent = MySplineComponent->GetTangentAtDistanceAlongSpline(i * MeshSize, ESplineCoordinateSpace::Local);
			FVector MEndTangent = MySplineComponent->GetTangentAtDistanceAlongSpline((i * MeshSize) + MeshSize, ESplineCoordinateSpace::Local);
			
			NewSplineMeshComponent->SetMobility(EComponentMobility::Movable);
			if (MeshAsset != nullptr) {
				NewSplineMeshComponent->SetStaticMesh(MeshAsset);
			}
			if (MeshAssetMaterial != nullptr) {
				NewSplineMeshComponent->SetMaterial(0, MeshAssetMaterial);
			}						
			NewSplineMeshComponent->SetStartAndEnd(MStartLocation, MStartTangent, MEndLocation, MEndTangent, true);
			NewSplineMeshComponent->SetCollisionEnabled(ECollisionEnabled::QueryAndProbe);
			
			UE_LOG(LogTemp, Warning, TEXT("False"));

		}
	}
}

Tangents can be a bit weird, but they are usually problematic from what I see on the forum.