Download

Custom Mesh Physics

I want to create custom mesh’s with their own Physics. I’ve been trying to do this for a long time now.
My main problem here is that if I attached this object to the custom mesh, the mesh just dissapears when the physics starts…
You can recreate this problem if you use this line in your custom mesh code:
CustomMesh->SetSimulatePhysics(true);
It’d be nice for the custom mesh tutorial to include physics, if anyone wants to do this.
Has anyone solved this problem? I don’t fully understand how unreal manages these things. I’m only a one man team, and only have the forums to consult. Please help me senpai’s.

Success in Voxel Smooth Terrain:
30d2cbaed4bdb82398e8f2114055d83128138d47.jpeg

As you can tell, the custom physics works well with static mesh’s attached. So I may research into them instead of the custom mesh’ code on the forum that extends UMeshComponent. But I heard that the latter, had alot of performance increases over static meshes.
Succeess in custom physics:
a20251a41e0c6d57b4da729a8413ac070790860a.jpeg

First I copied the UBoxComponent code and made my own class for custom physics:



class TESTTERRAIN_API UTestCustomPhysics : public UShapeComponent {


Then I created a bunch of Triangle Data in the header:



	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Shape)
	TArray<FGenerateMeshTriangle> Triangles;


Then I changed the UpdateBodySetup, from using the BoxElem to the ConvexElem.



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

#pragma once

#include "GameFramework/Actor.h"
#include "Components/ShapeComponent.h"
#include "GenerateMesh.h"
#include "TestCustomPhysics.generated.h"


UCLASS(ClassGroup = Shapes, editinlinenew, hidecategories = (Object, LOD, Lighting, TextureStreaming), meta = (BlueprintSpawnableComponent))
class TESTTERRAIN_API UTestCustomPhysics : public UShapeComponent {
	GENERATED_UCLASS_BODY()
public:
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Shape)
	TArray<FGenerateMeshTriangle> Triangles;
	//UPROPERTY(BlueprintCallable, Category = Shape)
	void AddCustomBox(FVector Size, FVector Position);
	//UPROPERTY(BlueprintCallable, Category = Shape)
		void AddCustomBox();

	virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) OVERRIDE;
		UPROPERTY(EditAnywhere, BlueprintReadOnly, export, Category = Shape)
			FVector Size;
		UPROPERTY(EditAnywhere, BlueprintReadOnly, export, Category = Shape)
			FVector Offset;

	// Begin UPrimitiveComponent interface.
	virtual FPrimitiveSceneProxy* CreateSceneProxy() override;
	virtual bool IsZeroExtent() const override;
	virtual struct FCollisionShape GetCollisionShape(float Inflation = 0.0f) const override;
	//virtual bool AreSymmetricRotations(const FQuat& A, const FQuat& B, const FVector& Scale3D) const override;
	// End UPrimitiveComponent interface.

	// Begin USceneComponent interface
	virtual FBoxSphereBounds CalcBounds(const FTransform & LocalToWorld) const override;
	//virtual void CalcBoundingCylinder(float& CylinderRadius, float& CylinderHalfHeight) const override;
	// End USceneComponent interface

	// Begin UShapeComponent interface
	virtual void UpdateBodySetup() override;
	// End UShapeComponent interface
};




#include "TestTerrain.h"
#include "Engine.h"
#include "DynamicMeshBuilder.h"
#include "TestCustomPhysics.h"

// Vertex Buffer
class FGeneratedMeshVertexBuffer : public FVertexBuffer {
public:
	TArray<FDynamicMeshVertex> Vertices;

	virtual void InitRHI() 	{

		//#if ENGINE_MAJOR_VERSION >= 4 && ENGINE_MINOR_VERSION >= 3
		FRHIResourceCreateInfo CreateInfo;
		VertexBufferRHI = RHICreateVertexBuffer(Vertices.Num() * sizeof(FDynamicMeshVertex), BUF_Static, CreateInfo);
		//#else
		//		VertexBufferRHI = RHICreateVertexBuffer(Vertices.Num() * sizeof(FDynamicMeshVertex), NULL, BUF_Static);
		//#endif
		// Copy the vertex data into the vertex buffer.
		void* VertexBufferData = RHILockVertexBuffer(VertexBufferRHI, 0, Vertices.Num() * sizeof(FDynamicMeshVertex), RLM_WriteOnly);
		FMemory::Memcpy(VertexBufferData, Vertices.GetTypedData(), Vertices.Num() * sizeof(FDynamicMeshVertex));
		RHIUnlockVertexBuffer(VertexBufferRHI);
	}
};

