[Chaos Vehicle] Experiences with the new vehicle implementation [+ SkidMark implementation]

Great. Looking forward to physX parity for adv. Set up.

One of the more annoying “features” with the PhysX vehicle implementation is the lack of interaction between two vehicles. Sure you can workaround the inmoveable cars during collsion (e.g. dummy collider which transfers the energy directly to the car itself) but this isn’t the greatest solution.

It appears that the chaos implementation doesn’t lack this feature. See the following video for a short demonstration:

But if the vehicles stay in touch permantely, there is one springy suspension “dance”. Funny to watch but not really usefull though ;).

[HR][/HR]
If you set-up your car the usual way, you can observe a strange behaviour during the collsion between the body & the tyre of another car:

This behaviour can simply turned off by disabling the collsion responses of the wheel colliders (normally four sphere colliders):


Result:

2 Likes

Brief update regarding physical materials for grounds. It turned out it is not enough to simply override the physical material within the collision properties of any object. If you do so, you get always the default physical material. You actually need to assign the physical material to a graphical one, which is then applied to the object (e.g. the ground). That was defnitely not the case with the original PhysX implementation…

Not sure thats not the case with PhysX. I suppose it depends on the ground being a mesh or being a landscape.
on the landscape the default is driven by the layer and the override can be wonky.

Read as:
We had consistent problems with footstep sounds and landscapes not reporting the correct material since .19 fixes and regressions between the version/patches.

/ yes its beta, but this is likely something that should be bug reported.

ps: whats with the shadows of your mesh? O_o

you’re right. if you going for a more advanced setup, this was always the way to go.

i just applied the window materials from the automotive package. it seems that the transparent surfaces do not create any shadows. i’ll look into that

Howto: Simple skid marks
I’ve been asked what my general approach for the skid marks is. In this post I want to share a few details of my quite simple (according to the correctness of the graphical result) implementation.

Disclaimer: There are many still-pending features and bugs within this implementation. The following snippets should give you just a brief overview of the general approach! If you got any questions or uncertainties feel free to ask :slight_smile:

  1. First you somehow need to access the wheel data which then can be used to compute all different things e.g. the skid marks. The principle to access this data hasn’t been changed from the old PhysX based vehicle system. You need to create a custom implementation of AWheeledVehiclePawn. By doing so you can override the tick function to collect the relevant wheel data. Here is my current implementation…
Header:
[SPOILER]

