Download

Trouble implementing SplineMeshComponents

So I had a simple blueprint to produce rails, pipes, etc using a Spline and SplineMeshComponents which I decided to convert to C++ (mainly to help me learn UE4 C++), anyway it has not been going well and after three days working on it I’m close to giving up, The main issue I have is the mesh keeps vanishing every time I manipulate the spline, I’ve tried recreating it on every OnConstruction event call but for whatever reason nothing seems to work, one idea I have is it might not be destroying the previous SplineMeshComponent objects correctly since the object name increments each time it’s called, but at this point I have no clear solution.

Header


#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include <Components/SplineComponent.h>
#include <Components/SplineMeshComponent.h>
#include <Components/SceneComponent.h>
#include <Materials/MaterialInstance.h>
#include <Components/StaticMeshComponent.h>
#include <Engine/StaticMesh.h>
#include <Containers/Array.h>
#include "Chryseus_SplineMesh.generated.h"

UCLASS()
class GAME_API AChryseus_SplineMesh : public AActor
{
    GENERATED_BODY()

public:    
    AChryseus_SplineMesh();

protected:
    virtual void BeginPlay() override;
    virtual void OnConstruction(const FTransform &Transform) override;
    virtual void PostEditChangeProperty(struct FPropertyChangedEvent &PropertyChangedEvent) override;
    virtual void PostEditMove(bool bFinished) override;

public:    
    virtual void Tick(float DeltaTime) override;
    void createSplineMesh();
    void updateSplineMesh();

    USceneComponent* C_Scene;
    UPROPERTY()
    USplineComponent* C_Spline;
    UPROPERTY()
    TArray<USplineMeshComponent *> A_SplineMesh;

    UPROPERTY(EditAnywhere, Category = Mesh)
    UStaticMesh *SingleMesh;
    UPROPERTY(EditAnywhere, Category = Material)
    UMaterial *Material;
    bool *created;
    int32 *numPoints;
};

Source


#include "Chryseus_SplineMesh.h"

// Sets default values
AChryseus_SplineMesh::AChryseus_SplineMesh()
{
    UE_LOG(LogTemp, Display, TEXT("AChryseus_SplineMesh Constructor."));
    UStaticMesh *useMesh = nullptr;
    // Create subobjects

    C_Spline = CreateDefaultSubobject<USplineComponent>(TEXT("Spline"));
    C_Scene = CreateDefaultSubobject<USceneComponent>(TEXT("SceneRoot"));
    //SingleMesh = CreateDefaultSubobject<UStaticMesh>(TEXT("Single Mesh"));
    Material = CreateDefaultSubobject<UMaterial>(TEXT("Override Material"));
    // Set root component
    RootComponent = C_Scene;
    // Attach spline to root
    C_Spline->SetupAttachment(RootComponent);
    // Set Mobility
    RootComponent->SetMobility(EComponentMobility::Type::Movable);
    C_Spline->SetMobility(EComponentMobility::Type::Movable);
    //Setup USplineMeshComponent array
    A_SplineMesh.Empty();
    created = new bool;
    *created = false;
    numPoints = new int32;

    // Enable ticking
    PrimaryActorTick.bCanEverTick = true;
}

void AChryseus_SplineMesh::OnConstruction(const FTransform& Transform)
{
    if (!created)
    {
        this->createSplineMesh();
    }
    else
    {
        UE_LOG(LogTemp, Display, TEXT("Updating"));
        this->updateSplineMesh();
    }
}

