FVector UKismetMathLibrary::ClampVectorSize(FVector A, float Min, float Max) { return A.GetClampedToSize(Min, Max); }
// Runs calculations on friction component, applies friction force to effected component and returns reaction forces(forces that can effect track or a wheel) void UMMTFrictionComponent::ApplyFriction(const FVector& ContactPointLocation, const FVector& ContactPointNormal, const FVector& InducedVelocity, const FVector& PreNormalForceAtPoint, const EPhysicalSurface& PhysicalSurface, const float& NumberOfContactPoints, const float& DeltaTime, FVector& NormalizedReactionForce, FVector& RollingFrictionForce) { // Gather stats SCOPE_CYCLE_COUNTER(STAT_MMTFrictionApply); float NormalForceAtContactPoint = PreNormalForceAtPoint.ProjectOnTo(ContactPointNormal).Size(); // Check if Effected Component Mesh reference is valid and escape early otherwise if (!IsValid(EffectedComponentMesh)) { GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, FString::Printf(TEXT("%s->%s component's EffectedComponentMesh reference is invalid!"), *GetOwner()->GetName(), *GetName())); UE_LOG(LogTemp, Warning, TEXT("%s->%s component's EffectedComponentMesh reference is invalid!"), *GetOwner()->GetName(), *GetName()); NormalizedReactionForce = FVector::ZeroVector; RollingFrictionForce = FVector::ZeroVector; return; } //Find relative velocity of the friction surface and ground/another object at point FVector RelativeVelocityAtPoint = EffectedComponentMesh->GetPhysicsLinearVelocityAtPoint(ContactPointLocation) + InducedVelocity + FrictionSurfaceVelocity; RelativeVelocityAtPoint = RelativeVelocityAtPoint.VectorPlaneProject(RelativeVelocityAtPoint, ContactPointNormal); //filter out oscillations when vehicle is standing still but friction overshoots RelativeVelocityAtPoint = (PrevRelativeVelocityAtPoint + RelativeVelocityAtPoint) * 0.5; PrevRelativeVelocityAtPoint = RelativeVelocityAtPoint; // early exit if velocity is too low to consider as vehicle most likely standing still if (RelativeVelocityAtPoint.Size() < 1.0f) { NormalizedReactionForce = FVector::ZeroVector; RollingFrictionForce = FVector::ZeroVector; return; } //Calculate static and kinetic friction coefficients, taking into account velocity direction and friction ellipse float MuStatic; float MuKinetic; UMMTBPFunctionLibrary::GetMuFromFrictionElipse(RelativeVelocityAtPoint.GetSafeNormal(), ReferenceFrameTransform.TransformVector(FVector(1.0f, 1.0f, 1.0f)), MuXStatic, MuXKinetic, MuYStatic, MuYKinetic, MuStatic, MuKinetic); //Calculate "stopping force" which is amount of force necessary to completely remove velocity of the object FVector StoppingForce = ((RelativeVelocityAtPoint * (-1.0f) * EffectedComponentMesh->GetMass()) / DeltaTime) / NumberOfContactPoints; //Static friction threshold float MuStaticByLoad = NormalForceAtContactPoint * MuStatic; //Friction Force that will be applied to effected mesh component FVector ApplicationForce; if (StoppingForce.Size() >= MuStaticByLoad) { ApplicationForce = StoppingForce.GetClampedToSize(0.0f, NormalForceAtContactPoint * MuKinetic); if (IsDebugMode) { DrawDebugString(GetWorld(), ContactPointLocation, FString("Kinetic Friction"), nullptr, FColor::Magenta, 0.0f, false); } } else { ApplicationForce = StoppingForce.GetClampedToSize(0.0f, MuStaticByLoad); if (IsDebugMode) { DrawDebugString(GetWorld(), ContactPointLocation, FString("Static Friction"), nullptr, FColor::Red, 0.0f, false); } } //Apply friction force UMMTBPFunctionLibrary::MMTAddForceAtLocationComponent(EffectedComponentMesh, ApplicationForce, ContactPointLocation); //Calculate Reaction force NormalizedReactionForce = (ApplicationForce * (-1.0f)) / EffectedComponentMesh->GetMass(); //Calculate Rolling Friction float RollingFrictionCoefficient = FPhysicalSurfaceRollingFrictionCoefficient().RollingFrictionCoefficient; for (int32 i = 0; i < PhysicsSurfaceResponse.Num(); i++) { if (PhysicsSurfaceResponse[i].PhysicalSurface == PhysicalSurface) { RollingFrictionCoefficient = PhysicsSurfaceResponse[i].RollingFrictionCoefficient; break; } } RollingFrictionForce = RelativeVelocityAtPoint.GetSafeNormal() * NormalForceAtContactPoint * RollingFrictionCoefficient; if (IsDebugMode) { DrawDebugLine(GetWorld(), ContactPointLocation, ContactPointLocation + ApplicationForce * 0.005f, FColor::Yellow, false, 0.0f, 0, 3.0f); DrawDebugLine(GetWorld(), ContactPointLocation, ContactPointLocation + RollingFrictionForce * 0.005f, FColor::Green, false, 0.0f, 0, 3.0f); DrawDebugString(GetWorld(), ContactPointLocation+FVector(0.0f, 0.0f, 50.0f), (PhysicalSurfaceEnum ? PhysicalSurfaceEnum->GetEnumName(PhysicalSurface) : FString("<Invalid Enum>")), nullptr, FColor::Cyan, 0.0f, false); //DrawDebugString(GetWorld(), ContactPointLocation + FVector(0.0f, 0.0f, 25.0f), FString("Normal Force: ") + FString::SanitizeFloat(NormalForceAtContactPoint), nullptr, FColor::Turquoise, 0.0f, false); } }