// Fill out your copyright notice in the Description page of Project Settings.
	
	#pragma once
	
	#include "CoreMinimal.h"
	#include "Delegates/Delegate.h"
	#include "Delegates/DelegateCombinations.h"
	#include "PhysicalMaterials/PhysicalMaterial.h"
	#include "PhysXIncludes.h"
	#include "ExtendedChaosWheeledVehicleMovementComponent.h"
	#include "WheeledVehiclePawn.h"
	#include "ProceduralMeshComponent.h"
	#include "ExtendedChaosVehicle.generated.h"
	
	/**
	*
	*/
	USTRUCT(Blueprintable)
	struct FExtendedWheelInfo
	{
	GENERATED_BODY()
	
	/** Get the wheel RPM [revolutions per minute] */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "ExtendedWheelInfo")
	float Rpm;
	
	/** Get the magnitude of the force pressing the wheel into the terrain [N.m] */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "ExtendedWheelInfo")
	float LoadForce;
	
	/** Get the slip angle for this wheel - angle between wheel forward axis and velocity vector [degrees] */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "ExtendedWheelInfo")
	float SlipAngle;
	
	/** Difference between the ground speed of the wheel and the road speed [km/h] */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "ExtendedWheelInfo")
	float SlipVelocity;
	
	/** Get the linear ground speed of the wheel based on its current rotational speed [km/h] */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "ExtendedWheelInfo")
	float GroundSpeed;
	
	/** Get the road speed at the wheel [km/h] */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "ExtendedWheelInfo")
	float Speed;
	
	/** Get the angular velocity of the wheel [degrees/sec] */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "ExtendedWheelInfo")
	float AngularVelocity;
	
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "ExtendedWheelInfo")
	float NormalizedLateralSlip;
	
	/** Get the current longitudinal slip value [0 no slip - using static friction, 1 full slip - using dynamic friction] */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "ExtendedWheelInfo")
	float NormalizedLongitudinalSlip;
	
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "ExtendedWheelInfo")
	bool InAir;
	
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "ExtendedWheelInfo")
	FString ContactSurfaceName;
	
	//UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "ExtendedWheelInfo")
	//TWeakObjectPtr<UPhysicalMaterial> ContactSurface;
	
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "ExtendedWheelInfo")
	int ContactSurfaceType;
	
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "ExtendedWheelInfo")
	FVector ContactPoint;
	
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "ExtendedWheelInfo")
	FVector ContactNormal;
	
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "ExtendedWheelInfo")
	bool Slipping;
	
	FExtendedWheelInfo() :
	Rpm(0.0f),
	LoadForce(0.0f),
	SlipAngle(0.0f),
	SlipVelocity(0.0f),
	GroundSpeed(0.0f),
	Speed(0.0f),
	NormalizedLateralSlip(0.0f),
	NormalizedLongitudinalSlip(0.0f),
	InAir(false),
	//ContactSurface(nullptr),
	ContactSurfaceType(-1),
	Slipping(false)
	{}
	};
	
	/**
	*
	*/
	USTRUCT(Blueprintable)
	struct FExtendedVehicleInfo
	{
	GENERATED_BODY()
	
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "ExtendedVehicleInfo")
	int32 Speed;
	
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "ExtendedVehicleInfo")
	float RPM;
	
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "ExtendedVehicleInfo")
	int32 Gear;
	
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "ExtendedVehicleInfo")
	bool Slipping;
	
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "ExtendedVehicleInfo")
	bool Skidding;
	
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "ExtendedVehicleInfo")
	TArray<FExtendedWheelInfo> WheelInfo;
	
	FExtendedVehicleInfo() :
	Speed(0),
	RPM(0.0f),
	Gear(0),
	Slipping(false),
	Skidding(false)
	{}
	};
	
	/**
	*
	*/
	USTRUCT(Blueprintable)
	struct FTyreSqueal
	{
	GENERATED_BODY()
	
	/** Threshold for the slip velocity [km/h] */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "TyreSqueal")
	float SlipVelocityThreshold;
	
	/** */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "TyreSqueal")
	float SlipAngleThreshold;
	
	FTyreSqueal() :
	SlipVelocityThreshold(5.0f),
	SlipAngleThreshold(40.0f)
	{}
	};
	
	DECLARE_DYNAMIC_MULTICAST_DELEGATE(FStartSlipping);
	DECLARE_DYNAMIC_MULTICAST_DELEGATE(FStopSlipping);
	
	DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FWheelEventWithIndex, uint8, WheelIndex);
	DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FWheelEventWithIndexAndWheelInfo, uint8, WheelIndex, FExtendedWheelInfo, WheelInfo);
	
	/**
	*
	*/
	UCLASS(Blueprintable)
	class AExtendedChaosVehicle : public AWheeledVehiclePawn
	{
	GENERATED_BODY()
	
	UPROPERTY(BlueprintAssignable, BlueprintCallable, Category = "ExtendedChaosVehicle")
	FStartSlipping OnStartSlipping;
	
	UPROPERTY(BlueprintAssignable, BlueprintCallable, Category = "ExtendedChaosVehicle")
	FStopSlipping OnStopSlipping;
	
	UPROPERTY(BlueprintAssignable, BlueprintCallable, Category = "ExtendedChaosVehicle")
	FWheelEventWithIndexAndWheelInfo OnWheelStartSlipping;
	
	UPROPERTY(BlueprintAssignable, BlueprintCallable, Category = "ExtendedChaosVehicle")
	FWheelEventWithIndexAndWheelInfo OnWheelSlipping;
	
	UPROPERTY(BlueprintAssignable, BlueprintCallable, Category = "ExtendedChaosVehicle")
	FWheelEventWithIndexAndWheelInfo OnWheelStopSlipping;
	
	private:
	FExtendedVehicleInfo LatestVehicleInfo;
	bool IsSlipping;
	
	public:
	
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ExtendedChaosVehicle")
	UMaterialInterface* Material;
	
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ExtendedChaosVehicle")
	FTyreSqueal TyreSqueal;
	
	AExtendedChaosVehicle(const FObjectInitializer& ObjectInitializer) :
	IsSlipping(false),
	Super(ObjectInitializer.SetDefaultSubobjectClass<UExtendedChaosWheeledVehicleMovementComponent>(VehicleMovementComponentName))
	{
	}
	
	void Tick(float deltaSeconds);
	
	UFUNCTION(BlueprintCallable, BlueprintPure, Category = "ExtendedChaosVehicle")
	inline FExtendedVehicleInfo GetVehicleInfo() { return LatestVehicleInfo; }
	
	private:
	FExtendedVehicleInfo DetermineVehicleInfo();
	};

[/SPOILER]

