예제 #1
0
            public void Execute()
            {
                ColliderCastInput colliderCastInput = new ColliderCastInput {
                    Collider    = Collider,
                    Orientation = Orientation,
                    Start       = Start,
                    End         = End
                };

                if (CollectAllHits)
                {
                    World.CastCollider(colliderCastInput, ref ColliderCastHits);
                }
                else if (World.CastCollider(colliderCastInput, out ColliderCastHit hit))
                {
                    ColliderCastHits.Add(hit);
                }
            }
예제 #2
0
        public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)
        {
            ColliderCastInput colliderCastInput = new ColliderCastInput
            {
                Collider    = Collider,
                Orientation = Orientation,
                Start       = Start,
                End         = End
            };

            if (CollectAllHits)
            {
                World.CastCollider(colliderCastInput, ref ColliderCastHits);
            }
            else if (World.CastCollider(colliderCastInput, out ColliderCastHit hit))
            {
                ColliderCastHits.Add(hit);
            }
        }
예제 #3
0
            public void Execute()
            {
                var colliderCastInput = new ColliderCastInput
                {
                    Collider    = (Collider *)collider.GetUnsafePtr(),
                    Start       = origin + new float3(0f, 1f, 0f),
                    End         = origin,
                    Orientation = quaternion.identity
                };

                if (!hits.IsCreated)
                {
                    hits = new NativeList <ColliderCastHit>(Allocator.TempJob);
                }
                hasHit = physicsWorld.CastCollider(colliderCastInput, ref hits);
            }
예제 #4
0
        public void RotatedColliderCast_Against_RotatedBox_Test(
            [Values(0f, 10f, -20f, 30f, 40f)] float startX)
        {
            // Simulate the physics.
            SimulatePhysics();

            var aabb = RotatedBox.Aabb;

            Assert.IsTrue(startX < aabb.Min.x);

            var start            = new float2(startX, 0f);
            var expectedPosition = new float2(aabb.Min.x + ColliderEpsilon, 0f);

            // Set-up the query.
            var geometry = new BoxGeometry {
                Size = new float2(BoxSize)
            };

            using (var colliderBlob = PhysicsBoxCollider.Create(geometry, CollisionFilter.Default, PhysicsMaterial.Default))
            {
                var input = new ColliderCastInput
                {
                    Start = start,
                    End   = expectedPosition,

                    Rotation = float2x2.Rotate(math.radians(BoxRotation)),
                    Collider = colliderBlob
                };

                // Perform the query.
                var results = PhysicsWorld.CastCollider(input, out ColliderCastHit hit);
                Assert.IsTrue(results);
                Assert.IsTrue(hit.PhysicsBodyIndex != PhysicsBody.Constants.InvalidBodyIndex, "PhysicsBody Index is Invalid.");
                Assert.AreEqual(hit.Entity, RotatedBox.Entity, "Entity is invalid.");
                PhysicsAssert.AreEqual(expectedPosition, hit.Position, QueryEpsilon, "Hit position is incorrect.");
            }
        }
예제 #5
0
    public static unsafe void CollideAndIntegrate(PhysicsWorld world, float deltaTime,
                                                  int maxIterations, float3 up, float3 gravity,
                                                  float characterMass, float tau, float damping, float maxSlope, bool affectBodies, Collider *collider,
                                                  ref NativeArray <DistanceHit> distanceHits, ref NativeArray <ColliderCastHit> castHits, ref NativeArray <SurfaceConstraintInfo> constraints,
                                                  ref RigidTransform transform, ref float3 linearVelocity, ref BlockStream.Writer deferredImpulseWriter)
    {
        float  remainingTime    = deltaTime;
        float3 lastDisplacement = linearVelocity * remainingTime;

        float3     newPosition = transform.pos;
        quaternion orientation = transform.rot;
        float3     newVelocity = linearVelocity;

        float maxSlopeCos = math.cos(maxSlope);

        const float timeEpsilon = 0.000001f;

        for (int i = 0; i < maxIterations && remainingTime > timeEpsilon; i++)
        {
            // First do distance query for penetration recovery
            MaxHitsCollector <DistanceHit> distanceHitsCollector = new MaxHitsCollector <DistanceHit>(0.0f, ref distanceHits);
            int numConstraints = 0;
            {
                ColliderDistanceInput input = new ColliderDistanceInput()
                {
                    MaxDistance = 0.0f,
                    Transform   = new RigidTransform
                    {
                        pos = newPosition,
                        rot = orientation,
                    },
                    Collider = collider
                };
                world.CalculateDistance(input, ref distanceHitsCollector);

                // Iterate over hits and create constraints from them
                for (int hitIndex = 0; hitIndex < distanceHitsCollector.NumHits; hitIndex++)
                {
                    DistanceHit hit = distanceHitsCollector.AllHits[hitIndex];
                    CreateConstraintFromHit(world, gravity, deltaTime, hit.RigidBodyIndex, hit.ColliderKey, hit.Position,
                                            hit.SurfaceNormal, hit.Distance, false, out SurfaceConstraintInfo constraint);

                    // Potentially add a max slope constraint
                    AddMaxSlopeConstraint(up, maxSlopeCos, ref constraint, ref constraints, ref numConstraints);

                    // Add original constraint to the list
                    constraints[numConstraints++] = constraint;
                }
            }

            float3 gravityMovement = gravity * remainingTime * remainingTime * 0.5f;

            // Then do a collider cast
            {
                float3 displacement = lastDisplacement + gravityMovement;
                float3 endPosition  = newPosition + displacement;
                MaxHitsCollector <ColliderCastHit> collector = new MaxHitsCollector <ColliderCastHit>(1.0f, ref castHits);
                ColliderCastInput input = new ColliderCastInput()
                {
                    Collider    = collider,
                    Orientation = orientation,
                    Position    = newPosition,
                    Direction   = displacement
                };
                world.CastCollider(input, ref collector);

                // Iterate over hits and create constraints from them
                for (int hitIndex = 0; hitIndex < collector.NumHits; hitIndex++)
                {
                    ColliderCastHit hit = collector.AllHits[hitIndex];

                    bool found = false;
                    for (int distanceHitIndex = 0; distanceHitIndex < distanceHitsCollector.NumHits; distanceHitIndex++)
                    {
                        DistanceHit dHit = distanceHitsCollector.AllHits[distanceHitIndex];
                        if (dHit.RigidBodyIndex == hit.RigidBodyIndex &&
                            dHit.ColliderKey.Equals(hit.ColliderKey))
                        {
                            found = true;
                            break;
                        }
                    }

                    // Skip duplicate hits
                    if (!found)
                    {
                        CreateConstraintFromHit(world, gravity, deltaTime, hit.RigidBodyIndex, hit.ColliderKey, hit.Position, hit.SurfaceNormal,
                                                hit.Fraction * math.length(lastDisplacement), false, out SurfaceConstraintInfo constraint);

                        // Potentially add a max slope constraint
                        AddMaxSlopeConstraint(up, maxSlopeCos, ref constraint, ref constraints, ref numConstraints);

                        // Add original constraint to the list
                        constraints[numConstraints++] = constraint;
                    }
                }
            }

            // Solve
            float3 prevVelocity = newVelocity;
            float3 prevPosition = newPosition;
            SimplexSolver.Solve(world, remainingTime, up, numConstraints, ref constraints, ref newPosition, ref newVelocity, out float integratedTime);

            // Apply impulses to hit bodies
            if (affectBodies)
            {
                ResolveContacts(world, remainingTime, gravity, tau, damping, characterMass, prevVelocity, numConstraints, ref constraints, ref deferredImpulseWriter);
            }

            float3 newDisplacement = newPosition - prevPosition;

            // Check if we can walk to the position simplex solver has suggested
            MaxHitsCollector <ColliderCastHit> newCollector = new MaxHitsCollector <ColliderCastHit>(1.0f, ref castHits);
            int newContactIndex = -1;

            // If simplex solver moved the character we need to re-cast to make sure it can move to new position
            if (math.lengthsq(newDisplacement) > SimplexSolver.c_SimplexSolverEpsilon)
            {
                float3            displacement = newDisplacement + gravityMovement;
                float3            endPosition  = prevPosition + displacement;
                ColliderCastInput input        = new ColliderCastInput()
                {
                    Collider    = collider,
                    Orientation = orientation,
                    Position    = prevPosition,
                    Direction   = displacement
                };

                world.CastCollider(input, ref newCollector);

                for (int hitIndex = 0; hitIndex < newCollector.NumHits; hitIndex++)
                {
                    ColliderCastHit hit = newCollector.AllHits[hitIndex];

                    bool found = false;
                    for (int constraintIndex = 0; constraintIndex < numConstraints; constraintIndex++)
                    {
                        SurfaceConstraintInfo constraint = constraints[constraintIndex];
                        if (constraint.RigidBodyIndex == hit.RigidBodyIndex &&
                            constraint.ColliderKey.Equals(hit.ColliderKey))
                        {
                            found = true;
                            break;
                        }
                    }

                    if (!found)
                    {
                        newContactIndex = hitIndex;
                        break;
                    }
                }
            }

            // Move character along the newDisplacement direction until it reaches this new contact
            if (newContactIndex >= 0)
            {
                ColliderCastHit newContact = newCollector.AllHits[newContactIndex];

                float fraction = newContact.Fraction / math.length(newDisplacement);
                integratedTime *= fraction;

                float3 displacement = newDisplacement * fraction;
                newPosition = prevPosition + displacement;
            }

            remainingTime -= integratedTime;

            // Remember last displacement for next iteration
            lastDisplacement = newVelocity * remainingTime;
        }

        // Write back position and velocity
        transform.pos  = newPosition;
        linearVelocity = newVelocity;
    }