// Index Buffer
class FGeneratedMeshIndexBuffer : public FIndexBuffer
{
public:
	TArray<int32> Indices;

	virtual void InitRHI()	{
		//#if ENGINE_MAJOR_VERSION >= 4 && ENGINE_MINOR_VERSION >= 3
		FRHIResourceCreateInfo CreateInfo;
		IndexBufferRHI = RHICreateIndexBuffer(sizeof(int32), Indices.Num() * sizeof(int32), BUF_Static, CreateInfo);
		//#else
		//		IndexBufferRHI = RHICreateIndexBuffer(sizeof(int32), Indices.Num() * sizeof(int32), NULL, BUF_Static);
		//#endif
		// Write the indices to the index buffer.
		void* Buffer = RHILockIndexBuffer(IndexBufferRHI, 0, Indices.Num() * sizeof(int32), RLM_WriteOnly);
		FMemory::Memcpy(Buffer, Indices.GetTypedData(), Indices.Num() * sizeof(int32));
		RHIUnlockIndexBuffer(IndexBufferRHI);
	}
};

// Vertex Factory
class FGeneratedMeshVertexFactory : public FLocalVertexFactory {
public:
	FGeneratedMeshVertexFactory()
	{}


	// Initialization
	void Init(const FGeneratedMeshVertexBuffer* VertexBuffer) 	{
		check(!IsInRenderingThread());

		ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER(
			InitGeneratedMeshVertexFactory,
			FGeneratedMeshVertexFactory*, VertexFactory, this,
			const FGeneratedMeshVertexBuffer*, VertexBuffer, VertexBuffer,
			{
			// Initialize the vertex factory's stream components.
			DataType 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);
			VertexFactory->SetData(NewData);
		});
	}
};

void UTestCustomPhysics::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) {

	AddCustomBox();
	Super::PostEditChangeProperty(PropertyChangedEvent);
}
UTestCustomPhysics::UTestCustomPhysics(const class FPostConstructInitializeProperties& PCIP)
: Super(PCIP)
{
	//SphereRadius = 130;
	Size = FVector(500, 500, 500);
	ShapeColor = FColor(255, 0, 0, 255);
	AddCustomBox();

	//bUseEditorCompositing = true;
}

void UTestCustomPhysics::AddCustomBox() {
	AddCustomBox(Size, Offset);
}