Source:
[SPOILER]


	// Fill out your copyright notice in the Description page of Project Settings.
	
	
	#include "ExtendedChaosVehicle.h"
	
	#define CHECK_BIT(var,pos) (((var)>>(pos)) & 1)
	
	void AExtendedChaosVehicle::Tick(float deltaSeconds)
	{
	// Local variables
	const auto movementComp = this->GetVehicleMovementComponent();
	
	Super::Tick(deltaSeconds);
	
	// Get next info
	auto info = DetermineVehicleInfo();
	
	// Check all wheels
	bool skidding = false;
	bool slipping = false;
	for (auto idx = 0; idx < info.WheelInfo.Num(); ++idx)
	{
	// Get last wheel info
	if (idx >= LatestVehicleInfo.WheelInfo.Num()) continue;
	auto& lastWheel = LatestVehicleInfo.WheelInfo[idx];
	
	// Get next wheel info
	auto& wheel = info.WheelInfo[idx];
	
	// Fire wheel event: start/stop slipping
	if (!lastWheel.Slipping && wheel.Slipping)
	{
	// Reset slipping duration
	//wheel.SlippingDuration = 0.0f;
	
	// Fire callback
	OnWheelStartSlipping.Broadcast(idx, wheel);
	}
	else if (lastWheel.Slipping && !wheel.Slipping) OnWheelStopSlipping.Broadcast(idx, wheel);
	else if (wheel.Slipping)
	{
	// Update slipping duration
	//wheel.SlippingDuration = lastWheel.SlippingDuration + deltaSeconds;
	
	// Fire callback
	OnWheelSlipping.Broadcast(idx, wheel);
	}
	
	// Accumulate skidding & slipping
	slipping = slipping || wheel.Slipping;
	}
	info.Slipping = slipping;
	
	// Fire events
	if (!IsSlipping && slipping)
	{
	IsSlipping = true;
	OnStartSlipping.Broadcast();
	}
	else if (IsSlipping && !slipping)
	{
	IsSlipping = false;
	OnStopSlipping.Broadcast();
	}
	
	// Update latest info
	LatestVehicleInfo = info;
	}
	
	FExtendedVehicleInfo AExtendedChaosVehicle::DetermineVehicleInfo()
	{
	// Local variables
	FExtendedVehicleInfo info;
	const auto movementComp = (UExtendedChaosWheeledVehicleMovementComponent*)this->GetVehicleMovementComponent();
	
	// Process wheel data
	for (int32_t wIdx = 0; wIdx < movementComp->PhysicsVehicle()->Wheels.Num(); ++wIdx)
	{
	// Get reference
	auto& wheel = movementComp->PhysicsVehicle()->Wheels[wIdx];
	const auto& chaosWheel = movementComp->GetWheel(wIdx);
	
	// Extract material's name
	TWeakObjectPtr<UPhysicalMaterial> contactSurfaceMaterial = nullptr;
	FString contactSurfaceString = "";
	int contactSurfaceType = -1;
	
	if (chaosWheel->GetContactSurfaceMaterial() && chaosWheel->GetContactSurfaceMaterial()->IsValidLowLevel())
	{
	contactSurfaceMaterial = chaosWheel->GetContactSurfaceMaterial();
	contactSurfaceString = contactSurfaceMaterial != nullptr ? contactSurfaceMaterial->GetName() : FString(TEXT("NONE"));
	contactSurfaceType = (int)contactSurfaceMaterial->SurfaceType;
	}
	
	// Add new wheel info
	FExtendedWheelInfo wheelInfo;
	wheelInfo.InAir = !wheel.bInContact;
	wheelInfo.Rpm = wheel.GetWheelRPM();
	wheelInfo.LoadForce = wheel.GetWheelLoadForce() * 0.01f;
	wheelInfo.SlipAngle = RadToDeg(wheel.GetSlipAngle());
	wheelInfo.SlipVelocity = (wheel.GetWheelGroundSpeed() - wheel.GetRoadSpeed()) * 0.036f;
	wheelInfo.AngularVelocity = wheel.GetAngularVelocity() * 57.3;
	wheelInfo.GroundSpeed = wheel.GetWheelGroundSpeed() * 0.036f;
	wheelInfo.Speed = wheel.GetRoadSpeed() * 0.036f;
	wheelInfo.NormalizedLongitudinalSlip = wheel.GetNormalizedLongitudinalSlip();
	wheelInfo.NormalizedLateralSlip = FMath::Clamp(RadToDeg(wheel.GetSlipAngle()) / 30.0f, -1.f, 1.f);//wheel.GetNormalizedLateralSlip();
	wheelInfo.ContactSurfaceName = contactSurfaceString;
	wheelInfo.ContactSurfaceType = contactSurfaceType;
	wheelInfo.ContactPoint = chaosWheel->HitResult.ImpactPoint;
	wheelInfo.ContactNormal = chaosWheel->HitResult.ImpactNormal;
	
	// Determine slipping & skidding
	float slipAngle = abs(wheelInfo.SlipAngle);
	wheelInfo.Slipping =
	slipAngle > TyreSqueal.SlipAngleThreshold && slipAngle < (180.0f - TyreSqueal.SlipAngleThreshold) ||
	abs(wheelInfo.SlipVelocity) > TyreSqueal.SlipVelocityThreshold;
	wheelInfo.Slipping &= !wheelInfo.InAir;
	
	// Add to list
	info.WheelInfo.Add(wheelInfo);
	}
	
	return info;
	}
	