void AChryseus_SplineMesh::createSplineMesh()
{
    FVector locStart;
    FVector tanStart;
    FVector locEnd;
    FVector tanEnd;

    // Clean up stale mesh components
    if (A_SplineMesh.Num() > 0)
    {
        for (int32 i = 0; i < A_SplineMesh.Num(); i++)
        {
            if (A_SplineMesh*)
            {
                A_SplineMesh*->DetachFromParent();
                A_SplineMesh*->DestroyComponent();
            }
        }
        A_SplineMesh.Empty();
    }

    *numPoints = C_Spline->GetNumberOfSplinePoints(); // Store number of spline points
    for (int32 i = 0; i < C_Spline->GetNumberOfSplinePoints() -1; i++)
    {
        USplineMeshComponent *splineMesh = NewObject<USplineMeshComponent>(this);
        UE_LOG(LogTemp, Display, TEXT("Spline Points: %d"), *numPoints);
        UE_LOG(LogTemp, Display, TEXT("Added new USplineMeshComponent: %s"), *splineMesh->GetName());
        splineMesh->CreationMethod = EComponentCreationMethod::UserConstructionScript;
        C_Spline->GetLocationAndTangentAtSplinePoint(i, locStart, tanStart, ESplineCoordinateSpace::Local);
        C_Spline->GetLocationAndTangentAtSplinePoint(i+1, locEnd, tanEnd, ESplineCoordinateSpace::Local);
        UE_LOG(LogTemp, Display, TEXT("Start location and tangent: %s / %s"), *locStart.ToCompactString(), *tanStart.ToCompactString());
        UE_LOG(LogTemp, Display, TEXT("End location and tangent: %s / %s"), *locEnd.ToCompactString(), *tanEnd.ToCompactString());
        splineMesh->SetMobility(EComponentMobility::Type::Movable);
        splineMesh->SetForwardAxis(ESplineMeshAxis::X);
        splineMesh->SetStaticMesh(SingleMesh);
        splineMesh->SetMaterial(0, Material);
        splineMesh->SetStartAndEnd(locStart, tanStart, locEnd, tanEnd);
        splineMesh->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
        splineMesh->AttachToComponent(C_Spline, FAttachmentTransformRules::FAttachmentTransformRules(EAttachmentRule::KeepRelative, true));
        A_SplineMesh.Add(splineMesh);
    }
    *created = true;
}

void AChryseus_SplineMesh::updateSplineMesh()
{
    // Try recreate the mesh instead of updating
    *created = false;
    createSplineMesh();
    return;

    FVector locStart;
    FVector tanStart;
    FVector locEnd;
    FVector tanEnd;
    if (C_Spline->GetNumberOfSplinePoints() != *numPoints)
    {
        // Spline points have been changed
        UE_LOG(LogTemp, Display, TEXT("Recreating spline mesh"));
        *created = false;
        createSplineMesh();
        return;
    }
    else
    {
        for (int32 i = 0; i < A_SplineMesh.Num(); i++)
        {
            // Update spline meshes
            if (!A_SplineMesh*) { UE_LOG(LogTemp, Display, TEXT("Null spline mesh!")); *created = false; createSplineMesh(); return; }
            C_Spline->GetLocationAndTangentAtSplinePoint(i, locStart, tanStart, ESplineCoordinateSpace::Local);
            C_Spline->GetLocationAndTangentAtSplinePoint(i+1, locEnd, tanEnd, ESplineCoordinateSpace::Local);
            UE_LOG(LogTemp, Display, TEXT("Start location and tangent: %s / %s"), *locStart.ToCompactString(), *tanStart.ToCompactString());
            UE_LOG(LogTemp, Display, TEXT("End location and tangent: %s / %s"), *locEnd.ToCompactString(), *tanEnd.ToCompactString());
            A_SplineMesh*->SetMobility(EComponentMobility::Type::Movable);
            A_SplineMesh*->SetForwardAxis(ESplineMeshAxis::X);
            UE_LOG(LogTemp, Display, TEXT("Static mesh is %s"), *SingleMesh->GetPathName());
            A_SplineMesh*->SetStaticMesh(SingleMesh);
            A_SplineMesh*->SetMaterial(0, Material);
            A_SplineMesh*->SetStartAndEnd(locStart, tanStart, locEnd, tanEnd);
            A_SplineMesh*->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
        }
    }
}

void AChryseus_SplineMesh::PostEditChangeProperty(struct FPropertyChangedEvent &PropertyChangedEvent)
{
    Super::PostEditChangeProperty(PropertyChangedEvent);
}

void AChryseus_SplineMesh::PostEditMove(bool bFinished)
{
    Super::PostEditMove(bFinished);
}

void AChryseus_SplineMesh::BeginPlay()
{
    Super::BeginPlay();
}


void AChryseus_SplineMesh::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
}

I’ve managed to get it working finally, the main issue was that I did not call RegisterComponent() on each SplineMeshComponent, Not sure why it shows them once then vanishes, perhaps automated registration the first time, also every time OnConstruction is called the SplineMeshComponents are marked for garbage collection so you have to remake the objects every time rather than try to update them, the documentation is really unhelpful when it comes to these small but important details.

Thank you for this information, this is the reason my spline mesh was not working in cpp.