void UTestCustomPhysics::AddCustomBox(FVector Size, FVector Position) {
	// make vertex positions
	FVector p0 = FVector(Position.X, Position.Y, Position.Z);
	FVector p1 = FVector(Position.X, Position.Y, Position.Z + Size.Z);
	FVector p2 = FVector(Position.X + Size.X, Position.Y, Position.Z + Size.Z);
	FVector p3 = FVector(Position.X + Size.X, Position.Y, Position.Z);
	FVector p4 = FVector(Position.X + Size.X, Position.Y + Size.Y, Position.Z);
	FVector p5 = FVector(Position.X + Size.X, Position.Y + Size.Y, Position.Z + Size.Z);
	FVector p6 = FVector(Position.X, Position.Y + Size.Y, Position.Z + Size.Z);
	FVector p7 = FVector(Position.X, Position.Y + Size.Y, Position.Z);

	// Verticies used in the triangles
	FGenerateMeshTriangleVertex v0;
	FGenerateMeshTriangleVertex v1;
	FGenerateMeshTriangleVertex v2;
	FGenerateMeshTriangleVertex v3;
	v0.U = 0; v0.V = 0;
	v1.U = 0; v1.V = .5;
	v2.U = .5; v2.V = .5;
	v3.U = .5; v3.V = 0;
	float LightRed = 1;
	float LightGreen = 1;
	float LightBlue = 1;
	v0.Color = FColor(LightRed, LightGreen, LightBlue, 255);
	v1.Color = FColor(LightRed, LightGreen, LightBlue, 255);
	v2.Color = FColor(LightRed, LightGreen, LightBlue, 255);
	v3.Color = FColor(LightRed, LightGreen, LightBlue, 255);
	v0.U = 0; v0.V = 0;
	v1.U = 0; v1.V = 0 + 1;
	v2.U = 0 + 1; v2.V = 0 + 1;
	v3.U = 0 + 1; v3.V = 0;
	FGenerateMeshTriangle t1;
	FGenerateMeshTriangle t2;

	// front face
	v0.Position = p0;
	v1.Position = p1;
	v2.Position = p2;
	v3.Position = p3;
	t1.Vertex0 = v0;
	t1.Vertex1 = v1;
	t1.Vertex2 = v2;
	t2.Vertex0 = v0;
	t2.Vertex1 = v2;
	t2.Vertex2 = v3;
	Triangles.Add(t1);
	Triangles.Add(t2);

	//back face
	v0.Position = p4;
	v1.Position = p5;
	v2.Position = p6;
	v3.Position = p7;
	t1.Vertex0 = v0;
	t1.Vertex1 = v1;
	t1.Vertex2 = v2;
	t2.Vertex0 = v0;
	t2.Vertex1 = v2;
	t2.Vertex2 = v3;
	Triangles.Add(t1);
	Triangles.Add(t2);

	// left face
	v0.Position = p7;
	v1.Position = p6;
	v2.Position = p1;
	v3.Position = p0;
	t1.Vertex0 = v0;
	t1.Vertex1 = v1;
	t1.Vertex2 = v2;
	t2.Vertex0 = v0;
	t2.Vertex1 = v2;
	t2.Vertex2 = v3;
	Triangles.Add(t1);
	Triangles.Add(t2);

	// right face
	v0.Position = p3;
	v1.Position = p2;
	v2.Position = p5;
	v3.Position = p4;
	t1.Vertex0 = v0;
	t1.Vertex1 = v1;
	t1.Vertex2 = v2;
	t2.Vertex0 = v0;
	t2.Vertex1 = v2;
	t2.Vertex2 = v3;
	Triangles.Add(t1);
	Triangles.Add(t2);

	// top face
	v0.Position = p1;
	v1.Position = p6;
	v2.Position = p5;
	v3.Position = p2;
	t1.Vertex0 = v0;
	t1.Vertex1 = v1;
	t1.Vertex2 = v2;
	t2.Vertex0 = v0;
	t2.Vertex1 = v2;
	t2.Vertex2 = v3;
	Triangles.Add(t1);
	Triangles.Add(t2);

	// bottom face
	v0.Position = p3;
	v1.Position = p4;
	v2.Position = p7;
	v3.Position = p0;
	t1.Vertex0 = v0;
	t1.Vertex1 = v1;
	t1.Vertex2 = v2;
	t2.Vertex0 = v0;
	t2.Vertex1 = v2;
	t2.Vertex2 = v3;
	Triangles.Add(t1);
	Triangles.Add(t2);
}

FBoxSphereBounds UTestCustomPhysics::CalcBounds(const FTransform & LocalToWorld) const {
	return FBoxSphereBounds(FBox(-Size, Size).TransformBy(LocalToWorld));
}

void UTestCustomPhysics::UpdateBodySetup() {
	if (ShapeBodySetup == NULL || ShapeBodySetup->IsPendingKill())
	{
		ShapeBodySetup = ConstructObject<UBodySetup>(UBodySetup::StaticClass(), this);
		ShapeBodySetup->CollisionTraceFlag = CTF_UseSimpleAsComplex;
		ShapeBodySetup->AggGeom.ConvexElems.Add(FKConvexElem());
	}
	FKConvexElem* convex = ShapeBodySetup->AggGeom.ConvexElems.GetTypedData();
	for (int32 i = convex->VertexData.Num() - 1; i > -1; i--) {
		convex->VertexData.RemoveAt(i);
	}
	for (int32 i = 0; i < Triangles.Num(); i++) {
		convex->VertexData.Add(Triangles*.Vertex0.Position);
		convex->VertexData.Add(Triangles*.Vertex1.Position);
		convex->VertexData.Add(Triangles*.Vertex2.Position);
	}
}

/*void UTestCustomPhysics::SetSphereRadius(float InSphereRadius, bool bUpdateOverlaps)
{
	//SphereRadius = InSphereRadius;
	MarkRenderStateDirty();

	if (bPhysicsStateCreated)
	{
		DestroyPhysicsState();
		UpdateBodySetup();
		CreatePhysicsState();

		if (bUpdateOverlaps && IsCollisionEnabled() && GetOwner())
		{
			UpdateOverlaps();
		}
	}
}*/

bool UTestCustomPhysics::IsZeroExtent() const
{
	return (Size.X == 0 && Size.Y == 0 && Size.Z == 0);
}