[/SPOILER]
  1. The next step is to ensure that you actually use this implementation as base class of your vehicle.
    You can then set-up the events for slip-state-notifications: Event connection posted by anonymous | blueprintUE | PasteBin For Unreal Engine 4
  2. The start slip event is currently ignored (empty method). The events stop-slip & slipping are controlling the skid mark rendering. See the blueprints:
    Slipping: https://blueprintue.com/blueprint/p370pm9f/
    Stop-Slip: Event: wheel stop slipping posted by LeBoozer | blueprintUE | PasteBin For Unreal Engine 4
  3. My approach to render the skid mark meshes uses a *UProceduralMeshComponent *per tyre to emulate a ribbon of skid marks on the ground. These components are generated in a custom implementation of UChaosWheeledVehicleMovementComponent (the custom implementation is already linked the first step). My component creates a skid mark mesh per tyre during the start up. It also offers to method to control the skid mark mesh. The first function *AddSkidMarkSupportPoint *adds a new support point with a few attributes to the ribbon / mesh. The second method *IncreaseSkidMarkSection *increases the section counter (a section represents a continuous sequence of triangles) to add interruptions to the ribbon / mesh.
    See the implementation of custom movement component as well as of the skid mark mesh…
Movement component (header):
[SPOILER]


	// Fill out your copyright notice in the Description page of Project Settings.
	
	#pragma once
	
	#include "CoreMinimal.h"
	#include "Delegates/Delegate.h"
	#include "Delegates/DelegateCombinations.h"
	#include "PhysicalMaterials/PhysicalMaterial.h"
	#include "PhysXIncludes.h"
	#include "ChaosWheeledVehicleMovementComponent.h"
	#include "SkidMarkMesh.h"
	#include "ExtendedChaosWheeledVehicleMovementComponent.generated.h"
	
	/**
	*
	*/
	UCLASS(Blueprintable)
	class UExtendedChaosWheeledVehicleMovementComponent : public UChaosWheeledVehicleMovementComponent
	{
	GENERATED_BODY()
	
	protected:
	TArray<TSharedPtr<SkidMarkMesh>> SkidMarkMeshes;
	
	public:
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category= "Extended - SkidMarks")
	float SkidMarkMeshWidth;
	
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category= "Extended - SkidMarks")
	float SkidMarkMeshGroundOffset;
	
	UExtendedChaosWheeledVehicleMovementComponent() :
	SkidMarkMeshWidth(20.0f),
	SkidMarkMeshGroundOffset(0.02f)
	{}
	
	virtual void BeginPlay() override;
	//void CreateVehicle(TArray<FWheelSetup> previousTires);
	
	UFUNCTION(BlueprintCallable, Category = "ExtendedWheeledVehicleMovementComponent4W")
	void AddSkidMarkSupportPoint(int wheelIdx, FVector position, FVector normal, float intensity, float uvDivider, float minSqrDistanceToPrevious, UMaterialInterface* material);
	
	UFUNCTION(BlueprintCallable, Category = "ExtendedWheeledVehicleMovementComponent4W")
	void IncreaseSkidMarkSection(int wheelIdx);
	
	UChaosVehicleWheel* GetWheel(int wheelIdx);
	
	protected:
	void DropSkidMarkMeshes();
	void CreateSkidMarkMeshes();
	};
	

[/SPOILER]