예제 #6
0
    public static unsafe void CollideAndIntegrate(
        CharacterControllerStepInput stepInput, float characterMass, bool affectBodies, Unity.Physics.Collider *collider,
        ref RigidTransform transform, ref float3 linearVelocity, ref NativeStream.Writer deferredImpulseWriter,
        NativeList <StatefulCollisionEvent> collisionEvents = default, NativeList <StatefulTriggerEvent> triggerEvents = default)
    {
        // Copy parameters
        float        deltaTime = stepInput.DeltaTime;
        float3       up        = stepInput.Up;
        PhysicsWorld world     = stepInput.World;

        float remainingTime = deltaTime;

        float3     newPosition = transform.pos;
        quaternion orientation = transform.rot;
        float3     newVelocity = linearVelocity;

        float maxSlopeCos = math.cos(stepInput.MaxSlope);

        const float timeEpsilon = 0.000001f;

        for (int i = 0; i < stepInput.MaxIterations && remainingTime > timeEpsilon; i++)
        {
            NativeList <SurfaceConstraintInfo> constraints = new NativeList <SurfaceConstraintInfo>(k_DefaultConstraintsCapacity, Allocator.Temp);

            // Do a collider cast
            {
                float3 displacement = newVelocity * remainingTime;
                NativeList <ColliderCastHit> triggerHits = default;
                if (triggerEvents.IsCreated)
                {
                    triggerHits = new NativeList <ColliderCastHit>(k_DefaultQueryHitsCapacity / 4, Allocator.Temp);
                }
                NativeList <ColliderCastHit> castHits = new NativeList <ColliderCastHit>(k_DefaultQueryHitsCapacity, Allocator.Temp);
                CharacterControllerAllHitsCollector <ColliderCastHit> collector = new CharacterControllerAllHitsCollector <ColliderCastHit>(
                    stepInput.RigidBodyIndex, 1.0f, ref castHits, world, triggerHits);
                ColliderCastInput input = new ColliderCastInput()
                {
                    Collider    = collider,
                    Orientation = orientation,
                    Start       = newPosition,
                    End         = newPosition + displacement
                };
                world.CastCollider(input, ref collector);

                // Iterate over hits and create constraints from them
                for (int hitIndex = 0; hitIndex < collector.NumHits; hitIndex++)
                {
                    ColliderCastHit hit = collector.AllHits[hitIndex];
                    CreateConstraint(stepInput.World, stepInput.Up,
                                     hit.RigidBodyIndex, hit.ColliderKey, hit.Position, hit.SurfaceNormal, math.dot(-hit.SurfaceNormal, hit.Fraction * displacement),
                                     stepInput.SkinWidth, maxSlopeCos, ref constraints);
                }

                // Update trigger events
                if (triggerEvents.IsCreated)
                {
                    UpdateTriggersSeen(stepInput, triggerHits, triggerEvents, collector.MinHitFraction);
                }
            }

            // Then do a collider distance for penetration recovery,
            // but only fix up penetrating hits
            {
                // Collider distance query
                NativeList <DistanceHit> distanceHits = new NativeList <DistanceHit>(k_DefaultQueryHitsCapacity, Allocator.Temp);
                CharacterControllerAllHitsCollector <DistanceHit> distanceHitsCollector = new CharacterControllerAllHitsCollector <DistanceHit>(
                    stepInput.RigidBodyIndex, stepInput.ContactTolerance, ref distanceHits, world);
                {
                    ColliderDistanceInput input = new ColliderDistanceInput()
                    {
                        MaxDistance = stepInput.ContactTolerance,
                        Transform   = transform,
                        Collider    = collider
                    };
                    world.CalculateDistance(input, ref distanceHitsCollector);
                }

                // Iterate over penetrating hits and fix up distance and normal
                int numConstraints = constraints.Length;
                for (int hitIndex = 0; hitIndex < distanceHitsCollector.NumHits; hitIndex++)
                {
                    DistanceHit hit = distanceHitsCollector.AllHits[hitIndex];
                    if (hit.Distance < stepInput.SkinWidth)
                    {
                        bool found = false;

                        // Iterate backwards to locate the original constraint before the max slope constraint
                        for (int constraintIndex = numConstraints - 1; constraintIndex >= 0; constraintIndex--)
                        {
                            SurfaceConstraintInfo constraint = constraints[constraintIndex];
                            if (constraint.RigidBodyIndex == hit.RigidBodyIndex &&
                                constraint.ColliderKey.Equals(hit.ColliderKey))
                            {
                                // Fix up the constraint (normal, distance)
                                {
                                    // Create new constraint
                                    CreateConstraintFromHit(world, hit.RigidBodyIndex, hit.ColliderKey,
                                                            hit.Position, hit.SurfaceNormal, hit.Distance,
                                                            stepInput.SkinWidth, out SurfaceConstraintInfo newConstraint);

                                    // Resolve its penetration
                                    ResolveConstraintPenetration(ref newConstraint);

                                    // Write back
                                    constraints[constraintIndex] = newConstraint;
                                }

                                found = true;
                                break;
                            }
                        }

                        // Add penetrating hit not caught by collider cast
                        if (!found)
                        {
                            CreateConstraint(stepInput.World, stepInput.Up,
                                             hit.RigidBodyIndex, hit.ColliderKey, hit.Position, hit.SurfaceNormal, hit.Distance,
                                             stepInput.SkinWidth, maxSlopeCos, ref constraints);
                        }
                    }
                }
            }

            // Min delta time for solver to break
            float minDeltaTime = 0.0f;
            if (math.lengthsq(newVelocity) > k_SimplexSolverEpsilonSq)
            {
                // Min delta time to travel at least 1cm
                minDeltaTime = 0.01f / math.length(newVelocity);
            }

            // Solve
            float3 prevVelocity = newVelocity;
            float3 prevPosition = newPosition;
            SimplexSolver.Solve(remainingTime, minDeltaTime, up, stepInput.MaxMovementSpeed, constraints, ref newPosition, ref newVelocity, out float integratedTime);

            // Apply impulses to hit bodies and store collision events
            if (affectBodies || collisionEvents.IsCreated)
            {
                CalculateAndStoreDeferredImpulsesAndCollisionEvents(stepInput, affectBodies, characterMass,
                                                                    prevVelocity, constraints, ref deferredImpulseWriter, collisionEvents);
            }

            // Calculate new displacement
            float3 newDisplacement = newPosition - prevPosition;

            // If simplex solver moved the character we need to re-cast to make sure it can move to new position
            if (math.lengthsq(newDisplacement) > k_SimplexSolverEpsilon)
            {
                // Check if we can walk to the position simplex solver has suggested
                var newCollector = new CharacterControllerClosestHitCollector <ColliderCastHit>(constraints, world, stepInput.RigidBodyIndex, 1.0f);

                ColliderCastInput input = new ColliderCastInput()
                {
                    Collider    = collider,
                    Orientation = orientation,
                    Start       = prevPosition,
                    End         = prevPosition + newDisplacement
                };

                world.CastCollider(input, ref newCollector);

                if (newCollector.NumHits > 0)
                {
                    ColliderCastHit hit = newCollector.ClosestHit;

                    // Move character along the newDisplacement direction until it reaches this new contact
                    {
                        Assert.IsTrue(hit.Fraction >= 0.0f && hit.Fraction <= 1.0f);

                        integratedTime *= hit.Fraction;
                        newPosition     = prevPosition + newDisplacement * hit.Fraction;
                    }
                }
            }

            // Reduce remaining time
            remainingTime -= integratedTime;

            // Write back position so that the distance query will update results
            transform.pos = newPosition;
        }

        // Write back final velocity
        linearVelocity = newVelocity;
    }
