Unreal Engine tire mark, tread mark tutorial

In Tank Brawl 2, we want tanks to leave track marks, jeeps leave tire marks on the ground. We looked at using the particle system ( include the ribbon module) to do it but it was difficult to make the track mark look continues and good. So we developed our own system where we would spawn polygons in real time as the track move.

To use this all you need to do is copy & paste UTrailMarkComponent’s cpp and h file into your game’s c++ project and updating it’s m_trailAlpha to turn on and off the track mark when required ( e.g when vehicle is in air, we don’t want the track mark visible ). You can also tweak m_numQuad, m_quadLength, m_quadWidth, m_textureLength to match your need as explained below.

The UTrailMarkComponent is attached to the tank’s track. When the component moves, we will update its custom vertex buffer in a way that will lay out the track mark on the ground like this:

UTrailMarkComponent derived from UMeshComponent, which allowing us to create and modify our own procedural mesh and render them on screen. As Unreal Engine only allowing update the whole vertex buffer one at a time ( no partial update ), The UTrailMarkComponent works by first create a full vertex buffer and index buffer of m_numQuad number of quads. Look inside UTrailMarkComponent::Initialise(int32 NumQuad) to see how we setup the vertex and index buffer. Initially all those vertices have its alpha values = 0, so nothing is visible. At first when the track mark is first visible, we update the first quad alpha to be 1, and put its first 2 vertices at the current track’s location and when UTrailMarkComponent moved to a new position, we update the 2 vertices at the end of the current quad to match the new position ( Look inside UTrailMarkComponent::calculateVertices for detail ). If the current quad’s length becomes longer m_quadLength, we will stop updating this quad and leave it unchanged and move to the next quad and continue this process. When we run out of quads to use, we go back to the begining and reuse the first quad ( so the very earliest track mark will disappear once the whole track mark becomes too long ).You can look at UTrailMarkComponent::updateTrail function to see this process in detail.

UTrailMarkComponent::calculateFirstVertices. This function calculate the position and uv of the first 2 vertices of the quad that start the trail ( i.e the first quad of the track mark or the first quad after vehicle is in air and land on ground ). The uv of those 2 vertices is 0, so the track texture start from begining when a new continues track trail starts.

UTrailMarkComponent::calculateVertices: Calculate position and uv for current’s quad last 2 vertices, the uv of these 2 will depends on the current distance from the track mark start.

Below are the the .h and .cpp file for the UTrailMarkComponent that can just copy & paste into your game’s c++ project and start using it straight away. All you need to do is updating it’s m_trailAlpha to turn on and off the track mark when required ( e.g when vehicle is in air, we don’t want the track mark visible ). A majority of the code in UTrailMarkComponent is to deal with setting up the dynamic vertex buffer, that we copied from Unreal Engine’s CustomMeshComponent: These includes:

class FCustomMeshSceneProxy : public FPrimitiveSceneProxy.
class FCustomMeshVertexFactory : public FLocalVertexFactory
class FCustomMeshIndexBuffer : public FIndexBuffer
class FCustomMeshVertexBuffer : public FVertexBuffer

TrailMarkComponent.h



#pragma once

#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "Interfaces/Interface_CollisionDataProvider.h"
#include "Components/MeshComponent.h"
#include "PhysicsEngine/ConvexElem.h"
#include "TrailMarkComponent.generated.h"

class FPrimitiveSceneProxy;

/**
*	Struct used to specify a tangent vector for a vertex
*	The Y tangent is computed from the cross product of the vertex normal (Tangent Z) and the TangentX member.
*/
USTRUCT(BlueprintType)
struct FTrailMarkTangent
{
	GENERATED_USTRUCT_BODY()

	/** Direction of X tangent for this vertex */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Tangent)
	FVector TangentX;
	
	FTrailMarkTangent()
		: TangentX(1.f, 0.f, 0.f)
	{}

	FTrailMarkTangent(float X, float Y, float Z)
		: TangentX(X, Y, Z)
	{}

	FTrailMarkTangent(FVector InTangentX, bool bInFlipTangentY)
		: TangentX(InTangentX)
	{}
};

