예제 #1
0
        /*
         * Provides raycast data based on where a SphereCast would have contacted
         * the specified normal.
         * Raycasting downwards from a point along the controller's bottom sphere,
         * based on the provided normal.
         */
        private bool SimulateSphereCast(CollisionSphere collisionSphere, Vector3 groundNormal, out RaycastHit hit)
        {
            float groundAngle = Vector3.Angle(groundNormal, playerView.Up) * Mathf.Deg2Rad;

            Vector3 secondaryOrigin = playerView.Position + (playerView.Up * settings.tolerance);

            if (!Mathf.Approximately(groundAngle, 0))
            {
                float horizontal = Mathf.Sin(groundAngle) * collisionSphere.Radius;
                float vertical   = (1f - Mathf.Cos(groundAngle)) * collisionSphere.Radius;

                Vector3 upslopeDirection = -CollisionMath.DownslopeDirection(groundNormal, playerView.Down);

                Vector3 horizontalDirection = Math3d.ProjectVectorOnPlane(playerView.Up, upslopeDirection).normalized;
                secondaryOrigin += horizontalDirection * horizontal + playerView.Up * vertical;
            }

            if (SphereCast(secondaryOrigin, settings.epsilon, playerView.Down, out hit))
            {
                hit.distance -= settings.tolerance;
                return(true);
            }

            return(false);
        }
예제 #2
0
        private void HandleEdgeCollision(CollisionSphere collisionSphere, RaycastHit hit)
        {
            Vector3 towardCenter = Math3d.ProjectVectorOnPlane(
                playerView.Up,
                (playerView.Position - hit.point).normalized * settings.epsilon
                );

            Quaternion planeAwayRotation = Quaternion.AngleAxis(
                settings.edgeCollisionRotateDegree,
                Vector3.Cross(towardCenter, playerView.Up)
                );

            Vector3 awayCenter = planeAwayRotation * -towardCenter;

            Vector3 nearPoint = hit.point + towardCenter + (playerView.Up * settings.epsilon);
            Vector3 farPoint  = hit.point + (awayCenter * settings.edgeCollisionFarPointMultiplier);

            RaycastHit nearHit;
            RaycastHit farHit;

            Raycast(nearPoint, playerView.Down, out nearHit);
            Raycast(farPoint, playerView.Down, out farHit);

            nearGround = new GroundHit(nearHit);
            farGround  = new GroundHit(farHit);

            // If we are standing on surface that should be counted as a
            // wall, attempt to flush against it on the ground
            if (Vector3.Angle(hit.normal, playerView.Up) > collidable.StandAngle)
            {
                FlushGround(collisionSphere, hit);
            }

            // If we are standing on a ledge then face the nearest center of
            // the player view, which should be steep enough to be counted as
            // a wall. Retrieve the ground it is connected to at its base, if
            // there is one.
            if ((Vector3.Angle(nearHit.normal, playerView.Up) > collidable.StandAngle) || (nearHit.distance > settings.tolerance))
            {
                Collidable nearCollidable = GetCollidable(nearHit);

                if (Vector3.Angle(nearHit.normal, playerView.Up) > nearCollidable.StandAngle)
                {
                    Vector3 downslopeDirection = CollisionMath.DownslopeDirection(nearHit.normal, playerView.Down);

                    RaycastHit stepHit;

                    if (Raycast(nearPoint, downslopeDirection, out stepHit))
                    {
                        stepGround = new GroundHit(stepHit);
                    }
                }
                else
                {
                    stepGround = new GroundHit(nearHit);
                }
            }
        }
예제 #3
0
        private void HandleClippingCollision(CollisionSphere collisionSphere, RaycastHit hit)
        {
            collidable = GetCollidable(hit);
            transform  = hit.transform;

            RaycastHit sphereCastHit;

            if (SimulateSphereCast(collisionSphere, hit.normal, out sphereCastHit))
            {
                primaryGround = new GroundHit(sphereCastHit);
            }
            else
            {
                primaryGround = new GroundHit(hit);
            }
        }
예제 #4
0
        public bool IsGrounded(CollisionSphere collisionSphere, bool currentlyGrounded, float distance)
        {
            if (!primaryGround.isFound || primaryGround.distance > distance)
            {
                return(false);
            }

            if (farGround.isFound)
            {
                // Check if flushing against wall
                if (!IsGroundStandable(farGround))
                {
                    if (flushGround.isFound &&
                        IsGroundStandable(flushGround) &&
                        flushGround.distance < distance)
                    {
                        return(true);
                    }

                    return(false);
                }

                // Check if at edge of ledge, or high angle slope
                if (!OnSteadyGround(collisionSphere, farGround.normal, primaryGround.point))
                {
                    if (nearGround.isFound &&
                        nearGround.distance < distance &&
                        IsGroundStandable(nearGround) &&
                        !OnSteadyGround(collisionSphere, nearGround.normal, nearGround.point))
                    {
                        return(true);
                    }

                    if (stepGround.isFound &&
                        stepGround.distance < distance &&
                        IsGroundStandable(stepGround))
                    {
                        return(true);
                    }

                    return(false);
                }
            }

            return(true);
        }
