CharacterContactPositionState TrySupportLocation(ConvexCollidable <CylinderShape> queryObject, ref Vector3 position, out float hintOffset, ref QuickList <CharacterContact> tractionContacts, ref QuickList <CharacterContact> supportContacts, ref QuickList <CharacterContact> sideContacts, ref QuickList <CharacterContact> headContacts) { hintOffset = 0; PrepareQueryObject(queryObject, ref position); QueryManager.QueryContacts(queryObject, ref tractionContacts, ref supportContacts, ref sideContacts, ref headContacts, true); bool obstructed = IsObstructed(ref sideContacts, ref headContacts); if (obstructed) { return(CharacterContactPositionState.Obstructed); } if (supportContacts.Count > 0) { CharacterContactPositionState supportState; CharacterContact supportContact; QueryManager.AnalyzeSupportState(ref tractionContacts, ref supportContacts, out supportState, out supportContact); var down = characterBody.orientationMatrix.Down; //Note that traction is not tested for; it isn't important for the stance manager. if (supportState == CharacterContactPositionState.Accepted) { //We're done! The guess found a good spot to stand on. //We need to have fairly good contacts after this process, so only push it up a bit. hintOffset = Math.Min(0, Vector3.Dot(supportContact.Contact.Normal, down) * (CollisionDetectionSettings.AllowedPenetration * .5f - supportContact.Contact.PenetrationDepth)); return(CharacterContactPositionState.Accepted); } else if (supportState == CharacterContactPositionState.TooDeep) { //Looks like we have to keep trying, but at least we found a good hint. hintOffset = Math.Min(0, Vector3.Dot(supportContact.Contact.Normal, down) * (CollisionDetectionSettings.AllowedPenetration * .5f - supportContact.Contact.PenetrationDepth)); return(CharacterContactPositionState.TooDeep); } else //if (supportState == SupportState.Separated) { //It's not obstructed, but the support isn't quite right. //It's got a negative penetration depth. //We can use that as a hint. hintOffset = -.001f - Vector3.Dot(supportContact.Contact.Normal, down) * supportContact.Contact.PenetrationDepth; return(CharacterContactPositionState.NoHit); } } else //Not obstructed, but no support. { return(CharacterContactPositionState.NoHit); } }
CharacterContactPositionState TryDownStepPosition(ref Vector3 position, ref Vector3 down, ref QuickList <CharacterContact> tractionContacts, ref QuickList <CharacterContact> supportContacts, ref QuickList <CharacterContact> sideContacts, ref QuickList <CharacterContact> headContacts, out float hintOffset) { hintOffset = 0; PrepareQueryObject(ref position); QueryManager.QueryContacts(currentQueryObject, ref tractionContacts, ref supportContacts, ref sideContacts, ref headContacts); if (IsDownStepObstructed(ref sideContacts)) { return(CharacterContactPositionState.Obstructed); } //Note the use of traction contacts. We only want to step down if there are traction contacts to support us. if (tractionContacts.Count > 0) { CharacterContactPositionState supportState; CharacterContact supportContact; QueryManager.AnalyzeSupportState(ref tractionContacts, ref supportContacts, out supportState, out supportContact); if (supportState == CharacterContactPositionState.Accepted) { //We're done! The guess found a good spot to stand on. //The final state doesn't need to actually create contacts, so shove it up //just barely to the surface. hintOffset = -Vector3.Dot(supportContact.Contact.Normal, down) * supportContact.Contact.PenetrationDepth; return(CharacterContactPositionState.Accepted); } else if (supportState == CharacterContactPositionState.TooDeep) { //Looks like we have to keep trying, but at least we found a good hint. hintOffset = Math.Min(0, .001f - Vector3.Dot(supportContact.Contact.Normal, down) * supportContact.Contact.PenetrationDepth); return(CharacterContactPositionState.TooDeep); } else //if (supportState == SupportState.Separated) { //It's not obstructed, but the support isn't quite right. //It's got a negative penetration depth. //We can use that as a hint. hintOffset = -.001f - Vector3.Dot(supportContact.Contact.Normal, down) * supportContact.Contact.PenetrationDepth; return(CharacterContactPositionState.NoHit); } } else { return(CharacterContactPositionState.NoHit); } }
CharacterContactPositionState TryUpStepPosition(ref Vector3 sideNormal, ref Vector3 position, ref Vector3 down, ref QuickList <CharacterContact> tractionContacts, ref QuickList <CharacterContact> supportContacts, ref QuickList <CharacterContact> sideContacts, ref QuickList <CharacterContact> headContacts, out float hintOffset) { hintOffset = 0; PrepareQueryObject(ref position); QueryManager.QueryContacts(currentQueryObject, ref tractionContacts, ref supportContacts, ref sideContacts, ref headContacts); if (headContacts.Count > 0) { //The head is obstructed. This will define a maximum bound. //Find the deepest contact on the head and use it to provide a hint. float dot; Vector3.Dot(ref down, ref headContacts.Elements[0].Contact.Normal, out dot); hintOffset = -dot * headContacts.Elements[0].Contact.PenetrationDepth; for (int i = 1; i < headContacts.Count; i++) { Vector3.Dot(ref down, ref headContacts.Elements[i].Contact.Normal, out dot); dot *= -headContacts.Elements[i].Contact.PenetrationDepth; if (dot > hintOffset) { hintOffset = dot; } } return(CharacterContactPositionState.HeadObstructed); } bool obstructed = IsUpStepObstructedBySideContacts(ref sideNormal, ref sideContacts); if (!obstructed && supportContacts.Count > 0) { CharacterContactPositionState supportState; CharacterContact supportContact; QueryManager.AnalyzeSupportState(ref tractionContacts, ref supportContacts, out supportState, out supportContact); if (supportState == CharacterContactPositionState.Accepted) { if (tractionContacts.Count > 0) { //We're done! The guess found a good spot to stand on. //Unlike down stepping, upstepping DOES need good contacts in the final state. //Push it up if necessary, but don't push it too far. //Putting it into the middle of the allowed penetration makes it very likely that it will properly generate contacts. //Choosing something smaller than allowed penetration ensures that the search makes meaningful progress forward when the sizes get really tiny; //we wouldn't want it edging every closer to AllowedPenetration and then exit because too many queries were made. hintOffset = Math.Min(0, Vector3.Dot(supportContact.Contact.Normal, down) * (CollisionDetectionSettings.AllowedPenetration * .5f - supportContact.Contact.PenetrationDepth)); return(CharacterContactPositionState.Accepted); } else { //No traction... Before we give up and reject the step altogether, let's try one last thing. It's possible that the character is trying to step up onto the side of a ramp or something. //In this scenario, the top-down ray cast detects a perfectly walkable slope. However, the contact queries will find a contact with a normal necessarily //steeper than the one found by the ray cast because it is an edge contact. Not being able to step up in this scenario doesn't feel natural to the player //even if it is technically consistent. //So, let's try to ray cast down to the a point just barely beyond the contact (to ensure we don't land right on the edge, which would invite numerical issues). //Note that this is NOT equivalent to the ray cast we performed earlier to test for an initial step height and surface normal. //This one is based on the QUERY state and the QUERY's contact position. //Find the down test ray's position. Ray downRay; downRay.Position = supportContact.Contact.Position + sideNormal * .001f; float verticalOffset = Vector3.Dot(downRay.Position - position, down); verticalOffset = characterBody.Height * .5f + verticalOffset; downRay.Position -= verticalOffset * down; downRay.Direction = down; //First, we must ensure that the ray cast test origin is not obstructed. Starting very close to the very top of the character is safe because the process has already validated //this location as accepted, just without traction. Ray obstructionTestRay; obstructionTestRay.Position = position - down * (characterBody.Height * .5f); obstructionTestRay.Direction = downRay.Position - obstructionTestRay.Position; if (!QueryManager.RayCastHitAnything(obstructionTestRay, 1)) { //Okay! it's safe to cast down, then. RayHit hit; if (QueryManager.RayCast(downRay, characterBody.Height, out hit)) { //Got a hit! if (characterBody.Height - maximumStepHeight < hit.T) { //It's in range! float dot; hit.Normal.Normalize(); Vector3.Dot(ref hit.Normal, ref down, out dot); if (Math.Abs(dot) > ContactCategorizer.TractionThreshold) { //Slope is shallow enough to stand on! hintOffset = Math.Min(0, Vector3.Dot(supportContact.Contact.Normal, down) * (CollisionDetectionSettings.AllowedPenetration * .5f - supportContact.Contact.PenetrationDepth)); //ONE MORE thing to check. The new position of the center ray must be able to touch the ground! downRay.Position = position; if (QueryManager.RayCast(downRay, characterBody.Height * .5f + maximumStepHeight, out hit)) { //It hit.. almost there! hit.Normal.Normalize(); Vector3.Dot(ref hit.Normal, ref down, out dot); if (Math.Abs(dot) > ContactCategorizer.TractionThreshold) { //It has traction! We can step! return(CharacterContactPositionState.Accepted); } } } } } } //If it didn't have traction, and this was the most valid location we could find, then there is no support. return(CharacterContactPositionState.Rejected); } } else if (supportState == CharacterContactPositionState.TooDeep) { //Looks like we have to keep trying, but at least we found a good hint. hintOffset = Math.Min(0, Vector3.Dot(supportContact.Contact.Normal, down) * (CollisionDetectionSettings.AllowedPenetration * .5f - supportContact.Contact.PenetrationDepth)); return(CharacterContactPositionState.TooDeep); } else //if (supportState == SupportState.Separated) { //It's not obstructed, but the support isn't quite right. //It's got a negative penetration depth. //We can use that as a hint. hintOffset = -.001f - Vector3.Dot(supportContact.Contact.Normal, down) * supportContact.Contact.PenetrationDepth; return(CharacterContactPositionState.NoHit); } } else if (obstructed) { return(CharacterContactPositionState.Obstructed); } else { return(CharacterContactPositionState.NoHit); } }