/** One vertex for the mesh, used for storing data internally */
USTRUCT(BlueprintType)
struct FTrailMarkVertex
{
	GENERATED_USTRUCT_BODY()

	/** Vertex position */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Vertex)
	FVector Position;

	/** Vertex normal */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Vertex)
	FVector Normal;

	/** Vertex tangent */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Vertex)
	FTrailMarkTangent Tangent;

	/** Vertex color */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Vertex)
	FColor Color;

	/** Vertex texture co-ordinate */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Vertex)
	FVector2D UV0;


	FTrailMarkVertex()
		: Position(0.f, 0.f, 0.f)
		, Normal(0.f, 0.f, 1.f)
		, Tangent(FVector(1.f, 0.f, 0.f), false)
		, Color(255, 255, 255)
		, UV0(0.f, 0.f)
	{}
};
/**
*	Component that allows you to specify custom triangle mesh geometry
*	Beware! This feature is experimental and may be substantially changed in future releases.
*/
UCLASS(hidecategories = (Object, LOD), meta = (BlueprintSpawnableComponent), ClassGroup = Rendering)
class ARM_API UTrailMarkComponent : public UMeshComponent, public IInterface_CollisionDataProvider
{
	GENERATED_UCLASS_BODY()
public:
	
		
	virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override;

	virtual FMatrix GetRenderMatrix() const override;

	
	//~ Begin UPrimitiveComponent Interface.
	virtual FPrimitiveSceneProxy* CreateSceneProxy() override;
	//~ End UPrimitiveComponent Interface.

	//~ Begin UMeshComponent Interface.
	virtual int32 GetNumMaterials() const override;
	//~ End UMeshComponent Interface.

	//~ Begin UObject Interface
	virtual void PostLoad() override;
	//~ End UObject Interface.


	UFUNCTION(BlueprintCallable, Category = "Arm", meta = (AutoCreateRefTerm = "Normals, UV0, VertexColors, Tangents"))
	void UpdateMesh(int32 startIndex, const TArray& Vertices, const TArray& Normals, const TArray& UV0, const TArray& VertexColors, const TArray& Tangents, bool pushToRenderThread);

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Arm")
	float m_numQuad = 20;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Arm")
	float m_quadLength = 150;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Arm")
	float m_quadWidth = 150;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Arm")
	float m_textureLength = 1500;

	float m_trailAlpha = 1.0f;
private:
	void Initialise(int NumQuad);
	void updateTrail(float DeltaTime, const FVector &pos, float alpha);

	//~ Begin USceneComponent Interface.
	virtual FBoxSphereBounds CalcBounds(const FTransform& LocalToWorld) const override;
	//~ Begin USceneComponent Interface.

	friend class FTrailMarkComponentSceneProxy;


	/** Vertex buffer for this section */
	UPROPERTY()
	TArray ProcVertexBuffer;

	/** Index buffer for this section */
	UPROPERTY()
	TArray ProcIndexBuffer;	

	/** Reset this section, clear all mesh info. */
	void Reset()
	{
		ProcVertexBuffer.Empty();
		ProcIndexBuffer.Empty();
	}

	void calculateFirstVertices(const FVector &pos, const FVector &dir, TArray& vertices, TArray& normals, TArray& uv0, TArray& vertexColors) const;
	void calculateVertices(const FVector &pos, const FVector &lastPos, float dist, float alpha, TArray& vertices, TArray& normals, TArray& uv0, TArray& vertexColors) const;
	
	// Trail update working
	enum State
	{
		None,
		First,
		Working
	};
	State m_state = None;
	float m_dist = 0;
	FVector m_lastPos;
	float m_alpha = 0;
	int m_currentVertIndex = 0;
};

TrailMarkComponent.cpp