Movement component (source):
[SPOILER]


	
	#include "ExtendedChaosWheeledVehicleMovementComponent.h"
	#include "SkidMarkMeshManager.h"
	
	void UExtendedChaosWheeledVehicleMovementComponent::BeginPlay()
	{
	// Call parent
	UChaosWheeledVehicleMovementComponent::BeginPlay();
	
	// Create skid mark meshes
	CreateSkidMarkMeshes();
	}
	
	/*void UExtendedChaosWheeledVehicleMovementComponent::CreateVehicle(TArray<ChaosWheelS> previousTires)
	{
	// Call parent
	UChaosWheeledVehicleMovementComponent::CreateVehicle();
	
	// Re-create skid mark meshes
	if (previousTires.Num() != WheelSetups.Num())
	{
	DropSkidMarkMeshes();
	CreateSkidMarkMeshes();
	}
	}*/
	
	void UExtendedChaosWheeledVehicleMovementComponent::AddSkidMarkSupportPoint(int wheelIdx, FVector position, FVector normal, float intensity,
	float uvDivider, float minSqrDistanceToPrevious, UMaterialInterface* material)
	{
	// Check wheel index
	if (wheelIdx >= SkidMarkMeshes.Num())
	{
	UE_LOG(LogTemp, Warning, TEXT("Cannot add support point to skid mark mesh. Invalid wheel index '%d' or skid mark mesh (Wheel count: %d)"), wheelIdx, SkidMarkMeshes.Num());
	return;
	}
	
	// Add skid mark
	if(uvDivider == 0.0f) uvDivider = 1.0f;
	SkidMarkMeshes[wheelIdx]->AddSupportPoint(position, normal, intensity, SkidMarkMeshWidth, SkidMarkMeshGroundOffset, uvDivider, minSqrDistanceToPrevious, material);
	}
	
	void UExtendedChaosWheeledVehicleMovementComponent::IncreaseSkidMarkSection(int wheelIdx)
	{
	// Check wheel index
	if (wheelIdx >= SkidMarkMeshes.Num())
	{
	UE_LOG(LogTemp, Warning, TEXT("Cannot add support point to skid mark mesh. Invalid wheel index: '%d' or skid mark mesh (Wheel count: %d)"), wheelIdx, SkidMarkMeshes.Num());
	return;
	}
	
	// Increase skid mark section
	SkidMarkMeshes[wheelIdx]->IncreaseSection();
	}
	
	UChaosVehicleWheel* UExtendedChaosWheeledVehicleMovementComponent::GetWheel(int wheelIdx)
	{
	// Check parameter
	check(wheelIdx <= this->Wheels.Num());
	return this->Wheels[wheelIdx];
	}
	
	void UExtendedChaosWheeledVehicleMovementComponent::DropSkidMarkMeshes()
	{
	// Local variables
	ASkidMarkMeshManager* manager = ASkidMarkMeshManager::FindManager(this->GetOwner());
	
	// Find skid mark manager
	if (!manager)
	{
	UE_LOG(LogTemp, Error, TEXT("Cannot destroy skid mark meshes. Manager object not found in level!"));
	return;
	}
	
	// Release all components
	for (auto& comp : SkidMarkMeshes)
	manager->DestroyMesh(comp);
	SkidMarkMeshes.Empty();
	}
	
	void UExtendedChaosWheeledVehicleMovementComponent::CreateSkidMarkMeshes()
	{
	// Local variables
	ASkidMarkMeshManager* manager = ASkidMarkMeshManager::FindManager(this->GetOwner());
	
	// Find skid mark manager
	if (!manager)
	{
	UE_LOG(LogTemp, Error, TEXT("Cannot create skid mark meshes. Manager object not found in level!"));
	return;
	}
	
	// Create list for components
	SkidMarkMeshes.Reserve(WheelSetups.Num());
	for (int i = 0; i < WheelSetups.Num(); ++i)
	{
	SkidMarkMeshes.Add(manager->CreateNewMesh(FString(TEXT("SkidMarkMesh_") + i)));
	}
	}
	

[/SPOILER]

Skid mark mesh (header):
[SPOILER]


	// Fill out your copyright notice in the Description page of Project Settings.
	
	#pragma once
	
	#include "CoreMinimal.h"
	#include "PhysicalMaterials/PhysicalMaterial.h"
	#include "PhysXIncludes.h"
	#include "ProceduralMeshComponent.h"
	
	/**
	*
	*/
	class SkidMarkMesh
	{
	private:
	UProceduralMeshComponent* ProceduralMeshComponent;
	
	uint32 SkidMarkMeshId;
	int32 CurrentSection;
	TArray<FVector> SupportPoints;
	TArray<FVector> Vertices;
	TArray<int32> Triangles;
	TArray<FVector> Normals;
	TArray<FVector2D> UV0;
	TArray<FLinearColor> VertexColors;
	TArray<FProcMeshTangent> Tangents;
	
	public:
	SkidMarkMesh(uint32 id, AActor* parent, const FString& name);
	
	inline uint32 GetSkidMarkMeshId() const { return SkidMarkMeshId; }
	
	void AddSupportPoint(FVector position, FVector normal, float intensity, float width, float groundOffset, float textureLength, float minSqrDistanceToPrev, UMaterialInterface* material);
	void IncreaseSection();
	void Drop();
	
	protected:
	void ProcessFirstSupportPoint(FVector point, FVector normal, FVector direction, float intensity, float width, float groundOffset);
	};
	

[/SPOILER]