FPrimitiveSceneProxy* UTestCustomPhysics::CreateSceneProxy() {
	/** Represents a DrawLightRadiusComponent to the scene manager. */
	class FSphereSceneProxy : public FPrimitiveSceneProxy {
	private:
		const uint32				bDrawOnlyIfSelected : 1;
		const FColor				SphereColor;
		const UMaterialInterface*	ShapeMaterial;
		const FVector Size;

		UMaterialInterface* Material;
		FGeneratedMeshVertexBuffer VertexBuffer;
		FGeneratedMeshIndexBuffer IndexBuffer;
		FGeneratedMeshVertexFactory VertexFactory;
		FMaterialRelevance MaterialRelevance;
	public:

		/** Initialization constructor. */
		FSphereSceneProxy(const UTestCustomPhysics* InComponent)
			: FPrimitiveSceneProxy(InComponent)
			, bDrawOnlyIfSelected(InComponent->bDrawOnlyIfSelected)
			, SphereColor(InComponent->ShapeColor)
			, ShapeMaterial(InComponent->ShapeMaterial)
			, Size(InComponent->Size)
		{
			for (int32 i = 0; i < InComponent->Triangles.Num(); i++) {
				const FGenerateMeshTriangle& Tri = InComponent->Triangles*;

					FDynamicMeshVertex Vert0;
					Vert0.Position = Tri.Vertex0.Position;
					Vert0.Color = Tri.Vertex0.Color;
					//Vert0.SetTangents(TangentX, TangentY, TangentZ);
					Vert0.SetTangents(Tri.Vertex0.Normal, Tri.Vertex1.Normal, Tri.Vertex2.Normal);
					Vert0.TextureCoordinate.Set(Tri.Vertex0.U, Tri.Vertex0.V);
					int32 VIndex = VertexBuffer.Vertices.Add(Vert0);
					IndexBuffer.Indices.Add(VIndex);
					// new stuff

					FDynamicMeshVertex Vert1;
					Vert1.Position = Tri.Vertex1.Position;
					Vert1.Color = Tri.Vertex1.Color;
					//Vert1.SetTangents(TangentX, TangentY, TangentZ);
					Vert1.SetTangents(Tri.Vertex0.Normal, Tri.Vertex1.Normal, Tri.Vertex2.Normal);
					Vert1.TextureCoordinate.Set(Tri.Vertex1.U, Tri.Vertex1.V);
					VIndex = VertexBuffer.Vertices.Add(Vert1);
					IndexBuffer.Indices.Add(VIndex);

					FDynamicMeshVertex Vert2;
					Vert2.Position = Tri.Vertex2.Position;
					Vert2.Color = Tri.Vertex2.Color;
					//Vert2.SetTangents(TangentX, TangentY, TangentZ);
					Vert2.SetTangents(Tri.Vertex0.Normal, Tri.Vertex1.Normal, Tri.Vertex2.Normal);
					Vert2.TextureCoordinate.Set(Tri.Vertex2.U, Tri.Vertex2.V);
					VIndex = VertexBuffer.Vertices.Add(Vert2);
					IndexBuffer.Indices.Add(VIndex);
				}

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

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

				// Grab material
				Material = InComponent->GetMaterial(0);
				if (Material == NULL) 			{
					Material = UMaterial::GetDefaultMaterial(MD_Surface);
				}
			bWillEverBeLit = false;
		}
		virtual ~FSphereSceneProxy() 		{
			VertexBuffer.ReleaseResource();
			IndexBuffer.ReleaseResource();
			VertexFactory.ReleaseResource();
		}
		// this is where the actual rendering is done
		/*virtual void GetDynamicMeshElements(const TArray<const FSceneView*>& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override
		{
			QUICK_SCOPE_CYCLE_COUNTER(STAT_BoxSceneProxy_GetDynamicMeshElements);

			const FMatrix& LocalToWorld = GetLocalToWorld();
			const FColor DrawColor = GetSelectionColor(SphereColor, IsSelected(), IsHovered(), false);

			for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
			{
				if (VisibilityMap & (1 << ViewIndex))
				{
					FPrimitiveDrawInterface* PDI = Collector.GetPDI(ViewIndex);
					DrawOrientedWireBox(PDI, LocalToWorld.GetOrigin(), LocalToWorld.GetScaledAxis(EAxis::X), LocalToWorld.GetScaledAxis(EAxis::Y), LocalToWorld.GetScaledAxis(EAxis::Z), Size, DrawColor, SDPG_World);
				}
			}
		}*/

		virtual void DrawDynamicElements(FPrimitiveDrawInterface* PDI, const FSceneView* View) override
		{
			/*QUICK_SCOPE_CYCLE_COUNTER(STAT_BoxSceneProxy_DrawDynamicElements);

			const FMatrix& LocalToWorld = GetLocalToWorld();
			const FColor DrawColor = GetSelectionColor(SphereColor, IsSelected(), IsHovered(), false);
			DrawOrientedWireBox(PDI, LocalToWorld.GetOrigin(), LocalToWorld.GetScaledAxis(EAxis::X), LocalToWorld.GetScaledAxis(EAxis::Y), LocalToWorld.GetScaledAxis(EAxis::Z), Size, DrawColor, SDPG_World);*/

			QUICK_SCOPE_CYCLE_COUNTER(STAT_GeneratedMeshSceneProxy_DrawDynamicElements);

			const bool bWireframe = View->Family->EngineShowFlags.Wireframe;

			FColoredMaterialRenderProxy WireframeMaterialInstance(
				WITH_EDITOR ? GEngine->WireframeMaterial->GetRenderProxy(IsSelected()) : NULL,
				//GEngine->WireframeMaterial->GetRenderProxy(IsSelected()),
				//FLinearColor(0, 0.5f, 1.f)
				FLinearColor(1.0f, 0.0f, 0.f)
				);

			FMaterialRenderProxy* MaterialProxy = NULL;
			if (bWireframe)
			{
				MaterialProxy = &WireframeMaterialInstance;
			}
			else
			{
				MaterialProxy = Material->GetRenderProxy(IsSelected());
			}

			// Draw the mesh.
			FMeshBatch Mesh;
			FMeshBatchElement& BatchElement = Mesh.Elements[0];
			Mesh.bWireframe = bWireframe;
			Mesh.VertexFactory = &VertexFactory;
			Mesh.MaterialRenderProxy = MaterialProxy;
			Mesh.ReverseCulling = IsLocalToWorldDeterminantNegative();
			Mesh.Type = PT_TriangleList;
			Mesh.DepthPriorityGroup = SDPG_World;
			Mesh.CastShadow = true;

			BatchElement.IndexBuffer = &IndexBuffer;
			BatchElement.FirstIndex = 0;
			BatchElement.NumPrimitives = IndexBuffer.Indices.Num() / 3;
			BatchElement.MinVertexIndex = 0;
			//BatchElement.MaxVertexIndex = VertexBuffer.Vertices.Num() - 1;
			//BatchElement.PrimitiveUniformBuffer = CreatePrimitiveUniformBufferImmediate(GetLocalToWorld(), GetBounds(), GetLocalBounds(), true);
			BatchElement.PrimitiveUniformBuffer = CreatePrimitiveUniformBufferImmediate(GetLocalToWorld(), GetBounds(), GetLocalBounds(), true, true);
			PDI->DrawMesh(Mesh);
		}

		virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View)  override {
			FPrimitiveViewRelevance Result;
			Result.bDrawRelevance = IsShown(View);
			Result.bShadowRelevance = IsShadowCast(View);
			Result.bDynamicRelevance = true;
			MaterialRelevance.SetPrimitiveViewRelevance(Result);
			return Result;
		}

		virtual bool CanBeOccluded() const OVERRIDE
		{
			return !MaterialRelevance.bDisableDepthTest;
		}
		virtual uint32 GetMemoryFootprint(void) const override { return(sizeof(*this) + GetAllocatedSize()); }
		uint32 GetAllocatedSize(void) const { return(FPrimitiveSceneProxy::GetAllocatedSize()); }
	};

	return new FSphereSceneProxy(this);
}