#include "TrailMarkComponent.h"
#include "PrimitiveViewRelevance.h"
#include "RenderResource.h"
#include "RenderingThread.h"
#include "PrimitiveSceneProxy.h"
#include "Containers/ResourceArray.h"
#include "EngineGlobals.h"
#include "VertexFactory.h"
#include "MaterialShared.h"
#include "Materials/Material.h"
#include "LocalVertexFactory.h"
#include "Engine/Engine.h"
#include "SceneManagement.h"
#include "PhysicsEngine/BodySetup.h"
#include "DynamicMeshBuilder.h"
#include "PhysicsEngine/PhysicsSettings.h"

/*

DECLARE_CYCLE_STAT(TEXT("Create TrailMark Proxy"), STAT_TrailMark_CreateSceneProxy, STATGROUP_TrailMarkComponent);
DECLARE_CYCLE_STAT(TEXT("Create Mesh Section"), STAT_TrailMark_CreateMeshSection, STATGROUP_TrailMarkComponent);
DECLARE_CYCLE_STAT(TEXT("UpdateSection GT"), STAT_TrailMark_UpdateSectionGT, STATGROUP_TrailMarkComponent);
DECLARE_CYCLE_STAT(TEXT("UpdateSection RT"), STAT_TrailMark_UpdateSectionRT, STATGROUP_TrailMarkComponent);
DECLARE_CYCLE_STAT(TEXT("Get TrailMark Elements"), STAT_TrailMark_GetMeshElements, STATGROUP_TrailMarkComponent);
DECLARE_CYCLE_STAT(TEXT("Update Collision"), STAT_TrailMark_UpdateCollision, STATGROUP_TrailMarkComponent);
*/

/** Resource array to pass  */
class FTrailMarkVertexResourceArray : public FResourceArrayInterface
{
public:
	FTrailMarkVertexResourceArray(void* InData, uint32 InSize)
		: Data(InData)
		, Size(InSize)
	{
	}

	virtual const void* GetResourceData() const override { return Data; }
	virtual uint32 GetResourceDataSize() const override { return Size; }
	virtual void Discard() override { }
	virtual bool IsStatic() const override { return false; }
	virtual bool GetAllowCPUAccess() const override { return false; }
	virtual void SetAllowCPUAccess(bool bInNeedsCPUAccess) override { }

private:
	void* Data;
	uint32 Size;
};

/** Vertex Buffer */
class FTrailMarkVertexBuffer : public FVertexBuffer
{
public:
	TArray Vertices;

	virtual void InitRHI() override
	{
		const uint32 SizeInBytes = Vertices.Num() * sizeof(FDynamicMeshVertex);

		FTrailMarkVertexResourceArray ResourceArray(Vertices.GetData(), SizeInBytes);
		FRHIResourceCreateInfo CreateInfo(&ResourceArray);
		VertexBufferRHI = RHICreateVertexBuffer(SizeInBytes, BUF_Static, CreateInfo);
	}

};

/** Index Buffer */
class FTrailMarkIndexBuffer : public FIndexBuffer
{
public:
	TArray Indices;

	virtual void InitRHI() override
	{
		FRHIResourceCreateInfo CreateInfo;
		void* Buffer = nullptr;
		IndexBufferRHI = RHICreateAndLockIndexBuffer(sizeof(int32), Indices.Num() * sizeof(int32), BUF_Static, CreateInfo, Buffer);

		// Write the indices to the index buffer.		
		FMemory::Memcpy(Buffer, Indices.GetData(), Indices.Num() * sizeof(int32));
		RHIUnlockIndexBuffer(IndexBufferRHI);
	}
};

/** Vertex Factory */
class FTrailMarkVertexFactory : public FLocalVertexFactory
{
public:

	FTrailMarkVertexFactory()
	{}