Skid mark mesh (source):
[SPOILER]


	
	#include "SkidMarkMesh.h"
	
	SkidMarkMesh::SkidMarkMesh(uint32 id, AActor* parent, const FString& name) :
	SkidMarkMeshId(id),
	CurrentSection(0)
	{
	// Create procedural mesh component
	ProceduralMeshComponent = NewObject<UProceduralMeshComponent>(parent);
	ProceduralMeshComponent->RegisterComponent();
	ProceduralMeshComponent->bCastDynamicShadow = false;
	ProceduralMeshComponent->bCastCinematicShadow = false;
	ProceduralMeshComponent->bCastStaticShadow = false;
	ProceduralMeshComponent->AttachTo(parent->GetRootComponent());
	}
	
	void SkidMarkMesh::AddSupportPoint(FVector position, FVector normal, float intensity, float width, float groundOffset,
	float textureLength, float minSqrDistanceToPrev, UMaterialInterface* material)
	{
	// Move point along normal to gain a little gap between the ground and the support point
	position = position + normal * groundOffset;
	
	// Check min distance, if possible
	if (SupportPoints.Num() > 2 && FVector::DistSquared(position, SupportPoints[SupportPoints.Num() -1]) < minSqrDistanceToPrev)
	return;
	
	// Add point
	SupportPoints.Add(position);
	
	// We need at least two point to create a visible mesh
	if (SupportPoints.Num() < 2) return;
	
	// Extract the previous and the current/next support points
	FVector prev = SupportPoints[SupportPoints.Num() - 2];
	FVector next = SupportPoints[SupportPoints.Num() - 1];
	
	// Calculate direction between the previous & the next support point
	FVector dir = (next - prev);
	FVector xDir = FVector::CrossProduct(dir, normal);
	xDir.Normalize();
	
	// Generate the two quad vertices on a perpendicular line to the direction
	FVector posL = next + xDir * width * 0.5f;
	FVector posR = next - xDir * width * 0.5f;
	
	// Calculate the tangent vector for this two vertices
	FProcMeshTangent tangent = FProcMeshTangent(xDir.X, xDir.Y, xDir.Z);
	
	// The second support was added?
	// -> Calculate the initial values for the first support point
	if (SupportPoints.Num() == 2)
	ProcessFirstSupportPoint(prev, normal, dir, intensity, width, groundOffset);
	
	// Fill buffers
	Vertices.Add(posL);
	Vertices.Add(posR);
	Tangents.Add(tangent);
	Tangents.Add(tangent);
	
	int32 vCount = Vertices.Num();
	
	Triangles.Add(vCount - 4);
	Triangles.Add(vCount - 3);
	Triangles.Add(vCount - 2);
	Triangles.Add(vCount - 3);
	Triangles.Add(vCount - 1);
	Triangles.Add(vCount - 2);
	
	Normals.Add(normal);
	Normals.Add(normal);
	
	VertexColors.Add(FLinearColor(1, 1, 1, intensity));
	VertexColors.Add(FLinearColor(1, 1, 1, intensity));
	
	UV0.Add(FVector2D(SupportPoints.Num() / textureLength, 0));
	UV0.Add(FVector2D(SupportPoints.Num() / textureLength, 1));
	
	// Create / update section
	ProceduralMeshComponent->CreateMeshSection_LinearColor(CurrentSection, Vertices, Triangles, Normals, UV0, VertexColors, Tangents, false);
	
	// Set material
	if (material)
	ProceduralMeshComponent->SetMaterial(CurrentSection, material);
	}
	
	void SkidMarkMesh::IncreaseSection()
	{
	// Increase section counter
	++CurrentSection;
	
	// Clear buffer lists
	SupportPoints.Empty();
	Vertices.Empty();
	Triangles.Empty();
	Normals.Empty();
	UV0.Empty();
	VertexColors.Empty();
	Tangents.Empty();
	}
	
	void SkidMarkMesh::Drop()
	{
	// Check parameter
	if (!ProceduralMeshComponent) return;
	
	// Destroy compontent
	ProceduralMeshComponent->Deactivate();
	//ProceduralMeshComponent->BeginDestroy();
	//ProceduralMeshComponent = nullptr;
	}
	
	void SkidMarkMesh::ProcessFirstSupportPoint(FVector point, FVector normal, FVector direction, float intensity, float width, float groundOffset)
	{
	// Calculate the artificial direction for the first support point
	FVector xDir = FVector::CrossProduct(direction, FVector::UpVector);
	xDir.Normalize();
	
	// Generate the two quad vertices on a perpendicular line to the direction
	FVector posL = point + xDir * width * 0.5f;
	FVector posR = point - xDir * width * 0.5f;
	
	// Calculate the tangent vector for this two vertices
	FProcMeshTangent tangent = FProcMeshTangent(xDir.X, xDir.Y, xDir.Z);
	
	// Fill buffers
	UV0.Add(FVector2D(0, 0));
	UV0.Add(FVector2D(0, 1));
	Vertices.Add(posL);
	Vertices.Add(posR);
	Tangents.Add(tangent);
	Tangents.Add(tangent);
	Normals.Add(normal);
	Normals.Add(normal);
	VertexColors.Add(FLinearColor(1, 1, 1, intensity));
	VertexColors.Add(FLinearColor(1, 1, 1, intensity));
	}
	

[/SPOILER]

  1. The movement component actually does not create the skid mark meshes by itself, but uses a skid mark manager. The skid mark manager is a normal actor, which can be and has to be placed in a map. The skid mark meshes will be rendered by the manager. This approach ensures that the skid mark meshes of a hidden vehicle will still be rendered.
