using BepuUtilities; using BepuUtilities.Memory; using BepuPhysics; using BepuPhysics.Collidables; using BepuPhysics.CollisionDetection; using BepuPhysics.Constraints; using System; using System.Runtime.CompilerServices; using System.Numerics; namespace Demos { public struct DemoPoseIntegratorCallbacks : IPoseIntegratorCallbacks { /// /// Gravity to apply to dynamic bodies in the simulation. /// public Vector3 Gravity; /// /// Fraction of dynamic body linear velocity to remove per unit of time. Values range from 0 to 1. 0 is fully undamped, while values very close to 1 will remove most velocity. /// public float LinearDamping; /// /// Fraction of dynamic body angular velocity to remove per unit of time. Values range from 0 to 1. 0 is fully undamped, while values very close to 1 will remove most velocity. /// public float AngularDamping; Vector3 gravityDt; float linearDampingDt; float angularDampingDt; public AngularIntegrationMode AngularIntegrationMode => AngularIntegrationMode.Nonconserving; public void Initialize(Simulation simulation) { //In this demo, we don't need to initialize anything. //If you had a simulation with per body gravity stored in a CollidableProperty or something similar, having the simulation provided in a callback can be helpful. } /// /// Creates a new set of simple callbacks for the demos. /// /// Gravity to apply to dynamic bodies in the simulation. /// Fraction of dynamic body linear velocity to remove per unit of time. Values range from 0 to 1. 0 is fully undamped, while values very close to 1 will remove most velocity. /// Fraction of dynamic body angular velocity to remove per unit of time. Values range from 0 to 1. 0 is fully undamped, while values very close to 1 will remove most velocity. public DemoPoseIntegratorCallbacks(Vector3 gravity, float linearDamping = .03f, float angularDamping = .03f) : this() { Gravity = gravity; LinearDamping = linearDamping; AngularDamping = angularDamping; } public void PrepareForIntegration(float dt) { //No reason to recalculate gravity * dt for every body; just cache it ahead of time. gravityDt = Gravity * dt; //Since these callbacks don't use per-body damping values, we can precalculate everything. linearDampingDt = MathF.Pow(MathHelper.Clamp(1 - LinearDamping, 0, 1), dt); angularDampingDt = MathF.Pow(MathHelper.Clamp(1 - AngularDamping, 0, 1), dt); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void IntegrateVelocity(int bodyIndex, in RigidPose pose, in BodyInertia localInertia, int workerIndex, ref BodyVelocity velocity) { //Note that we avoid accelerating kinematics. Kinematics are any body with an inverse mass of zero (so a mass of ~infinity). No force can move them. if (localInertia.InverseMass > 0) { velocity.Linear = (velocity.Linear + gravityDt) * linearDampingDt; velocity.Angular = velocity.Angular * angularDampingDt; } //Implementation sidenote: Why aren't kinematics all bundled together separately from dynamics to avoid this per-body condition? //Because kinematics can have a velocity- that is what distinguishes them from a static object. The solver must read velocities of all bodies involved in a constraint. //Under ideal conditions, those bodies will be near in memory to increase the chances of a cache hit. If kinematics are separately bundled, the the number of cache //misses necessarily increases. Slowing down the solver in order to speed up the pose integrator is a really, really bad trade, especially when the benefit is a few ALU ops. //Note that you CAN technically modify the pose in IntegrateVelocity by directly accessing it through the Simulation.Bodies.ActiveSet.Poses, it just requires a little care and isn't directly exposed. //If the PositionFirstTimestepper is being used, then the pose integrator has already integrated the pose. //If the PositionLastTimestepper or SubsteppingTimestepper are in use, the pose has not yet been integrated. //If your pose modification depends on the order of integration, you'll want to take this into account. //This is also a handy spot to implement things like position dependent gravity or per-body damping. } } public struct DemoNarrowPhaseCallbacks : INarrowPhaseCallbacks { public SpringSettings ContactSpringiness; public void Initialize(Simulation simulation) { //Use a default if the springiness value wasn't initialized. if (ContactSpringiness.AngularFrequency == 0 && ContactSpringiness.TwiceDampingRatio == 0) ContactSpringiness = new SpringSettings(30, 1); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool AllowContactGeneration(int workerIndex, CollidableReference a, CollidableReference b) { //While the engine won't even try creating pairs between statics at all, it will ask about kinematic-kinematic pairs. //Those pairs cannot emit constraints since both involved bodies have infinite inertia. Since most of the demos don't need //to collect information about kinematic-kinematic pairs, we'll require that at least one of the bodies needs to be dynamic. return a.Mobility == CollidableMobility.Dynamic || b.Mobility == CollidableMobility.Dynamic; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool AllowContactGeneration(int workerIndex, CollidablePair pair, int childIndexA, int childIndexB) { return true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool ConfigureContactManifold(int workerIndex, CollidablePair pair, ref TManifold manifold, out PairMaterialProperties pairMaterial) where TManifold : struct, IContactManifold { pairMaterial.FrictionCoefficient = 1f; pairMaterial.MaximumRecoveryVelocity = 2f; pairMaterial.SpringSettings = ContactSpringiness; return true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool ConfigureContactManifold(int workerIndex, CollidablePair pair, int childIndexA, int childIndexB, ref ConvexContactManifold manifold) { return true; } public void Dispose() { } } }