	/** Init function that should only be called on render thread. */
	void Init_RenderThread(const FTrailMarkVertexBuffer* VertexBuffer)
	{
		check(IsInRenderingThread());

		// Initialize the vertex factory's stream components.
		FDataType NewData;
		NewData.PositionComponent = STRUCTMEMBER_VERTEXSTREAMCOMPONENT(VertexBuffer, FDynamicMeshVertex, Position, VET_Float3);
		NewData.TextureCoordinates.Add(
			FVertexStreamComponent(VertexBuffer, STRUCT_OFFSET(FDynamicMeshVertex, TextureCoordinate), sizeof(FDynamicMeshVertex), VET_Float2)
		);
		NewData.TangentBasisComponents[0] = STRUCTMEMBER_VERTEXSTREAMCOMPONENT(VertexBuffer, FDynamicMeshVertex, TangentX, VET_PackedNormal);
		NewData.TangentBasisComponents[1] = STRUCTMEMBER_VERTEXSTREAMCOMPONENT(VertexBuffer, FDynamicMeshVertex, TangentZ, VET_PackedNormal);
		NewData.ColorComponent = STRUCTMEMBER_VERTEXSTREAMCOMPONENT(VertexBuffer, FDynamicMeshVertex, Color, VET_Color);
		SetData(NewData);
	}

	/** Init function that can be called on any thread, and will do the right thing (enqueue command if called on main thread) */
	void Init(const FTrailMarkVertexBuffer* VertexBuffer)
	{
		if (IsInRenderingThread())
		{
			Init_RenderThread(VertexBuffer);
		}
		else
		{
			ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER(
				InitTrailMarkVertexFactory,
				FTrailMarkVertexFactory*, VertexFactory, this,
				const FTrailMarkVertexBuffer*, VertexBuffer, VertexBuffer,
				{
					VertexFactory->Init_RenderThread(VertexBuffer);
				});
		}
	}
};

/**
*	Struct used to send update to mesh data
*	Arrays may be empty, in which case no update is performed.
*/
class FTrailMarkSectionUpdateData
{
public:
	int32 m_startIndex;

	/** New vertex information */
	TArray NewVertexBuffer;
};

static void ConvertTrailMarkToDynMeshVertex(FDynamicMeshVertex& Vert, const FTrailMarkVertex& ProcVert)
{
	Vert.Position = ProcVert.Position;
	Vert.Color = ProcVert.Color;
	Vert.TextureCoordinate = ProcVert.UV0;
	Vert.TangentX = ProcVert.Tangent.TangentX;
	Vert.TangentZ = ProcVert.Normal;
	Vert.TangentZ.Vector.W = 0;
}

/** mesh scene proxy */
class FTrailMarkComponentSceneProxy : public FPrimitiveSceneProxy
{
public:


	FTrailMarkComponentSceneProxy(UTrailMarkComponent* Component)
		: FPrimitiveSceneProxy(Component)
		, MaterialRelevance(Component->GetMaterialRelevance(GetScene().GetFeatureLevel()))
	{
		if (Component->ProcIndexBuffer.Num() > 0 && Component->ProcVertexBuffer.Num() > 0)
		{			
			// Copy data from vertex buffer
			const int32 NumVerts = Component->ProcVertexBuffer.Num();

			// Allocate verts
			VertexBuffer.Vertices.SetNumUninitialized(NumVerts);

			// Copy verts
			for (int VertIdx = 0; VertIdx < NumVerts; VertIdx++) { const FTrailMarkVertex& ProcVert = Component->ProcVertexBuffer[VertIdx];
				FDynamicMeshVertex& Vert = VertexBuffer.Vertices[VertIdx];
				ConvertTrailMarkToDynMeshVertex(Vert, ProcVert);
			}

			// Copy index buffer
			IndexBuffer.Indices = Component->ProcIndexBuffer;

			// Init vertex factory
			VertexFactory.Init(&VertexBuffer);

			// Enqueue initialization of render resource
			BeginInitResource(&VertexBuffer);
			BeginInitResource(&IndexBuffer);
			BeginInitResource(&VertexFactory);

			// Grab material
			Material = Component->GetMaterial(0);
			if (Material == NULL)
			{
				Material = UMaterial::GetDefaultMaterial(MD_Surface);
			}
		}
	}