예제 #7
0
    public static unsafe void CheckSupport(
        ref PhysicsWorld world, ref PhysicsCollider collider, CharacterControllerStepInput stepInput, RigidTransform transform,
        out CharacterSupportState characterState, out float3 surfaceNormal, out float3 surfaceVelocity,
        NativeList <StatefulCollisionEvent> collisionEvents = default)
    {
        surfaceNormal   = float3.zero;
        surfaceVelocity = float3.zero;

        // Up direction must be normalized
        Assert.IsTrue(Unity.Physics.Math.IsNormalized(stepInput.Up));

        // Query the world
        NativeList <ColliderCastHit> castHits = new NativeList <ColliderCastHit>(k_DefaultQueryHitsCapacity, Allocator.Temp);
        CharacterControllerAllHitsCollector <ColliderCastHit> castHitsCollector = new CharacterControllerAllHitsCollector <ColliderCastHit>(
            stepInput.RigidBodyIndex, 1.0f, ref castHits, world);
        var maxDisplacement = -stepInput.ContactTolerance * stepInput.Up;

        {
            ColliderCastInput input = new ColliderCastInput()
            {
                Collider    = collider.ColliderPtr,
                Orientation = transform.rot,
                Start       = transform.pos,
                End         = transform.pos + maxDisplacement
            };

            world.CastCollider(input, ref castHitsCollector);
        }

        // If no hits, proclaim unsupported state
        if (castHitsCollector.NumHits == 0)
        {
            characterState = CharacterSupportState.Unsupported;
            return;
        }

        float maxSlopeCos = math.cos(stepInput.MaxSlope);

        // Iterate over distance hits and create constraints from them
        NativeList <SurfaceConstraintInfo> constraints = new NativeList <SurfaceConstraintInfo>(k_DefaultConstraintsCapacity, Allocator.Temp);
        float maxDisplacementLength = math.length(maxDisplacement);

        for (int i = 0; i < castHitsCollector.NumHits; i++)
        {
            ColliderCastHit hit = castHitsCollector.AllHits[i];
            CreateConstraint(stepInput.World, stepInput.Up,
                             hit.RigidBodyIndex, hit.ColliderKey, hit.Position, hit.SurfaceNormal, hit.Fraction * maxDisplacementLength,
                             stepInput.SkinWidth, maxSlopeCos, ref constraints);
        }

        // Velocity for support checking
        float3 initialVelocity = maxDisplacement / stepInput.DeltaTime;

        // Solve downwards (don't use min delta time, try to solve full step)
        float3 outVelocity = initialVelocity;
        float3 outPosition = transform.pos;

        SimplexSolver.Solve(stepInput.DeltaTime, stepInput.DeltaTime, stepInput.Up, stepInput.MaxMovementSpeed,
                            constraints, ref outPosition, ref outVelocity, out float integratedTime, false);

        // Get info on surface
        int numSupportingPlanes = 0;
        {
            for (int j = 0; j < constraints.Length; j++)
            {
                var constraint = constraints[j];
                if (constraint.Touched && !constraint.IsTooSteep && !constraint.IsMaxSlope)
                {
                    numSupportingPlanes++;
                    surfaceNormal   += constraint.Plane.Normal;
                    surfaceVelocity += constraint.Velocity;

                    // Add supporting planes to collision events
                    if (collisionEvents.IsCreated)
                    {
                        var collisionEvent = new StatefulCollisionEvent(stepInput.World.Bodies[stepInput.RigidBodyIndex].Entity,
                                                                        stepInput.World.Bodies[constraint.RigidBodyIndex].Entity, stepInput.RigidBodyIndex, constraint.RigidBodyIndex,
                                                                        ColliderKey.Empty, constraint.ColliderKey, constraint.Plane.Normal);
                        collisionEvent.CollisionDetails = new StatefulCollisionEvent.Details(1, 0, constraint.HitPosition);
                        collisionEvents.Add(collisionEvent);
                    }
                }
            }

            if (numSupportingPlanes > 0)
            {
                float invNumSupportingPlanes = 1.0f / numSupportingPlanes;
                surfaceNormal   *= invNumSupportingPlanes;
                surfaceVelocity *= invNumSupportingPlanes;

                surfaceNormal = math.normalize(surfaceNormal);
            }
        }

        // Check support state
        {
            if (math.lengthsq(initialVelocity - outVelocity) < k_SimplexSolverEpsilonSq)
            {
                // If velocity hasn't changed significantly, declare unsupported state
                characterState = CharacterSupportState.Unsupported;
            }
            else if (math.lengthsq(outVelocity) < k_SimplexSolverEpsilonSq && numSupportingPlanes > 0)
            {
                // If velocity is very small, declare supported state
                characterState = CharacterSupportState.Supported;
            }
            else
            {
                // Check if sliding
                outVelocity = math.normalize(outVelocity);
                float slopeAngleSin   = math.max(0.0f, math.dot(outVelocity, -stepInput.Up));
                float slopeAngleCosSq = 1 - slopeAngleSin * slopeAngleSin;
                if (slopeAngleCosSq <= maxSlopeCos * maxSlopeCos)
                {
                    characterState = CharacterSupportState.Sliding;
                }
                else if (numSupportingPlanes > 0)
                {
                    characterState = CharacterSupportState.Supported;
                }
                else
                {
                    // If numSupportingPlanes is 0, surface normal is invalid, so state is unsupported
                    characterState = CharacterSupportState.Unsupported;
                }
            }
        }
    }
    public static unsafe void CollideAndIntegrate(
        CharacterControllerStepInput stepInput, float characterMass, bool affectBodies, Collider *collider,
        MaxHitsCollector <DistanceHit> distanceHitsCollector, ref NativeArray <ColliderCastHit> castHits, ref NativeArray <SurfaceConstraintInfo> constraints,
        ref RigidTransform transform, ref float3 linearVelocity, ref BlockStream.Writer deferredImpulseWriter)
    {
        // Copy parameters
        float        deltaTime = stepInput.DeltaTime;
        float3       gravity   = stepInput.Gravity;
        float3       up        = stepInput.Up;
        PhysicsWorld world     = stepInput.World;

        float  remainingTime    = deltaTime;
        float3 lastDisplacement = linearVelocity * remainingTime;

        float3     newPosition = transform.pos;
        quaternion orientation = transform.rot;
        float3     newVelocity = linearVelocity;

        float maxSlopeCos = math.cos(stepInput.MaxSlope);

        // Iterate over hits and create constraints from them
        int numDistanceConstraints = 0;

        for (int hitIndex = 0; hitIndex < distanceHitsCollector.NumHits; hitIndex++)
        {
            DistanceHit hit = distanceHitsCollector.AllHits[hitIndex];
            CreateConstraintFromHit(world, gravity, deltaTime, hit.RigidBodyIndex, hit.ColliderKey, hit.Position,
                                    hit.SurfaceNormal, hit.Distance, false, out SurfaceConstraintInfo constraint);

            // Check if max slope plane is required
            float verticalComponent = math.dot(constraint.Plane.Normal, up);
            bool  shouldAddPlane    = verticalComponent > SimplexSolver.c_SimplexSolverEpsilon && verticalComponent < maxSlopeCos;
            if (shouldAddPlane)
            {
                AddMaxSlopeConstraint(up, ref constraint, ref constraints, ref numDistanceConstraints);
            }

            // Add original constraint to the list
            constraints[numDistanceConstraints++] = constraint;
        }

        const float timeEpsilon = 0.000001f;

        for (int i = 0; i < stepInput.MaxIterations && remainingTime > timeEpsilon; i++)
        {
            int    numConstraints  = numDistanceConstraints;
            float3 gravityMovement = gravity * remainingTime * remainingTime * 0.5f;

            // Then do a collider cast (but not in first iteration)
            if (i > 0)
            {
                float3 displacement = lastDisplacement + gravityMovement;
                MaxHitsCollector <ColliderCastHit> collector = new MaxHitsCollector <ColliderCastHit>(1.0f, ref castHits);
                ColliderCastInput input = new ColliderCastInput()
                {
                    Collider    = collider,
                    Orientation = orientation,
                    Start       = newPosition,
                    End         = newPosition + displacement,
                };
                world.CastCollider(input, ref collector);

                // Iterate over hits and create constraints from them
                for (int hitIndex = 0; hitIndex < collector.NumHits; hitIndex++)
                {
                    ColliderCastHit hit = collector.AllHits[hitIndex];

                    bool found = false;
                    for (int distanceHitIndex = 0; distanceHitIndex < distanceHitsCollector.NumHits; distanceHitIndex++)
                    {
                        DistanceHit dHit = distanceHitsCollector.AllHits[distanceHitIndex];
                        if (dHit.RigidBodyIndex == hit.RigidBodyIndex &&
                            dHit.ColliderKey.Equals(hit.ColliderKey))
                        {
                            found = true;
                            break;
                        }
                    }

                    // Skip duplicate hits
                    if (!found)
                    {
                        CreateConstraintFromHit(world, gravity, deltaTime, hit.RigidBodyIndex, hit.ColliderKey, hit.Position, hit.SurfaceNormal,
                                                hit.Fraction * math.length(lastDisplacement), false, out SurfaceConstraintInfo constraint);

                        // Check if max slope plane is required
                        float verticalComponent = math.dot(constraint.Plane.Normal, up);
                        bool  shouldAddPlane    = verticalComponent > SimplexSolver.c_SimplexSolverEpsilon && verticalComponent < maxSlopeCos;
                        if (shouldAddPlane)
                        {
                            AddMaxSlopeConstraint(up, ref constraint, ref constraints, ref numConstraints);
                        }

                        // Add original constraint to the list
                        constraints[numConstraints++] = constraint;
                    }
                }
            }

            // Solve
            float3 prevVelocity = newVelocity;
            float3 prevPosition = newPosition;
            SimplexSolver.Solve(world, remainingTime, up, numConstraints, ref constraints, ref newPosition, ref newVelocity, out float integratedTime);

            // Apply impulses to hit bodies
            if (affectBodies)
            {
                CalculateAndStoreDeferredImpulses(stepInput, characterMass, prevVelocity, numConstraints, ref constraints, ref deferredImpulseWriter);
            }

            float3 newDisplacement = newPosition - prevPosition;

            // Check if we can walk to the position simplex solver has suggested
            MaxHitsCollector <ColliderCastHit> newCollector = new MaxHitsCollector <ColliderCastHit>(1.0f, ref castHits);
            int newContactIndex = -1;

            // If simplex solver moved the character we need to re-cast to make sure it can move to new position
            if (math.lengthsq(newDisplacement) > SimplexSolver.c_SimplexSolverEpsilon)
            {
                float3            displacement = newDisplacement + gravityMovement;
                ColliderCastInput input        = new ColliderCastInput()
                {
                    Collider    = collider,
                    Orientation = orientation,
                    Start       = prevPosition,
                    End         = prevPosition + displacement
                };

                world.CastCollider(input, ref newCollector);

                for (int hitIndex = 0; hitIndex < newCollector.NumHits; hitIndex++)
                {
                    ColliderCastHit hit = newCollector.AllHits[hitIndex];

                    bool found = false;
                    for (int constraintIndex = 0; constraintIndex < numConstraints; constraintIndex++)
                    {
                        SurfaceConstraintInfo constraint = constraints[constraintIndex];
                        if (constraint.RigidBodyIndex == hit.RigidBodyIndex &&
                            constraint.ColliderKey.Equals(hit.ColliderKey))
                        {
                            found = true;
                            break;
                        }
                    }

                    if (!found)
                    {
                        newContactIndex = hitIndex;
                        break;
                    }
                }
            }

            // Move character along the newDisplacement direction until it reaches this new contact
            if (newContactIndex >= 0)
            {
                ColliderCastHit newContact = newCollector.AllHits[newContactIndex];

                Assert.IsTrue(newContact.Fraction >= 0.0f && newContact.Fraction <= 1.0f);

                integratedTime *= newContact.Fraction;
                newPosition     = prevPosition + newDisplacement * newContact.Fraction;
            }

            remainingTime -= integratedTime;

            // Remember last displacement for next iteration
            lastDisplacement = newVelocity * remainingTime;
        }

        // Write back position and velocity
        transform.pos  = newPosition;
        linearVelocity = newVelocity;
    }
