// Copyright Epic Games, Inc. All Rights Reserved. #include "RigPhysicsSimulation.h" #include "RigPhysicsSolverComponent.h" #include "Rigs/RigHierarchy.h" #include "Rigs/RigHierarchyController.h" #include "RigVMCore/RigVMExecuteContext.h" #include "ControlRig.h" #include "PhysicsControlHelpers.h" #include "Engine/Engine.h" #include "Physics/ImmediatePhysics/ImmediatePhysicsActorHandle.h" #include "Physics/ImmediatePhysics/ImmediatePhysicsJointHandle.h" #include "Physics/ImmediatePhysics/ImmediatePhysicsSimulation.h" #include "Physics/Experimental/PhysScene_Chaos.h" #include "Chaos/ParticleHandle.h" #include "Chaos/PBDJointConstraints.h" #include "Chaos/ImplicitObject.h" #include "Chaos/ShapeInstance.h" #include "Chaos/Capsule.h" #include "Chaos/PBDJointConstraintUtilities.h" #include "PhysicsEngine/BodyInstance.h" #include "PhysicsEngine/ConstraintInstance.h" #include "PhysicsEngine/BodySetup.h" TAutoConsoleVariable CVarControlRigPhysicsShowSimulationSpaceInfo( TEXT("ControlRig.Physics.ShowSimulationSpaceInfo"), 0, TEXT("Shows information associated with the simulation space used in control rig physics.")); //====================================================================================================================== FORCEINLINE_DEBUGGABLE FTransform GetSpaceTransform( ERigPhysicsSimulationSpace Space, const FTransform& ComponentTM, const FTransform& BoneTM) { switch (Space) { case ERigPhysicsSimulationSpace::Component: return ComponentTM; case ERigPhysicsSimulationSpace::World: return FTransform::Identity; case ERigPhysicsSimulationSpace::SpaceBone: return BoneTM * ComponentTM; default: ensureMsgf(false, TEXT("Unsupported Simulation Space")); return FTransform::Identity; } } //====================================================================================================================== FTransform FRigPhysicsSimulation::GetSimulationSpaceTransform(const FRigPhysicsSolverSettings& SolverSettings) const { switch (SolverSettings.SimulationSpace) { case ERigPhysicsSimulationSpace::Component: return SimulationSpaceState.ComponentTM; case ERigPhysicsSimulationSpace::World: return FTransform::Identity; case ERigPhysicsSimulationSpace::SpaceBone: return SimulationSpaceState.BoneRelComponentTM * SimulationSpaceState.ComponentTM; default: ensureMsgf(false, TEXT("Unsupported Simulation Space")); return FTransform::Identity; } } //====================================================================================================================== FTransform FRigPhysicsSimulation::ConvertComponentSpaceTransformToSimSpace( const FRigPhysicsSolverSettings& SolverSettings, const FTransform& TM) const { switch (SolverSettings.SimulationSpace) { case ERigPhysicsSimulationSpace::Component: return TM; case ERigPhysicsSimulationSpace::World: return TM * SimulationSpaceState.ComponentTM; case ERigPhysicsSimulationSpace::SpaceBone: return TM.GetRelativeTransform(SimulationSpaceState.BoneRelComponentTM); default: ensureMsgf(false, TEXT("Unsupported Simulation Space")); return TM; } } //====================================================================================================================== FTransform FRigPhysicsSimulation::ConvertSimSpaceTransformToComponentSpace( const FRigPhysicsSolverSettings& SolverSettings, const FTransform& TM) const { switch (SolverSettings.SimulationSpace) { case ERigPhysicsSimulationSpace::Component: return TM; case ERigPhysicsSimulationSpace::World: return TM.GetRelativeTransform(SimulationSpaceState.ComponentTM); case ERigPhysicsSimulationSpace::SpaceBone: return TM * SimulationSpaceState.BoneRelComponentTM; default: ensureMsgf(false, TEXT("Unsupported Simulation Space")); return TM; } } //====================================================================================================================== FORCEINLINE_DEBUGGABLE FVector ConvertWorldVectorToSimSpaceNoScale( ERigPhysicsSimulationSpace Space, const FVector& WorldVector, const FTransform& ComponentTM, const FTransform& BoneTM) { switch (Space) { case ERigPhysicsSimulationSpace::Component: return ComponentTM.InverseTransformVectorNoScale(WorldVector); case ERigPhysicsSimulationSpace::World: return WorldVector; case ERigPhysicsSimulationSpace::SpaceBone: return BoneTM.InverseTransformVectorNoScale(ComponentTM.InverseTransformVectorNoScale(WorldVector)); default: ensureMsgf(false, TEXT("Unsupported Simulation Space")); return WorldVector; } } //====================================================================================================================== FVector FRigPhysicsSimulation::ConvertWorldVectorToSimSpaceNoScale( const FRigPhysicsSolverSettings& SolverSettings, const FVector& WorldVector) const { switch (SolverSettings.SimulationSpace) { case ERigPhysicsSimulationSpace::Component: return SimulationSpaceState.ComponentTM.InverseTransformVectorNoScale(WorldVector); case ERigPhysicsSimulationSpace::World: return WorldVector; case ERigPhysicsSimulationSpace::SpaceBone: return SimulationSpaceState.BoneRelComponentTM.InverseTransformVectorNoScale( SimulationSpaceState.ComponentTM.InverseTransformVectorNoScale(WorldVector)); default: ensureMsgf(false, TEXT("Unsupported Simulation Space")); return WorldVector; } } //====================================================================================================================== FTransform FRigPhysicsSimulation::ConvertCollisionSpaceTransformToSimSpace( const FRigPhysicsSolverSettings& SolverSettings, const FTransform& TM) const { FTransform SimSpaceTM = GetSpaceTransform( SolverSettings.SimulationSpace, SimulationSpaceState.ComponentTM, SimulationSpaceState.BoneRelComponentTM); FTransform CollisionSpaceTM = GetSpaceTransform( SolverSettings.CollisionSpace, SimulationSpaceState.ComponentTM, SimulationSpaceState.BoneRelComponentTM); FTransform WorldSpaceTM = TM * CollisionSpaceTM; return WorldSpaceTM.GetRelativeTransform(SimSpaceTM); } //====================================================================================================================== void FRigPhysicsSimulation::InitSimulationSpace( const FTransform& ComponentTM, const FTransform& BoneRelComponentTM) { SimulationSpaceState.ComponentTM = ComponentTM; SimulationSpaceState.BoneRelComponentTM = BoneRelComponentTM; } //====================================================================================================================== // Danny TODO We need support for double precision Perlin inputs - for now duplicate. namespace FPerlinHelpers { // random permutation of 256 numbers, repeated 2x static const int32 Permutation[512] = { 63, 9, 212, 205, 31, 128, 72, 59, 137, 203, 195, 170, 181, 115, 165, 40, 116, 139, 175, 225, 132, 99, 222, 2, 41, 15, 197, 93, 169, 90, 228, 43, 221, 38, 206, 204, 73, 17, 97, 10, 96, 47, 32, 138, 136, 30, 219, 78, 224, 13, 193, 88, 134, 211, 7, 112, 176, 19, 106, 83, 75, 217, 85, 0, 98, 140, 229, 80, 118, 151, 117, 251, 103, 242, 81, 238, 172, 82, 110, 4, 227, 77, 243, 46, 12, 189, 34, 188, 200, 161, 68, 76, 171, 194, 57, 48, 247, 233, 51, 105, 5, 23, 42, 50, 216, 45, 239, 148, 249, 84, 70, 125, 108, 241, 62, 66, 64, 240, 173, 185, 250, 49, 6, 37, 26, 21, 244, 60, 223, 255, 16, 145, 27, 109, 58, 102, 142, 253, 120, 149, 160, 124, 156, 79, 186, 135, 127, 14, 121, 22, 65, 54, 153, 91, 213, 174, 24, 252, 131, 192, 190, 202, 208, 35, 94, 231, 56, 95, 183, 163, 111, 147, 25, 67, 36, 92, 236, 71, 166, 1, 187, 100, 130, 143, 237, 178, 158, 104, 184, 159, 177, 52, 214, 230, 119, 87, 114, 201, 179, 198, 3, 248, 182, 39, 11, 152, 196, 113, 20, 232, 69, 141, 207, 234, 53, 86, 180, 226, 74, 150, 218, 29, 133, 8, 44, 123, 28, 146, 89, 101, 154, 220, 126, 155, 122, 210, 168, 254, 162, 129, 33, 18, 209, 61, 191, 199, 157, 245, 55, 164, 167, 215, 246, 144, 107, 235, 63, 9, 212, 205, 31, 128, 72, 59, 137, 203, 195, 170, 181, 115, 165, 40, 116, 139, 175, 225, 132, 99, 222, 2, 41, 15, 197, 93, 169, 90, 228, 43, 221, 38, 206, 204, 73, 17, 97, 10, 96, 47, 32, 138, 136, 30, 219, 78, 224, 13, 193, 88, 134, 211, 7, 112, 176, 19, 106, 83, 75, 217, 85, 0, 98, 140, 229, 80, 118, 151, 117, 251, 103, 242, 81, 238, 172, 82, 110, 4, 227, 77, 243, 46, 12, 189, 34, 188, 200, 161, 68, 76, 171, 194, 57, 48, 247, 233, 51, 105, 5, 23, 42, 50, 216, 45, 239, 148, 249, 84, 70, 125, 108, 241, 62, 66, 64, 240, 173, 185, 250, 49, 6, 37, 26, 21, 244, 60, 223, 255, 16, 145, 27, 109, 58, 102, 142, 253, 120, 149, 160, 124, 156, 79, 186, 135, 127, 14, 121, 22, 65, 54, 153, 91, 213, 174, 24, 252, 131, 192, 190, 202, 208, 35, 94, 231, 56, 95, 183, 163, 111, 147, 25, 67, 36, 92, 236, 71, 166, 1, 187, 100, 130, 143, 237, 178, 158, 104, 184, 159, 177, 52, 214, 230, 119, 87, 114, 201, 179, 198, 3, 248, 182, 39, 11, 152, 196, 113, 20, 232, 69, 141, 207, 234, 53, 86, 180, 226, 74, 150, 218, 29, 133, 8, 44, 123, 28, 146, 89, 101, 154, 220, 126, 155, 122, 210, 168, 254, 162, 129, 33, 18, 209, 61, 191, 199, 157, 245, 55, 164, 167, 215, 246, 144, 107, 235 }; // Gradient functions for 1D, 2D and 3D Perlin noise FORCEINLINE float Grad1(int32 Hash, double X) { // Slicing Perlin's 3D improved noise would give us only scales of -1, 0 and 1; this looks pretty bad so let's use a different sampling static const double Grad1Scales[16] = { -8 / 8, -7 / 8., -6 / 8., -5 / 8., -4 / 8., -3 / 8., -2 / 8., -1 / 8., 1 / 8., 2 / 8., 3 / 8., 4 / 8., 5 / 8., 6 / 8., 7 / 8., 8 / 8 }; return Grad1Scales[Hash & 15] * X; } // Curve w/ second derivative vanishing at 0 and 1, from Perlin's improved noise paper FORCEINLINE double SmoothCurve(double X) { return X * X * X * (X * (X * 6.0 - 15.0) + 10.0); } FORCEINLINE float PerlinNoise1D(double X) { const double Xfl = FMath::FloorToFloat(X); const int64 Xi = (int64)(Xfl) & 255; X -= Xfl; const double Xm1 = X - 1.0; const int32 A = Permutation[Xi]; const int32 B = Permutation[Xi + 1]; const double U = SmoothCurve(X); // 2.0 factor to ensure (-1, 1) range return 2.0f * FMath::Lerp(Grad1(A, X), Grad1(B, Xm1), U); } } //====================================================================================================================== // Note - don't use the space conversion functions here as the state won't have been set yet. FRigPhysicsSimulation::FSimulationSpaceData FRigPhysicsSimulation::UpdateSimulationSpaceStateAndCalculateData( const FRigVMExecuteContext& ExecuteContext, const FRigPhysicsSolverComponent* SolverComponent, const float Dt) { const FRigPhysicsSolverSettings& SolverSettings = SolverComponent->SolverSettings; const FRigPhysicsSimulationSpaceSettings& SimulationSpaceSettings = SolverComponent->SimulationSpaceSettings; if (const USceneComponent* OwningSceneComponent = OwningControlRig->GetOwningSceneComponent()) { SimulationSpaceState.ComponentTM = OwningSceneComponent->GetComponentTransform(); } else { SimulationSpaceState.ComponentTM.SetIdentity(); } if (SolverSettings.SimulationSpace == ERigPhysicsSimulationSpace::SpaceBone && SolverSettings.SpaceBone.IsValid()) { URigHierarchy* Hierarchy = OwningControlRig->GetHierarchy(); SimulationSpaceState.BoneRelComponentTM = Hierarchy->GetGlobalTransform(SolverSettings.SpaceBone); } // Record the history - but avoid polluting it with zero Dt updates. What that means is - if we // get a zero-Dt update, then just update our current sim space TM, which means the time delta // from the previous state is actually the current Dt (i.e. don't overwrite the current Dt). if (Dt > SMALL_NUMBER) { SimulationSpaceState.PrevDt = SimulationSpaceState.Dt; SimulationSpaceState.Dt = Dt; SimulationSpaceState.PrevPrevSimulationSpaceTM = SimulationSpaceState.PrevSimulationSpaceTM; SimulationSpaceState.PrevSimulationSpaceTM = SimulationSpaceState.SimulationSpaceTM; } SimulationSpaceState.SimulationSpaceTM = GetSpaceTransform( SolverSettings.SimulationSpace, SimulationSpaceState.ComponentTM, SimulationSpaceState.BoneRelComponentTM); SimulationSpaceData = FSimulationSpaceData(); SimulationSpaceData.Gravity = ::ConvertWorldVectorToSimSpaceNoScale( SolverSettings.SimulationSpace, SolverSettings.Gravity, SimulationSpaceState.ComponentTM, SimulationSpaceState.BoneRelComponentTM); if (SolverSettings.SimulationSpace == ERigPhysicsSimulationSpace::World) { // Danny TODO This is probably redundant unless we support runtime switching of the space InitSimulationSpace(SimulationSpaceState.ComponentTM, SimulationSpaceState.BoneRelComponentTM); SimulationSpaceData.LinearVelocity = SimulationSpaceSettings.ExternalLinearVelocity; SimulationSpaceData.AngularVelocity = FMath::DegreesToRadians(SimulationSpaceSettings.ExternalAngularVelocity); return SimulationSpaceData; } // If the timestep is zero, then it doesn't actually matter what the velocity is - but make sure // it doesn't corrupt anything. if (SimulationSpaceState.Dt < SMALL_NUMBER) { SimulationSpaceData.LinearVelocity = FVector::ZeroVector; SimulationSpaceData.AngularVelocity = FVector::ZeroVector; SimulationSpaceData.LinearAcceleration = FVector::ZeroVector; SimulationSpaceData.AngularAcceleration = FVector::ZeroVector; return SimulationSpaceData; } // We calculate velocities etc in world space first, and then subsequently convert them into // simulation space. // Note that the velocity/accel calculations are intended to track the world/simulation behavior // - not necessarily be the most accurate calculations! For example, we could use one-sided // finite difference approximations, but this wouldn't necessarily be correct. // World-space component linear velocity and acceleration SimulationSpaceData.LinearVelocity = UE::PhysicsControl::CalculateLinearVelocity( SimulationSpaceState.PrevSimulationSpaceTM.GetTranslation(), SimulationSpaceState.SimulationSpaceTM.GetTranslation(), SimulationSpaceState.Dt); const FVector PrevSpaceLinearVel = SimulationSpaceState.PrevDt < SMALL_NUMBER ? SimulationSpaceData.LinearVelocity : UE::PhysicsControl::CalculateLinearVelocity( SimulationSpaceState.PrevPrevSimulationSpaceTM.GetTranslation(), SimulationSpaceState.PrevSimulationSpaceTM.GetTranslation(), SimulationSpaceState.PrevDt); SimulationSpaceData.LinearAcceleration = (SimulationSpaceData.LinearVelocity - PrevSpaceLinearVel) / SimulationSpaceState.Dt; // World-space component angular velocity and acceleration SimulationSpaceData.AngularVelocity = UE::PhysicsControl::CalculateAngularVelocity( SimulationSpaceState.PrevSimulationSpaceTM.GetRotation(), SimulationSpaceState.SimulationSpaceTM.GetRotation(), SimulationSpaceState.Dt); const FVector PrevSpaceAngularVel = SimulationSpaceState.PrevDt < SMALL_NUMBER ? SimulationSpaceData.AngularVelocity : UE::PhysicsControl::CalculateAngularVelocity( SimulationSpaceState.PrevPrevSimulationSpaceTM.GetRotation(), SimulationSpaceState.PrevSimulationSpaceTM.GetRotation(), SimulationSpaceState.PrevDt); SimulationSpaceData.AngularAcceleration = (SimulationSpaceData.AngularVelocity - PrevSpaceAngularVel) / SimulationSpaceState.Dt; // Apply Z scale SimulationSpaceData.LinearVelocity.Z *= SimulationSpaceSettings.VelocityScaleZ; SimulationSpaceData.LinearAcceleration.Z *= SimulationSpaceSettings.VelocityScaleZ; bool bLinearAccelerationTrigger = SimulationSpaceSettings.LinearAccelerationThresholdForTeleport > 0 && SimulationSpaceData.LinearAcceleration.SquaredLength() > FMath::Square(SimulationSpaceSettings.LinearAccelerationThresholdForTeleport); bool bAngularAccelerationTrigger = SimulationSpaceSettings.AngularAccelerationThresholdForTeleport > 0 && SimulationSpaceData.AngularAcceleration.SquaredLength() > FMath::Square(FMath::DegreesToRadians(SimulationSpaceSettings.AngularAccelerationThresholdForTeleport)); bool bPositionTrigger = SimulationSpaceSettings.PositionChangeThresholdForTeleport > 0 && SimulationSpaceData.LinearVelocity.SquaredLength() > FMath::Square(SimulationSpaceSettings.PositionChangeThresholdForTeleport / SimulationSpaceState.Dt); bool bOrientationTrigger = SimulationSpaceSettings.OrientationChangeThresholdForTeleport > 0 && SimulationSpaceData.AngularVelocity.SquaredLength() > FMath::Square(FMath::DegreesToRadians( SimulationSpaceSettings.OrientationChangeThresholdForTeleport / SimulationSpaceState.Dt)); // Clamped world-space motion of the simulation space if (bLinearAccelerationTrigger || bAngularAccelerationTrigger || bPositionTrigger || bOrientationTrigger) { if (bLinearAccelerationTrigger) { UE_LOG(LogRigPhysics, Log, TEXT("Detected linear Acceleration (%f > %f) teleport in %s"), SimulationSpaceData.LinearAcceleration.Length(), SimulationSpaceSettings.LinearAccelerationThresholdForTeleport, *OwningControlRig->GetName()); } if (bAngularAccelerationTrigger) { UE_LOG(LogRigPhysics, Log, TEXT("Detected angular Acceleration (%f > %f) teleport in %s"), SimulationSpaceData.AngularAcceleration.Length(), SimulationSpaceSettings.AngularAccelerationThresholdForTeleport, *OwningControlRig->GetName()); } if (bPositionTrigger) { UE_LOG(LogRigPhysics, Log, TEXT("Detected position (%f > %f) teleport in %s"), SimulationSpaceData.LinearVelocity.Length() * SimulationSpaceState.Dt, SimulationSpaceSettings.PositionChangeThresholdForTeleport, *OwningControlRig->GetName()); } if (bOrientationTrigger) { UE_LOG(LogRigPhysics, Log, TEXT("Detected orientation (%f > %f) teleport in %s"), FMath::RadiansToDegrees(SimulationSpaceData.AngularVelocity.Length() * SimulationSpaceState.Dt), SimulationSpaceSettings.OrientationChangeThresholdForTeleport, *OwningControlRig->GetName()); } // Note that a teleport detection shouldn't change the pose, or the current motion. We just // don't want to bring in that unwanted global motion. SimulationSpaceData.LinearVelocity = FVector::ZeroVector; SimulationSpaceData.AngularVelocity = FVector::ZeroVector; SimulationSpaceData.LinearAcceleration = FVector::ZeroVector; SimulationSpaceData.AngularAcceleration = FVector::ZeroVector; // This will stop the next step from using bogus values too. SimulationSpaceState.PrevSimulationSpaceTM = SimulationSpaceState.SimulationSpaceTM; SimulationSpaceState.PrevPrevSimulationSpaceTM = SimulationSpaceState.SimulationSpaceTM; SimulationSpaceState.PrevDt = 0; SimulationSpaceState.Dt = 0; // Avoid cached transforms being used in controls by bumping the update counter. UpdateCounter += 1; } else { if (SimulationSpaceSettings.bClampLinearVelocity) { SimulationSpaceData.LinearVelocity = SimulationSpaceData.LinearVelocity.GetClampedToMaxSize(SimulationSpaceSettings.MaxLinearVelocity); } if (SimulationSpaceSettings.bClampAngularVelocity) { SimulationSpaceData.AngularVelocity = SimulationSpaceData.AngularVelocity.GetClampedToMaxSize(SimulationSpaceSettings.MaxAngularVelocity); } if (SimulationSpaceSettings.bClampLinearAcceleration) { SimulationSpaceData.LinearAcceleration = SimulationSpaceData.LinearAcceleration.GetClampedToMaxSize(SimulationSpaceSettings.MaxLinearAcceleration); } if (SimulationSpaceSettings.bClampAngularAcceleration) { SimulationSpaceData.AngularAcceleration = SimulationSpaceData.AngularAcceleration.GetClampedToMaxSize(SimulationSpaceSettings.MaxAngularAcceleration); } } SimulationSpaceData.LinearVelocity += SimulationSpaceSettings.ExternalLinearVelocity; SimulationSpaceData.AngularVelocity += FMath::DegreesToRadians(SimulationSpaceSettings.ExternalAngularVelocity); if (!SimulationSpaceSettings.ExternalTurbulenceVelocity.IsNearlyZero()) { double GlobalTime = ExecuteContext.GetAbsoluteTime(); FVector T( FPerlinHelpers::PerlinNoise1D(GlobalTime), FPerlinHelpers::PerlinNoise1D(GlobalTime + 10.0), FPerlinHelpers::PerlinNoise1D(GlobalTime + 20.0)); FVector Turbulence = T * SimulationSpaceSettings.ExternalTurbulenceVelocity; SimulationSpaceData.LinearVelocity += Turbulence; if (GEngine && CVarControlRigPhysicsShowSimulationSpaceInfo.GetValueOnAnyThread()) { GEngine->AddOnScreenDebugMessage(INDEX_NONE, 0.f, FColor::Yellow, FString::Printf(TEXT("Sim %s Turbulence %s"), *OwningControlRig->GetName(), *Turbulence.ToString())); } } // Transform world-space motion into simulation space Danny TODO note that this matches the code // in RBAN, and is doing what the interface requires (i.e. movement of the space in the space of // the space!). SimulationSpaceData.LinearVelocity = SimulationSpaceState.SimulationSpaceTM.InverseTransformVector(SimulationSpaceData.LinearVelocity); SimulationSpaceData.AngularVelocity = SimulationSpaceState.SimulationSpaceTM.InverseTransformVector(SimulationSpaceData.AngularVelocity); SimulationSpaceData.LinearAcceleration = SimulationSpaceState.SimulationSpaceTM.InverseTransformVector(SimulationSpaceData.LinearAcceleration); SimulationSpaceData.AngularAcceleration = SimulationSpaceState.SimulationSpaceTM.InverseTransformVector(SimulationSpaceData.AngularAcceleration); if (GEngine && CVarControlRigPhysicsShowSimulationSpaceInfo.GetValueOnAnyThread()) { GEngine->AddOnScreenDebugMessage(INDEX_NONE, 0.f, FColor::Yellow, FString::Printf(TEXT("Sim %s dt %6.2fms Space P %35s V %35s A %35s"), *OwningControlRig->GetName(), Dt * 1000.0f, *SimulationSpaceState.SimulationSpaceTM.GetLocation().ToString(), *SimulationSpaceData.LinearVelocity.ToString(), *SimulationSpaceData.LinearAcceleration.ToString())); } return SimulationSpaceData; }