	virtual ~FTrailMarkComponentSceneProxy()
	{
		VertexBuffer.ReleaseResource();
		IndexBuffer.ReleaseResource();
		VertexFactory.ReleaseResource();
	}

	/** Called on render thread to assign new dynamic data */
	void UpdateSection_RenderThread(FTrailMarkSectionUpdateData* SectionData)
	{
		//SCOPE_CYCLE_COUNTER(STAT_TrailMark_UpdateSectionRT);

		check(IsInRenderingThread());

		// Check we have data 
		if (SectionData != nullptr && VertexBuffer.Vertices.Num() > 0)
		{			
			
			// Lock vertex buffer
			const int32 NumVerts = SectionData->NewVertexBuffer.Num();
			FDynamicMeshVertex* VertexBufferData = (FDynamicMeshVertex*)RHILockVertexBuffer(VertexBuffer.VertexBufferRHI, 0, NumVerts * sizeof(FDynamicMeshVertex), RLM_WriteOnly);
			//FDynamicMeshVertex* VertexBufferData = (FDynamicMeshVertex*)RHILockVertexBuffer(VertexBuffer.VertexBufferRHI, SectionData->m_startIndex, NumVerts * sizeof(FDynamicMeshVertex), RLM_WriteOnly);

			// Iterate through vertex data, copying in new info
			for (int32 VertIdx = 0; VertIdx<NumVerts; VertIdx++) { const FTrailMarkVertex& ProcVert = SectionData->NewVertexBuffer[VertIdx];
				FDynamicMeshVertex& Vert = VertexBufferData[VertIdx];
				ConvertTrailMarkToDynMeshVertex(Vert, ProcVert);
			}

			// Unlock vertex buffer
			RHIUnlockVertexBuffer(VertexBuffer.VertexBufferRHI);
			
			// Free data sent from game thread
			delete SectionData;
		}
	}
	
	virtual void GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override
	{
		//SCOPE_CYCLE_COUNTER(STAT_TrailMark_GetMeshElements);
		
		if (IndexBuffer.Indices.Num() <= 0) return; // Set up wireframe material (if needed) const bool bWireframe = AllowDebugViewmodes() && ViewFamily.EngineShowFlags.Wireframe; FColoredMaterialRenderProxy* WireframeMaterialInstance = NULL; if (bWireframe) { WireframeMaterialInstance = new FColoredMaterialRenderProxy( GEngine->WireframeMaterial ? GEngine->WireframeMaterial->GetRenderProxy(IsSelected()) : NULL,
				FLinearColor(0, 0.5f, 1.f)
			);