예제 #9
0
        protected unsafe override void OnUpdate()
        {
            PhysicsWorld physicsWorld = _worldBuildSystem.PhysicsWorld;
            NativeMultiHashMap <Entity, HitboxCollision> collisions = _collisions;

            collisions.Clear();
            var collisionWriter = collisions.AsParallelWriter();

            Entities
            .WithName("QueryHitboxCollisons")
            .WithReadOnly(physicsWorld)
            .ForEach((Entity entity, ref Hitbox hitbox, ref HitboxState state, in LocalToWorld transform) => {
                if (!state.Enabled)
                {
                    return;
                }

                BlobAssetReference <Collider> collider =
                    SphereCollider.Create(new SphereGeometry {
                    Center = float3.zero,
                    Radius = hitbox.Radius
                }, new CollisionFilter {
                    BelongsTo    = (uint)PhysicsLayers.HITBOX,
                    CollidesWith = (uint)PhysicsLayers.HITBOX,
                });

                float3 position = math.transform(transform.Value, float3.zero);
                var hits        = new NativeList <ColliderCastHit>(Allocator.Temp);
                physicsWorld.CastCollider(new ColliderCastInput {
                    Collider    = (Collider *)collider.GetUnsafePtr(),
                    Start       = state.PreviousPosition ?? position,
                    End         = position,
                    Orientation = float4.zero
                }, ref hits);
                collider.Dispose();

                state.PreviousPosition = position;

                for (var i = 0; i < hits.Length; i++)
                {
                    var hit = hits[i];
                    if (!HasComponent <Hurtbox>(hit.Entity))
                    {
                        continue;
                    }
                    var hurtbox = GetComponent <Hurtbox>(hit.Entity);
                    if (!hurtbox.Enabled || hurtbox.Player == Entity.Null || hurtbox.Player == state.Player)
                    {
                        continue;
                    }

                    collisionWriter.Add(hurtbox.Player, new HitboxCollision {
                        OriginPlayer = entity,
                        Hitbox       = hitbox,
                        Hurtbox      = hurtbox,
                    });
                }
            }).ScheduleParallel();

            Entities
            .WithName("ApplyCollisions")
            .WithReadOnly(collisions)
            .ForEach((Entity entity, ref PlayerComponent player, in PlayerConfig config, in CharacterFrame frame) => {
                NativeArray <HitboxCollision>?playerCollisions = collisions.CopyValuesForKey(entity);
                if (playerCollisions == null)
                {
                    return;
                }

                // Sort collisions
                playerCollisions.Value.Sort();
            }).Schedule();
    public static unsafe void CheckSupport(
        ref PhysicsWorld world, Collider *collider, CharacterControllerStepInput stepInput, float3 groundProbeVector, RigidTransform transform, float maxSlope,
        ref NativeList <SurfaceConstraintInfo> constraints, ref NativeList <ColliderCastHit> castHits,
        out CharacterSupportState characterState, out float3 surfaceNormal, out float3 surfaceVelocity)
    {
        surfaceNormal   = float3.zero;
        surfaceVelocity = float3.zero;

        //查询物理世界
        var displacement = groundProbeVector;
        SelfFilteringAllHitsCollector <ColliderCastHit> hitCollector = new SelfFilteringAllHitsCollector <ColliderCastHit>(stepInput.RigidBodyIndex, 1.0f, ref castHits);
        ColliderCastInput input = new ColliderCastInput()
        {
            Collider    = collider,
            Orientation = transform.rot,
            Start       = transform.pos,
            End         = transform.pos + displacement
        };

        world.CastCollider(input, ref hitCollector);
        //如果没检测到任何碰撞体,那么可以肯定是非支持状态
        if (hitCollector.NumHits == 0)
        {
            characterState = CharacterSupportState.Unsupported;
            return;
        }

        float3 downwardsDirection = -stepInput.Up;

        Assert.IsTrue(Unity.Physics.Math.IsNormalized(downwardsDirection));

        float maxSlopCos = math.cos(maxSlope);

        for (int i = 0; i < hitCollector.NumHits; i++)
        {
            var hit = hitCollector.AllHits[i];
            CreateConstraint(stepInput.World, stepInput.Up,
                             hit.RigidBodyIndex, hit.ColliderKey, hit.Position, hit.SurfaceNormal, hit.Fraction * math.length(displacement),
                             stepInput.SkinWidth, maxSlopCos, ref constraints);
        }

        float3 initialVelocity = groundProbeVector * (1.0f / stepInput.DeltaTime);//初速度(期望在1秒内到达目标)

        float3 outVelocity = initialVelocity;
        float3 outPosition = transform.pos;

        SimplexSolver.Solve(stepInput.World, stepInput.DeltaTime, stepInput.DeltaTime, stepInput.Up, stepInput.MaxMovementSpeed,
                            constraints, ref outPosition, ref outVelocity, out float integratedTime, false);
        {
            int numSupportingPlanes = 0;
            for (int i = 0; i < constraints.Length; i++)
            {
                var constraint = constraints[i];
                if (constraint.Touched && !constraint.IsTooSteep)
                {
                    numSupportingPlanes++;
                    surfaceNormal   += constraint.Plane.Normal;
                    surfaceVelocity += constraint.Velocity;
                }
            }

            if (numSupportingPlanes > 0)
            {
                float invNumSupportingPlanes = 1.0f / numSupportingPlanes;
                surfaceNormal   *= invNumSupportingPlanes;
                surfaceVelocity *= invNumSupportingPlanes;

                surfaceNormal = math.normalize(surfaceNormal);
            }
        }

        {
            if (math.lengthsq(initialVelocity - outVelocity) < k_SimplexSolverEpsilonSq)
            {
                //如果速度没有显著改变,那么肯定是未支持状态?
                characterState = CharacterSupportState.Unsupported;
            }
            else if (math.lengthsq(outVelocity) < k_SimplexSolverEpsilonSq)
            {
                //如果速度非常小,那么肯定是支持状态
                characterState = CharacterSupportState.Supported;
            }
            else
            {
                //滑行和支持状态
                outVelocity = math.normalize(outVelocity);
                float slopeAngleSin   = math.max(0.0f, math.dot(outVelocity, downwardsDirection));
                float slopeAngleCosSq = 1 - slopeAngleSin * slopeAngleSin;
                if (slopeAngleCosSq < maxSlopCos * maxSlopCos)
                {
                    characterState = CharacterSupportState.Sliding;
                }
                else
                {
                    characterState = CharacterSupportState.Supported;
                }
            }
        }
    }
예제 #11
0
    public static unsafe void CheckSupport(
        ref PhysicsWorld world, Collider *collider, CharacterControllerStepInput stepInput, float3 groundProbeVector, RigidTransform transform, float maxSlope,
        ref NativeList <SurfaceConstraintInfo> constraints, ref NativeList <ColliderCastHit> castHits,
        out CharacterSupportState characterState, out float3 surfaceNormal, out float3 surfaceVelocity)
    {
        surfaceNormal   = float3.zero;
        surfaceVelocity = float3.zero;

        // Query the world
        var displacement = groundProbeVector;
        SelfFilteringAllHitsCollector <ColliderCastHit> hitCollector = new SelfFilteringAllHitsCollector <ColliderCastHit>(stepInput.RigidBodyIndex, 1.0f, ref castHits);
        ColliderCastInput input = new ColliderCastInput()
        {
            Collider    = collider,
            Orientation = transform.rot,
            Start       = transform.pos,
            End         = transform.pos + displacement
        };

        world.CastCollider(input, ref hitCollector);

        // If no hits, proclaim unsupported state
        if (hitCollector.NumHits == 0)
        {
            characterState = CharacterSupportState.Unsupported;
            return;
        }

        // Downwards direction must be normalized
        float3 downwardsDirection = -stepInput.Up;

        Assert.IsTrue(Unity.Physics.Math.IsNormalized(downwardsDirection));

        float maxSlopeCos = math.cos(maxSlope);

        // Iterate over distance hits and create constraints from them
        for (int i = 0; i < hitCollector.NumHits; i++)
        {
            var hit = hitCollector.AllHits[i];
            CreateConstraint(stepInput.World, stepInput.Up,
                             hit.RigidBodyIndex, hit.ColliderKey, hit.Position, hit.SurfaceNormal, hit.Fraction * math.length(displacement),
                             stepInput.SkinWidth, maxSlopeCos, ref constraints);
        }

        float3 initialVelocity = groundProbeVector * (1.0f / stepInput.DeltaTime);

        // Solve downwards (don't use min delta time, try to solve full step)
        float3 outVelocity = initialVelocity;
        float3 outPosition = transform.pos;

        SimplexSolver.Solve(stepInput.World, stepInput.DeltaTime, stepInput.DeltaTime, stepInput.Up, stepInput.MaxMovementSpeed,
                            constraints, ref outPosition, ref outVelocity, out float integratedTime, false);

        // Get info on surface
        {
            int numSupportingPlanes = 0;
            for (int j = 0; j < constraints.Length; j++)
            {
                var constraint = constraints[j];
                if (constraint.Touched && !constraint.IsTooSteep)
                {
                    numSupportingPlanes++;
                    surfaceNormal   += constraint.Plane.Normal;
                    surfaceVelocity += constraint.Velocity;
                }
            }

            if (numSupportingPlanes > 0)
            {
                float invNumSupportingPlanes = 1.0f / numSupportingPlanes;
                surfaceNormal   *= invNumSupportingPlanes;
                surfaceVelocity *= invNumSupportingPlanes;

                surfaceNormal = math.normalize(surfaceNormal);
            }
        }

        // Check support state
        {
            if (math.lengthsq(initialVelocity - outVelocity) < k_SimplexSolverEpsilonSq)
            {
                // If velocity hasn't changed significantly, declare unsupported state
                characterState = CharacterSupportState.Unsupported;
            }
            else if (math.lengthsq(outVelocity) < k_SimplexSolverEpsilonSq)
            {
                // If velocity is very small, declare supported state
                characterState = CharacterSupportState.Supported;
            }
            else
            {
                // Check if sliding or supported
                outVelocity = math.normalize(outVelocity);
                float slopeAngleSin   = math.max(0.0f, math.dot(outVelocity, downwardsDirection));
                float slopeAngleCosSq = 1 - slopeAngleSin * slopeAngleSin;
                if (slopeAngleCosSq < maxSlopeCos * maxSlopeCos)
                {
                    characterState = CharacterSupportState.Sliding;
                }
                else
                {
                    characterState = CharacterSupportState.Supported;
                }
            }
        }
    }
    public static unsafe void CollideAndIntegrate(
        CharacterControllerStepInput stepInput, float characterMass, bool affectBodies, Collider *collider,
        ref NativeArray <ColliderCastHit> castHits, ref NativeArray <SurfaceConstraintInfo> constraints, int numConstraints,
        ref RigidTransform transform, ref float3 linearVelocity, ref BlockStream.Writer deferredImpulseWriter)
    {
        // Copy parameters
        float        deltaTime = stepInput.DeltaTime;
        float3       gravity   = stepInput.Gravity;
        float3       up        = stepInput.Up;
        PhysicsWorld world     = stepInput.World;

        float  remainingTime    = deltaTime;
        float3 lastDisplacement = linearVelocity * remainingTime;

        float3     newPosition = transform.pos;
        quaternion orientation = transform.rot;
        float3     newVelocity = linearVelocity;

        float maxSlopeCos = math.cos(stepInput.MaxSlope);

        const float timeEpsilon = 0.000001f;

        for (int i = 0; i < stepInput.MaxIterations && remainingTime > timeEpsilon; i++)
        {
            float3 gravityMovement = gravity * remainingTime * remainingTime * 0.5f;

            // Then do a collider cast (but not in first iteration)
            if (i > 0)
            {
                int numCastConstraints = 0;

                float3 displacement = lastDisplacement + gravityMovement;
                MaxHitsCollector <ColliderCastHit> collector = new MaxHitsCollector <ColliderCastHit>(stepInput.RigidBodyIndex, 1.0f, ref castHits);
                ColliderCastInput input = new ColliderCastInput()
                {
                    Collider    = collider,
                    Orientation = orientation,
                    Start       = newPosition,
                    End         = newPosition + displacement * (1.0f + stepInput.ContactTolerance),
                };
                world.CastCollider(input, ref collector);

                // Iterate over hits and create constraints from them
                for (int hitIndex = 0; hitIndex < collector.NumHits; hitIndex++)
                {
                    ColliderCastHit hit = collector.AllHits[hitIndex];
                    CreateConstraint(stepInput.World, stepInput.Up,
                                     hit.RigidBodyIndex, hit.ColliderKey, hit.Position, hit.SurfaceNormal, hit.Fraction * math.length(lastDisplacement),
                                     stepInput.SkinWidth, maxSlopeCos, ref constraints, ref numCastConstraints);
                }

                numConstraints = numCastConstraints;
            }

            // Min delta time for solver to break
            float minDeltaTime = 0.0f;
            if (math.lengthsq(newVelocity) > k_SimplexSolverEpsilonSq)
            {
                // Min delta time to travel at least 1cm
                minDeltaTime = 0.01f / math.length(newVelocity);
            }

            // Solve
            float3 prevVelocity = newVelocity;
            float3 prevPosition = newPosition;
            SimplexSolver.Solve(world, remainingTime, minDeltaTime, up, numConstraints, ref constraints, ref newPosition, ref newVelocity, out float integratedTime);

            // Apply impulses to hit bodies
            if (affectBodies)
            {
                CalculateAndStoreDeferredImpulses(stepInput, characterMass, prevVelocity, numConstraints, ref constraints, ref deferredImpulseWriter);
            }

            float3 newDisplacement = newPosition - prevPosition;

            // Check if we can walk to the position simplex solver has suggested
            MaxHitsCollector <ColliderCastHit> newCollector = new MaxHitsCollector <ColliderCastHit>(stepInput.RigidBodyIndex, 1.0f, ref castHits);
            int newContactIndex = -1;

            // If simplex solver moved the character we need to re-cast to make sure it can move to new position
            if (math.lengthsq(newDisplacement) > k_SimplexSolverEpsilon)
            {
                float3            displacement = newDisplacement + gravityMovement;
                ColliderCastInput input        = new ColliderCastInput()
                {
                    Collider    = collider,
                    Orientation = orientation,
                    Start       = prevPosition,
                    End         = prevPosition + displacement * (1.0f + stepInput.ContactTolerance)
                };

                world.CastCollider(input, ref newCollector);
                float minFraction = float.MaxValue;

                for (int hitIndex = 0; hitIndex < newCollector.NumHits; hitIndex++)
                {
                    ColliderCastHit hit = newCollector.AllHits[hitIndex];
                    if (hit.Fraction < minFraction)
                    {
                        bool found = false;
                        for (int constraintIndex = 0; constraintIndex < numConstraints; constraintIndex++)
                        {
                            SurfaceConstraintInfo constraint = constraints[constraintIndex];
                            if (constraint.RigidBodyIndex == hit.RigidBodyIndex &&
                                constraint.ColliderKey.Equals(hit.ColliderKey))
                            {
                                found = true;
                                break;
                            }
                        }

                        if (!found)
                        {
                            minFraction     = hit.Fraction;
                            newContactIndex = hitIndex;
                        }
                    }
                }
            }

            // Move character along the newDisplacement direction until it reaches this new contact
            if (newContactIndex >= 0)
            {
                ColliderCastHit newContact = newCollector.AllHits[newContactIndex];

                Assert.IsTrue(newContact.Fraction >= 0.0f && newContact.Fraction <= 1.0f);

                integratedTime *= newContact.Fraction;
                newPosition     = prevPosition + newDisplacement * newContact.Fraction;
            }

            remainingTime -= integratedTime;

            // Remember last displacement for next iteration
            lastDisplacement = newVelocity * remainingTime;
        }

        // Write back position and velocity
        transform.pos  = newPosition;
        linearVelocity = newVelocity;
    }
예제 #13
0
        public static unsafe void SolveCollisionConstraints(PhysicsWorld world, float deltaTime, int maxIterations, float skinWidth, float maxSlope, Collider *collider, ref RigidTransform transform, ref float3 velocity, ref NativeArray <DistanceHit> distanceHits, ref NativeArray <ColliderCastHit> colliderHits, ref NativeArray <SurfaceConstraintInfo> surfaceConstraints)
        {
            float  remainingTime        = deltaTime;
            float3 previousDisplacement = velocity * remainingTime;

            float3 outPosition = transform.pos;
            float3 outVelocity = velocity;

            quaternion orientation = transform.rot;

            const float timeEpsilon = 0.000001f;

            for (int i = 0; i < maxIterations && remainingTime > timeEpsilon; i++)
            {
                MaxHitCollector <DistanceHit> distanceHitCollector = new MaxHitCollector <DistanceHit>(skinWidth, ref distanceHits);

                int constraintCount = 0;

                // Handle distance checks
                {
                    ColliderDistanceInput input = new ColliderDistanceInput
                    {
                        Collider    = collider,
                        MaxDistance = skinWidth,
                        Transform   = new RigidTransform
                        {
                            pos = outPosition,
                            rot = orientation
                        }
                    };
                    world.CalculateDistance(input, ref distanceHitCollector);

                    for (int hitIndex = 0; hitIndex < distanceHitCollector.NumHits; hitIndex++)
                    {
                        DistanceHit hit = distanceHitCollector.AllHits[hitIndex];
                        CreateConstraintFromHit(world, hit.ColliderKey, hit.RigidBodyIndex, hit.Position, float3.zero, hit.SurfaceNormal, hit.Distance, deltaTime, out SurfaceConstraintInfo constraint);
                        CreateSlopeConstraint(math.up(), math.cos(maxSlope), ref constraint, ref surfaceConstraints, ref constraintCount);
                        surfaceConstraints[constraintCount++] = constraint;
                    }
                }

                // Handle Collider
                {
                    float3 displacement = previousDisplacement;
                    MaxHitCollector <ColliderCastHit> colliderHitCollector = new MaxHitCollector <ColliderCastHit>(1.0f, ref colliderHits);

                    ColliderCastInput input = new ColliderCastInput
                    {
                        Collider    = collider,
                        Position    = outPosition,
                        Direction   = velocity,
                        Orientation = orientation
                    };
                    world.CastCollider(input, ref colliderHitCollector);

                    for (int hitIndex = 0; hitIndex < colliderHitCollector.NumHits; hitIndex++)
                    {
                        ColliderCastHit hit = colliderHitCollector.AllHits[hitIndex];

                        bool duplicate = false;
                        for (int distanceHitIndex = 0; distanceHitIndex < distanceHitCollector.NumHits; distanceHitIndex++)
                        {
                            DistanceHit distanceHit = distanceHitCollector.AllHits[distanceHitIndex];
                            if (distanceHit.RigidBodyIndex == hit.RigidBodyIndex && distanceHit.ColliderKey.Equals(hit.ColliderKey))
                            {
                                duplicate = true;
                                break;
                            }
                        }

                        if (!duplicate)
                        {
                            CreateConstraintFromHit(world, hit.ColliderKey, hit.RigidBodyIndex, hit.Position, outVelocity, hit.SurfaceNormal, hit.Fraction * math.length(previousDisplacement), deltaTime, out SurfaceConstraintInfo constraint);
                            CreateSlopeConstraint(math.up(), math.cos(maxSlope), ref constraint, ref surfaceConstraints, ref constraintCount);
                            surfaceConstraints[constraintCount++] = constraint;
                        }
                    }
                }

                float3 previousPosition = outPosition;
                float3 previousVelocity = outVelocity;

                SimplexSolver.Solve(world, remainingTime, math.up(), constraintCount, ref surfaceConstraints, ref outPosition, ref outVelocity, out float integratedTime);

                float3 currentDisplacement = outPosition - previousPosition;

                MaxHitCollector <ColliderCastHit> displacementHitCollector = new MaxHitCollector <ColliderCastHit>(1.0f, ref colliderHits);
                int displacementContactIndex = -1;

                if (math.lengthsq(currentDisplacement) > SimplexSolver.c_SimplexSolverEpsilon)
                {
                    ColliderCastInput input = new ColliderCastInput
                    {
                        Collider    = collider,
                        Position    = previousPosition,
                        Direction   = currentDisplacement,
                        Orientation = orientation
                    };
                    world.CastCollider(input, ref displacementHitCollector);

                    for (int hitIndex = 0; hitIndex < distanceHitCollector.NumHits; hitIndex++)
                    {
                        ColliderCastHit hit = displacementHitCollector.AllHits[hitIndex];

                        bool duplicate = false;
                        for (int constrainIndex = 0; constrainIndex < constraintCount; constrainIndex++)
                        {
                            SurfaceConstraintInfo constraint = surfaceConstraints[constrainIndex];
                            if (constraint.RigidBodyIndex == hit.RigidBodyIndex && constraint.ColliderKey.Equals(hit.ColliderKey))
                            {
                                duplicate = true;
                                break;
                            }

                            if (!duplicate)
                            {
                                displacementContactIndex = hitIndex;
                                break;
                            }
                        }
                    }

                    if (displacementContactIndex >= 0)
                    {
                        ColliderCastHit newContact = displacementHitCollector.AllHits[displacementContactIndex];

                        float fraction = newContact.Fraction / math.length(currentDisplacement);
                        integratedTime *= fraction;

                        float3 displacement = currentDisplacement * fraction;
                        outPosition = previousPosition + displacement;
                    }
                }

                remainingTime -= integratedTime;

                previousDisplacement = outVelocity * remainingTime;
            }

            transform.pos = outPosition;
            velocity      = outVelocity;
        }
    public static unsafe void CollideAndIntegrate(PhysicsWorld world, float deltaTime,
                                                  int maxIterations, float3 up, float3 gravity,
                                                  float characterMass, float tau, float damping, bool affectBodies, Collider *collider,
                                                  ref NativeArray <DistanceHit> distanceHits, ref NativeArray <ColliderCastHit> castHits, ref NativeArray <SurfaceConstraintInfo> constraints,
                                                  ref RigidTransform transform, ref float3 linearVelocity, ref BlockStream.Writer deferredImpulseWriter)
    {
        float  remainingTime    = deltaTime;
        float3 lastDisplacement = linearVelocity * remainingTime;

        float3     newPosition = transform.pos;
        quaternion orientation = transform.rot;
        float3     newVelocity = linearVelocity;

        const float timeEpsilon = 0.000001f;

        for (int i = 0; i < maxIterations && remainingTime > timeEpsilon; i++)
        {
            // First do distance query for penetration recovery
            MaxHitsCollector <DistanceHit> distanceHitsCollector = new MaxHitsCollector <DistanceHit>(0.0f, ref distanceHits);
            int numConstraints = 0;
            {
                ColliderDistanceInput input = new ColliderDistanceInput()
                {
                    MaxDistance = 0.0f,
                    Transform   = new RigidTransform
                    {
                        pos = newPosition,
                        rot = orientation,
                    },
                    Collider = collider
                };
                world.CalculateDistance(input, ref distanceHitsCollector);

                // Iterate over hits and create constraints from them
                for (int hitIndex = 0; hitIndex < distanceHitsCollector.NumHits; hitIndex++)
                {
                    DistanceHit hit = distanceHitsCollector.AllHits[hitIndex];
                    CreateConstraintFromHit(world, gravity, deltaTime, hit.RigidBodyIndex, hit.ColliderKey, hit.Position,
                                            hit.SurfaceNormal, hit.Distance, false, out SurfaceConstraintInfo constraint);
                    constraints[numConstraints++] = constraint;
                }
            }

            // Then do a collider cast
            {
                float3 displacement = lastDisplacement - up * timeEpsilon;
                float3 endPosition  = newPosition + displacement;
                MaxHitsCollector <ColliderCastHit> collector = new MaxHitsCollector <ColliderCastHit>(1.0f, ref castHits);
                ColliderCastInput input = new ColliderCastInput()
                {
                    Collider    = collider,
                    Orientation = orientation,
                    Position    = newPosition,
                    Direction   = displacement
                };
                world.CastCollider(input, ref collector);

                // Iterate over hits and create constraints from them
                for (int hitIndex = 0; hitIndex < collector.NumHits; hitIndex++)
                {
                    ColliderCastHit hit = collector.AllHits[hitIndex];

                    bool found = false;
                    for (int distanceHitIndex = 0; distanceHitIndex < distanceHitsCollector.NumHits; distanceHitIndex++)
                    {
                        DistanceHit dHit = distanceHitsCollector.AllHits[distanceHitIndex];
                        if (dHit.RigidBodyIndex == hit.RigidBodyIndex &&
                            dHit.ColliderKey.Equals(hit.ColliderKey))
                        {
                            found = true;
                            break;
                        }
                    }

                    // Skip duplicate hits
                    if (!found)
                    {
                        CreateConstraintFromHit(world, gravity, deltaTime, hit.RigidBodyIndex, hit.ColliderKey, hit.Position, hit.SurfaceNormal,
                                                hit.Fraction * math.length(lastDisplacement), false, out SurfaceConstraintInfo constraint);
                        constraints[numConstraints++] = constraint;
                    }
                }
            }

            // petarm.todo: Add max slope plane to avoid climbing the not allowed slopes

            // Solve
            float3 prevVelocity = newVelocity;
            SimplexSolver.Solve(world, deltaTime, up, numConstraints, ref constraints, ref newPosition, ref newVelocity, out float integratedTime);

            remainingTime   -= integratedTime;
            lastDisplacement = newVelocity * remainingTime;

            // Apply impulses to hit bodies
            if (affectBodies)
            {
                ResolveContacts(world, deltaTime, gravity, tau, damping, characterMass, prevVelocity, numConstraints, ref constraints, ref deferredImpulseWriter);
            }
        }

        // Write back position and velocity
        transform.pos  = newPosition;
        linearVelocity = newVelocity;
    }