## Tank Brawl 2

### Phung Games | Coming 2018

Tank Brawl 2 is currently in development and will be released around late 2018. Tank Brawl 2 is the sequel to our first published game Tank Brawl. In the first game, we wanted to remake something we loved as kids, the Battle City game on the NES, and give it some modern and personal touch that everyone old and new would enjoy playing.

Unreal Engine custom tire mark, tread mark, ground 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.

Posted by on - Intermediate Client Side Coding

Our website : www.tankbrawl2.com/subscribe

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::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

``````// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.

#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 */
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 */
FVector Position;

/** Vertex normal */
FVector Normal;

/** Vertex tangent */
FTrailMarkTangent Tangent;

/** Vertex color */
FColor Color;

/** Vertex texture co-ordinate */
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
//~ End UObject Interface.

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

float m_textureLength = 1500;

float m_trailAlpha = 1.0f;
private:
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<FTrailMarkVertex> ProcVertexBuffer;

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

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

void calculateFirstVertices(const FVector &pos, const FVector &dir, TArray<FVector>& vertices, TArray<FVector>& normals, TArray<FVector2D>& uv0, TArray<FColor>& vertexColors) const;
void calculateVertices(const FVector &pos, const FVector &lastPos, float dist, float alpha, TArray<FVector>& vertices, TArray<FVector>& normals, TArray<FVector2D>& uv0, TArray<FColor>& 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

``````// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.

#include "TrailMarkComponent.h"
#include "PrimitiveViewRelevance.h"
#include "RenderResource.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("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<FDynamicMeshVertex> 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<int32> 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. */
{

// Initialize the vertex factory's stream components.
FDataType NewData;
NewData.PositionComponent = STRUCTMEMBER_VERTEXSTREAMCOMPONENT(VertexBuffer, FDynamicMeshVertex, Position, VET_Float3);
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)
{
{
}
else
{
ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER(
InitTrailMarkVertexFactory,
FTrailMarkVertexFactory*, VertexFactory, this,
const FTrailMarkVertexBuffer*, VertexBuffer, 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<FTrailMarkVertex> 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 */
{

// 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<const FSceneView*>& 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;
}
}
}

virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const
{
FPrimitiveViewRelevance Result;
Result.bDrawRelevance = IsShown(View);
Result.bDynamicRelevance = true;
Result.bRenderInMainPass = ShouldRenderInMainPass();
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);
}

{

}

{
//SCOPE_CYCLE_COUNTER(STAT_TrailMark_CreateMeshSection);

Reset();

// Copy data to vertex buffer
ProcVertexBuffer.Reset();
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();
{

}

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<FVector> vertices;
TArray<FVector> normals;
TArray<FVector2D> uv0;
TArray<FColor> 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<FTrailMarkTangent>(), 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<FVector> vertices;
TArray<FVector> normals;
TArray<FVector2D> uv0;
TArray<FColor> 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<FTrailMarkTangent>(), true);

{
static float minDist = 0.1;
static float maxDist = 1;
static float maxTime = 1;

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

}

void UTrailMarkComponent::UpdateMesh(int32 startIndex, const TArray<FVector>& Vertices, const TArray<FVector>& Normals, const TArray<FVector2D>& UV0, const TArray<FColor>& VertexColors, const TArray<FTrailMarkTangent>& Tangents, bool pushToRenderThread)
{

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];
}

{
// Create data to update section
FTrailMarkSectionUpdateData* SectionData = new FTrailMarkSectionUpdateData;
SectionData->m_startIndex = startIndex;

SectionData->NewVertexBuffer = ProcVertexBuffer;

/*
// Copy data to vertex buffer
SectionData->NewVertexBuffer.Reset();
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,
{
}
);
}
}

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);
return NewBounds;
}

void UTrailMarkComponent::calculateFirstVertices(const FVector &pos, const FVector &dir, TArray<FVector>& vertices, TArray<FVector>& normals, TArray<FVector2D>& uv0, TArray<FColor>& 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<FVector>& vertices, TArray<FVector>& normals, TArray<FVector2D>& uv0, TArray<FColor>& 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);
}``````

Only registered members can share their thoughts. So come on! Join the community today (totally free - or sign in with your social account on the right) and join in the conversation.

Profile
Tutorial
##### Share
Related Games
Tank Brawl 2 Tactical Shooter
Related Engines
Unreal Engine 4 Commercial
Related Groups
Phung Games Developer & Publisher
Unreal Engine 4 Devs, Modders and Players Fans & Clans with 664 members