			Collector.RegisterOneFrameMaterialProxy(WireframeMaterialInstance);
		}

		// Iterate over sections
		
		FMaterialRenderProxy* MaterialProxy = bWireframe ? WireframeMaterialInstance : Material->GetRenderProxy(IsSelected());

		// For each view..
		for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
		{
			if (VisibilityMap & (1 << ViewIndex))
			{
				const FSceneView* View = Views[ViewIndex];
				// Draw the mesh.
				FMeshBatch& Mesh = Collector.AllocateMesh();				
				
				FMeshBatchElement& BatchElement = Mesh.Elements[0];
				BatchElement.PrimitiveUniformBuffer = CreatePrimitiveUniformBufferImmediate(GetLocalToWorld(), GetBounds(), GetLocalBounds(), true, UseEditorDepthTest());
				BatchElement.IndexBuffer = &IndexBuffer;
				BatchElement.FirstIndex = 0;
				BatchElement.NumPrimitives = IndexBuffer.Indices.Num() / 3;
				BatchElement.MinVertexIndex = 0;
				BatchElement.MaxVertexIndex = VertexBuffer.Vertices.Num() - 1;
				
				Mesh.bWireframe = bWireframe;
				Mesh.VertexFactory = &VertexFactory;
				Mesh.MaterialRenderProxy = MaterialProxy;
				
				Mesh.ReverseCulling = IsLocalToWorldDeterminantNegative();
				Mesh.Type = PT_TriangleList;
				Mesh.DepthPriorityGroup = SDPG_World;
				Mesh.bCanApplyViewModeOverrides = false;
				Collector.AddMesh(ViewIndex, Mesh);
			}
		}
	}

	virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const
	{
		FPrimitiveViewRelevance Result;
		Result.bDrawRelevance = IsShown(View);
		Result.bShadowRelevance = IsShadowCast(View);
		Result.bDynamicRelevance = true;
		Result.bRenderInMainPass = ShouldRenderInMainPass();
		Result.bUsesLightingChannels = GetLightingChannelMask() != GetDefaultLightingChannelMask();
		Result.bRenderCustomDepth = ShouldRenderCustomDepth();
		MaterialRelevance.SetPrimitiveViewRelevance(Result);
		return Result;
	}

	virtual bool CanBeOccluded() const override
	{
		return !MaterialRelevance.bDisableDepthTest;
	}

	virtual uint32 GetMemoryFootprint(void) const
	{
		return(sizeof(*this) + GetAllocatedSize());
	}

	uint32 GetAllocatedSize(void) const
	{
		return(FPrimitiveSceneProxy::GetAllocatedSize());
	}

private:

	UBodySetup* BodySetup;

	FMaterialRelevance MaterialRelevance;

	/** Material applied to this section */
	UMaterialInterface* Material;
	/** Vertex buffer for this section */
	FTrailMarkVertexBuffer VertexBuffer;
	/** Index buffer for this section */
	FTrailMarkIndexBuffer IndexBuffer;
	/** Vertex factory for this section */
	FTrailMarkVertexFactory VertexFactory;

};

//////////////////////////////////////////////////////////////////////////


UTrailMarkComponent::UTrailMarkComponent(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	PrimaryComponentTick.TickGroup = TG_PrePhysics;
	PrimaryComponentTick.bCanEverTick = true;
	PrimaryComponentTick.bStartWithTickEnabled = true;
	SetComponentTickEnabled(true);
}

void UTrailMarkComponent::PostLoad()
{
	Super::PostLoad();

	Initialise(m_numQuad);
}