예제 #5
0
        /*
         * Scans surface below controller for ground. Follows up the initial
         * scan with subsequent scans to handle different edge cases.
         */
        public void ProbeGround(CollisionSphere collisionSphere)
        {
            ResetGrounds();

            Vector3 origin = collisionSphere.Position + (playerView.Up * settings.tolerance);

            // Reduce radius to avoid failing SphereCast due to wall clipping
            float smallerRadius = collisionSphere.Radius - (settings.tolerance * settings.tolerance);

            RaycastHit hit;

            if (SphereCast(origin, smallerRadius, playerView.Down, out hit))
            {
                collidable = GetCollidable(hit);
                transform  = hit.transform;

                SimulateSphereCast(collisionSphere, hit.normal, out hit);

                primaryGround = new GroundHit(hit);

                // If we are standing on a perfectly flat surface, we cannot be
                // either on an edge, on a slope, or stepping off a ledge
                Vector3 surface = Math3d.ProjectPointOnPlane(playerView.Up, playerView.Position, hit.point);

                if (Vector3.Distance(surface, playerView.Position) < settings.epsilon)
                {
                    return;
                }

                // Now the collision is on an edge, so normals of the two faces
                // on either side of the edge are computed and stored in nearHit
                // and farHit
                HandleEdgeCollision(collisionSphere, hit);
            }
            // If initial SphereCast fails, which is likely due to controller
            // clipping a wall, fallback to raycast simulating SphereCast data
            else if (Raycast(origin, playerView.Down, out hit))
            {
                HandleClippingCollision(collisionSphere, hit);
            }
            else
            {
                Debug.Log("No ground found below player");
            }
        }
예제 #6
0
        /*
         * Checks if the ground below player is "steady", or that the standing
         * surface is not too extreme of an ledge, allowing player to smoothly
         * fall off surfaces and not hang on edge of ledges
         */
        private bool OnSteadyGround(CollisionSphere collisionSphere, Vector3 normal, Vector3 point)
        {
            float angle = Vector3.Angle(normal, playerView.Up);

            float angleRatio = angle / settings.upperBoundAngle;

            float distanceRatio = Mathf.Lerp(
                settings.minPercentFromCenter,
                settings.maxPercentFromCenter,
                angleRatio
                );

            Vector3 projection = Math3d.ProjectPointOnPlane(playerView.Up, playerView.Position, point);

            float distanceFromCenter = Vector3.Distance(projection, playerView.Position);

            return(distanceFromCenter <= distanceRatio * collisionSphere.Radius);
        }
예제 #7
0
        private void FlushGround(CollisionSphere collisionSphere, RaycastHit hit)
        {
            Vector3 downslopeDirection = CollisionMath.DownslopeDirection(hit.normal, playerView.Down);

            Vector3 flushOrigin = hit.point + (hit.normal * settings.epsilon);

            RaycastHit flushHit;

            if (Raycast(flushOrigin, downslopeDirection, out flushHit))
            {
                RaycastHit sphereCastHit;

                if (SimulateSphereCast(collisionSphere, flushHit.normal, out sphereCastHit))
                {
                    flushGround = new GroundHit(sphereCastHit);
                }
                else
                {
                    Debug.Log("Failed to flush against ground");
                }
            }
        }
예제 #8
0
        private void CreateCollisionSpheres()
        {
            List <CollisionSphere> newCollisionSpheres = new List <CollisionSphere>();

            foreach (CollisionSphereRequest request in settings.collisionSphereRequests)
            {
                maxCollisionSphereRadius = Mathf.Max(maxCollisionSphereRadius, request.radius);

                CollisionSphere collisionSphere = collisionSphereFactory.Create(playerView, request);
                newCollisionSpheres.Add(collisionSphere);
            }

            if (newCollisionSpheres.Count == 0)
            {
                maxCollisionSphereRadius = settings.defaultCollisionSphereRequest.radius;

                CollisionSphere collisionSphere = collisionSphereFactory.Create(playerView, settings.defaultCollisionSphereRequest);
                newCollisionSpheres.Add(collisionSphere);
            }

            collisionSpheres = new ReadOnlyCollection <CollisionSphere>(newCollisionSpheres);
            feetSphere       = collisionSpheres[0];
            headSphere       = collisionSpheres[collisionSpheres.Count - 1];
        }