FCollisionShape UTestCustomPhysics::GetCollisionShape(float Inflation) const {
	//if (IsBox) {
		FVector Extent = Size + Inflation;
		if (Inflation < 0.f)
		{
			// Don't shrink below zero size.
			Extent = Extent.ComponentMax(FVector::ZeroVector);
		}

		return FCollisionShape::MakeBox(Extent);
}


Ahh wow I fixed this pretty quick. The harder part was mainly the bit I did in the above code.
First I create an object. Then I add the normal custom mesh to it. However I don’t set it as a root, and I turn all collisions off.
Next I add the Custom Physics Object to it, and set it as the root. Then when updating the mesh, I update the physics object too.
The key part was setting the Render Mesh to the location/rotation of the actor in the tick cycle!

It’s more of a hack, but it works. If anyone has any idea how to create a singular object, with both rendering and physics, let me know. Pretty sure it’s to due with UShapeComponent only rendering in wireframe mode. I tried to turn it off but failed. I think if I delved into shape component’s code i would figure it all out.

I still have a problem. The convex mesh doesn’t work (the collisionss don’t work on the mesh) when i use this line:
ShapeBodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple;
and if i use:
ShapeBodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseSimpleAsComplex;
It simplifies all the triangle data. So the collisions with the mesh are too simple. If any one knows a way around this, please let me know.