Skid mark manager (header):
[SPOILER]


	
	#pragma once
	
	#include "CoreMinimal.h"
	#include "GameFramework/Actor.h"
	#include "Vehicle/SkidMarkMesh.h"
	#include "SkidMarkMeshManager.generated.h"
	
	UCLASS()
	class ASkidMarkMeshManager : public AActor
	{
	GENERATED_BODY()
	
	uint32 IdCounter;
	TMap<uint32, TSharedPtr<SkidMarkMesh>> SkidMarkMeshes;
	
	public:
	ASkidMarkMeshManager() :
	IdCounter(0)
	{}
	
	TSharedPtr<SkidMarkMesh> CreateNewMesh(FString name);
	void DestroyMesh(TSharedPtr<SkidMarkMesh> mesh);
	
	virtual void BeginDestroy() override;
	
	static ASkidMarkMeshManager* FindManager(AActor* enquirer);
	};
	

[/SPOILER]

Skid mark manager (source):
[SPOILER]


	
	#include "SkidMarkMeshManager.h"
	#include "Kismet/GameplayStatics.h"
	
	TSharedPtr<SkidMarkMesh> ASkidMarkMeshManager::CreateNewMesh(FString name)
	{
	// Generate mesh
	TSharedPtr<SkidMarkMesh> result = MakeShared<SkidMarkMesh>(IdCounter, this, name);
	SkidMarkMeshes.Add(IdCounter, result);
	
	// Update id counter
	++IdCounter;
	
	return result;
	}
	
	void ASkidMarkMeshManager::DestroyMesh(TSharedPtr<SkidMarkMesh> mesh)
	{
	// Check parameter
	if (!mesh) return;
	
	// Remove instance from list
	TSharedPtr<SkidMarkMesh> outPtr;
	if (!SkidMarkMeshes.RemoveAndCopyValue(mesh->GetSkidMarkMeshId(), outPtr)) return;
	outPtr->Drop();
	}
	
	void ASkidMarkMeshManager::BeginDestroy()
	{
	// Release all meshes
	for (auto& comp : SkidMarkMeshes)
	comp.Value->Drop();
	SkidMarkMeshes.Empty();
	
	// Call parent's method
	AActor::BeginDestroy();
	}
	
	ASkidMarkMeshManager* ASkidMarkMeshManager::FindManager(AActor* enquirer)
	{
	// Local variables
	ASkidMarkMeshManager* manager = nullptr;
	
	// Check parameter
	if (!enquirer) return nullptr;
	
	// Find skid mark manager
	TArray<AActor*> foundActors;
	UGameplayStatics::GetAllActorsOfClass(enquirer->GetWorld(), ASkidMarkMeshManager::StaticClass(), foundActors);
	if (foundActors.Num() == 0 || (manager = Cast<ASkidMarkMeshManager>(foundActors[0])) == nullptr)
	return nullptr;
	
	return manager;
	}
	

[/SPOILER]

Improved skid mark demo video:

2 Likes

Thank you so much for providing Skid Marks Implementation. will really help me save my time.

Simple spoiler by using the aerofoil system

Today I tried the new aerofoil system (does not exists in the PhysX implementation as far as I know) of the chaos vehicle system. See the following video for the result:

First there is a spoiler set-up within the vehicle component by creating a aerofoil. Make sure to fill out all needed parameters, otherwise it crashes the whole engine!

  • Type: we’ve got a static spoiler, so this type has to be fixed
  • Bone: You can specify a bone where the spoiler is centered at
  • Offset: Additional offset to the autmatically computed one (based on the specified bone)
  • Up axis: Axis for the determination of the angle of attack (air flow vs. spoiler axis). Usally pointing upwards
  • Area: The effective area of the spoiler. The bigger the more lift (e.g planes) / downforce is generated
  • Camber / Max Control Angle: Ignored since we creating a static spoiler and not a rudder for planes
  • Stall angle: In our case the max. angle for the angle of attack (must not be null -> crash).
  • Lift multiplier: Custom scaler to modify the computed lift
  • Drag multiplier: Custom scaler to modify the computed drag (see bug below)

Apparently there are two bugs in the current code of the vehicle plugin (using the preview 2 installer version):

  1. If you set the drag multiplier to values other than zero, the engines crashes in the physics engine update (don’t know why, yet).
  2. The comutation of the world location while applying the aerofoil forces is in the wrong scale (can be manually fixed by re-compiling the engine plugin). For more details see: [Chaos Vehicle] Possible bug in "ApplyAerofoilForces" - Engine Source & GitHub - Unreal Engine Forums
1 Like

Wait. So you are using a procedural mesh component for skid marks?
why no hook it up with Niagara gpu sprite or similar?

My approach would completely differ:

Its easy enough to just spawn a contact/customizable vector field under the wheel with Niagara. Literally just place a socket and you are good to work.
You can then capture the parricles (not visible in game) to a RT and use it in a custom function you add to the terrain/material thats used for ground.