void UTrailMarkComponent::Initialise(int32 NumQuad)
{
	//SCOPE_CYCLE_COUNTER(STAT_TrailMark_CreateMeshSection);

	Reset();

	// Copy data to vertex buffer
	ProcVertexBuffer.Reset();
	ProcVertexBuffer.AddUninitialized(NumQuad * 4);
	for (int32 VertIdx = 0; VertIdx < ProcVertexBuffer.Num(); VertIdx++)
	{
		FTrailMarkVertex& Vertex = ProcVertexBuffer[VertIdx];

		Vertex.Position = FVector::ZeroVector;
		Vertex.Normal = FVector::UpVector;
		Vertex.UV0 = FVector2D(0.f, 0.f);
		Vertex.Color = FColor(255, 255, 255);
		Vertex.Tangent = FTrailMarkTangent();
	}

	// Copy index buffer (clamping to vertex range)
	ProcIndexBuffer.Reset();
	ProcIndexBuffer.AddUninitialized(NumQuad * 6);
	for (int32 quad = 0; quad < NumQuad; ++quad) { ProcIndexBuffer[quad * 6 + 0] = quad * 4 + 0; ProcIndexBuffer[quad * 6 + 1] = quad * 4 + 1; ProcIndexBuffer[quad * 6 + 2] = quad * 4 + 2; ProcIndexBuffer[quad * 6 + 3] = quad * 4 + 2; ProcIndexBuffer[quad * 6 + 4] = quad * 4 + 1; ProcIndexBuffer[quad * 6 + 5] = quad * 4 + 3; } MarkRenderStateDirty(); // New section requires recreating scene proxy } void UTrailMarkComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); updateTrail(DeltaTime, GetComponentLocation(), m_trailAlpha); } FMatrix UTrailMarkComponent::GetRenderMatrix() const { return FMatrix::Identity; } void UTrailMarkComponent::updateTrail(float DeltaTime, const FVector &pos, float alpha) { static float blendSpeed = 10; m_alpha = FMath::FInterpConstantTo(m_alpha, alpha, DeltaTime, blendSpeed); switch (m_state) { case None: if (m_alpha >= 0.001f)
		{
			m_state = First;
			m_lastPos = pos;
		}
		break;

	case First:
		if (m_alpha < 0.001f) { m_state = None; } else { FVector dir = pos - m_lastPos; if (dir.SizeSquared() > 0.001f)
			{
				if (dir.SizeSquared() < 5000 * 5000)
				{
					TArray vertices;
					TArray normals;
					TArray uv0;
					TArray vertexColors;
					vertices.SetNumUninitialized(2);
					normals.SetNumUninitialized(2);
					uv0.SetNumUninitialized(2);
					vertexColors.SetNumUninitialized(2);

					dir.Normalize();
					calculateFirstVertices(m_lastPos, dir, vertices, normals, uv0, vertexColors);
					UpdateMesh(m_currentVertIndex, vertices, normals, uv0, vertexColors, TArray(), false);
					m_state = Working;
					m_dist = 0;
				}
				m_lastPos = pos;
			}

		}
		break;

	case Working:
		if (m_alpha < 0.001f) { m_state = None; } else { float distToLastPos = FVector::Dist(pos, m_lastPos); if (distToLastPos > 0.001f)
			{
				TArray vertices;
				TArray normals;
				TArray uv0;
				TArray vertexColors;
				vertices.SetNumUninitialized(2);
				normals.SetNumUninitialized(2);
				uv0.SetNumUninitialized(2);
				vertexColors.SetNumUninitialized(2);

				calculateVertices(pos, m_lastPos, m_dist + distToLastPos, m_alpha, vertices, normals, uv0, vertexColors);
				UpdateMesh(m_currentVertIndex + 2, vertices, normals, uv0, vertexColors, TArray(), true);
				
				// Add new quad or not
				{
					static float minDist = 0.1;
					static float maxDist = 1;
					static float maxTime = 1;

					if (distToLastPos >= m_quadLength)
					{
						m_currentVertIndex += 4;
						m_currentVertIndex = m_currentVertIndex % ProcVertexBuffer.Num();
						UpdateMesh(m_currentVertIndex, vertices, normals, uv0, vertexColors, TArray(), false);
						m_dist += distToLastPos;
						m_lastPos = pos;
					}
				}
			}
		}
		break;
	}

}

