/* * 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); }
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); } } }
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); } }
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); }
/* * 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"); } }
/* * 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); }
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"); } } }
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]; }