This has the benefit of also removing grass when you run over grass.
The obvious kink to work out is how to tilt the vector field based on the skid direction so that the pattern you could potentially form is properly displaced.
should be simple enough to work out based on the socket rotation compared to the wheel information.
might still need your way to pull said wheel information, in fact, you probably need the same way to start/stop spawning the particles too.

Anyway, that’s how I’d go about it since the RT system is usually already in place for stuff like water.

Potentially, you could also code the terrain function to add a pattern within the vector field, but it needs to happen post render because you will need a high spawn rate of the particle to create a continuous trail.

Last render cost i remember at 4k for 2k RT was sub 2ms.

Have you seen any sign of boat vehicle support yet in the preview? Particularly with the new water system…

Not yet. There isn’t any sign of buoyancy support. Wheeled as well as flying vehicles are currently supported by code (preview 2 code base). Currently i’m playing around with the wheeled stuff, but later on I’ll try to give the flying vehicles a spin…

At the time this was the easiest approach for me to create such an effect. But your idea is certainly interessting and I can image if done right more flexible.

Frame rate differences

I have had the feeling for a while now that the vehicle simulation is not indepentent from the frame rate. So I conducted two very simple tests:

  • The first test is a simple time attack run: from a standing position the vehicle accelerates at full speed until the second/end check point is reached. This run is executed at 40FPS and ~120FPS (my PC cannot generate more frames :rolleyes:). As one can clearly see the two times differ from each other.
  • The second test is a slide test on an uneven ground (again executed at 40FPS and ~120FPS). The result is quite interessting. I yet haven’t been able to make the car stop sliding at 40FPS, while at ~120FPS the car stands perfectly still.

UE4 Chaos Vehicle - Frame rate differences - YouTube

So far I haven’t found any chaos settings equivalent to the PhysX sub-stepping feature. Enabling this sub-stepping feature in the UE physics settings results in the same outcome (probably only used by PhysX).

Yes. I can confirm from trying to work ragdolls that substepping is off / currently the phys x setting does nothing.

However the baseline stabilization seems much better even with substepping off. at least, while you simulate parts.
It seems the latest preview omits some basic stuff needed to keep ragoll meshes in place, or even simulated objects. (The xyz locking options don’t work at all).

What works worse is the overall FPS with muti collision simulation. Colliding around 120 phat objects currently gives me fps of about 2.
before, with the physx and same options/scene the fps was 75 or above.

I’m positive this will all change once they reach parity.
I sure hope they do so soon too, since I made the mistake of updating my main project.
I can roll back if need be, but I’d rather not.
Either way, in my case its clear. Working on ragdolls before parity is a waste of time.

Also, cloth seems to work just about the same / makes me wonder if its even been replaced at all yet (when you update a project) or if the old stuff is still in play for it.
I did set the solvers to the chaos ones. Didn’t really see any changes. Nor does the backstop value work as the nvidia docs explain… it never has, at this rate, it never will :stuck_out_tongue:

Wheel-based trailer

My first go with a wheel-based physical trailer.

After a lot try & error while tweaking all possible values, the result stills feels a way off. Either the trailer is stiff as hell or completely unmanagable to drive. I figure that a non wheel-based (physically speaking) trailer is much easier to implement than spending houndreds of hours tweaking the values for all possible scenarios…

Terrain/landscape offroad

I’ve lifted the chassis of the Honda Prelude pretty high up (looks kinda ridiculous though, but gets the job done) to test the offroad capabilities / stability of the new vehicle system (I recall that the PhysX based system sometimes had trouble with long springs). First I tried to drive the street version of the Prelude on the landscape (just a scaled random height map), didn’t work out very well. The second run was done with the lifted offroad version of the Prelude. The system works quite well innately with the right set of parameters (e.g. long springs, more longitudal grip).
The following video showcases both tests:

  1. Can we Implement Tanks with Chaos?(We could with physx but it was awful)
    2.Which one is More efficient Chaos or physx(FPS And Network)?
1 Like

Neither of the systems has anything to do with network. AFAIK all physics simulations are always local (done on client) and eventually synced with server.

You can’t really trust a benchmark of a Beta feature. Wait for .26 to be fully release and I’m sure stats will be shared. So far, its promising and slightly more performant in some situations.

As far as Tanks go, I don’t see that either system would make much of a difference. The way they move is fundamentally different from a car, and from tank to tank. That’s mostly why people do custom implementations.

I suppose the better question would be, does Chaos currently support in-phase steering? (Which would be required to fake almost all tank movement with invisible wheels).

If Physics is calculated in Client then replicated to server then its unsafe for a Competitive game right?

Depends. There are multiple ways to keep it in sync, but you need to know what you are doing network wise. Its the major case of “lag” normally.