void UTrailMarkComponent::UpdateMesh(int32 startIndex, const TArray& Vertices, const TArray& Normals, const TArray& UV0, const TArray& VertexColors, const TArray& Tangents, bool pushToRenderThread)
{
	//SCOPE_CYCLE_COUNTER(STAT_TrailMark_UpdateSectionGT);
		
	const int32 NumVerts = Vertices.Num();

	// Iterate through vertex data, copying in new info
	for (int32 VertIdx = 0; VertIdx < NumVerts; VertIdx++)
	{
		FTrailMarkVertex& ModifyVert = ProcVertexBuffer[startIndex + VertIdx];
				
		ModifyVert.Position = Vertices[VertIdx];
		ModifyVert.Normal = Normals[VertIdx];
		if (VertIdx < Tangents.Num())
			ModifyVert.Tangent = Tangents[VertIdx];
		if (VertIdx < UV0.Num())
			ModifyVert.UV0 = UV0[VertIdx];
		if (VertIdx < VertexColors.Num()) ModifyVert.Color = VertexColors[VertIdx]; } if (SceneProxy && pushToRenderThread) { // Create data to update section FTrailMarkSectionUpdateData* SectionData = new FTrailMarkSectionUpdateData; SectionData->m_startIndex = startIndex;
		
		SectionData->NewVertexBuffer = ProcVertexBuffer;

		/*
		// Copy data to vertex buffer
		SectionData->NewVertexBuffer.Reset();
		SectionData->NewVertexBuffer.AddUninitialized(NumVerts);
		for (int32 VertIdx = 0; VertIdx < NumVerts; VertIdx++) { FTrailMarkVertex& ModifyVert = SectionData->NewVertexBuffer[VertIdx];

			ModifyVert.Position = Vertices[VertIdx];
			ModifyVert.Normal = Normals[VertIdx];
			if (VertIdx < Tangents.Num())
				ModifyVert.Tangent = Tangents[VertIdx];
			if (VertIdx < UV0.Num())
				ModifyVert.UV0 = UV0[VertIdx];
			if (VertIdx < VertexColors.Num()) ModifyVert.Color = VertexColors[VertIdx]; } */ // Enqueue command to send to render thread ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER( FTrailMarkSectionUpdate, FTrailMarkComponentSceneProxy*, TrailMarkSceneProxy, (FTrailMarkComponentSceneProxy*)SceneProxy, FTrailMarkSectionUpdateData*, SectionData, SectionData, { TrailMarkSceneProxy->UpdateSection_RenderThread(SectionData);
			}
		);
	}
}

FPrimitiveSceneProxy* UTrailMarkComponent::CreateSceneProxy()
{
	//SCOPE_CYCLE_COUNTER(STAT_TrailMark_CreateSceneProxy);

	return new FTrailMarkComponentSceneProxy(this);
}

int32 UTrailMarkComponent::GetNumMaterials() const
{
	return 1;
}


FBoxSphereBounds UTrailMarkComponent::CalcBounds(const FTransform& LocalToWorld) const
{
	FBoxSphereBounds NewBounds;
	NewBounds.Origin = FVector::ZeroVector;
	NewBounds.BoxExtent = FVector(HALF_WORLD_MAX, HALF_WORLD_MAX, HALF_WORLD_MAX);
	NewBounds.SphereRadius = FMath::Sqrt(3.0f * FMath::Square(HALF_WORLD_MAX));
	return NewBounds;
}

void UTrailMarkComponent::calculateFirstVertices(const FVector &pos, const FVector &dir, TArray& vertices, TArray& normals, TArray& uv0, TArray& vertexColors) const
{
	FVector right = FVector::CrossProduct(dir, FVector::UpVector);

	// Position
	vertices[0] = pos + right * m_quadWidth * 0.5f;
	vertices[1] = pos - right * m_quadWidth * 0.5f;


	uv0[0].X = 0;
	uv0[1].X = 0;

	uv0[0].Y = 0;
	uv0[1].Y = 1;
	normals[0] = FVector::UpVector;
	normals[1] = FVector::UpVector;

	vertexColors[0] = FColor(255, 255, 255, 0);
	vertexColors[1] = FColor(255, 255, 255, 0);

}
void UTrailMarkComponent::calculateVertices(const FVector &pos, const FVector &lastPos, float dist, float alpha, TArray& vertices, TArray& normals, TArray& uv0, TArray& vertexColors) const
{
	FVector dir = pos - lastPos;
	dir.Normalize();
	FVector right = FVector::CrossProduct(dir, FVector::UpVector);

	// Position
	vertices[0] = pos + right * m_quadWidth * 0.5f;
	vertices[1] = pos - right * m_quadWidth * 0.5f;

	// UVs, world time, intensity
	float u = dist / m_textureLength;
	uv0[0].X= u;
	uv0[1].X = u;

	uv0[0].Y = 0;
	uv0[1].Y = 1;
	normals[0] = FVector::UpVector;
	normals[1] = FVector::UpVector;

	vertexColors[0] = FColor(255, 255, 255, alpha *255.0f);
	vertexColors[1] = FColor(255, 255, 255, alpha *255.0f);
}