- Home /
Physics.SphereCastAll - how far did the trace travel?
I'm used to working in idTech, where swept traces return, as part of their RaycastHit equivalent, the fraction that the trace travelled before hitting an object. This is fantastically useful for movement code, as you can just position your swept object as far as the trace got, and be guaranteed not to be stuck inside anything.
According to the documentation, there doesn't seem to be a direct equivalent (not for spheres or capsules at least). the closest I can see to this is "point" (the position which the sphere collided with the world) and "distance" which is the distance from the start of the trace, to the collision point... or at least, that's what I assume from the description.
Now, with a line trace, the "Fraction" travelled would be super easy to compute, but it gets a little harder when the trace is "thick". See image:
[Edit note: This diagram is DEFINITELY wrong. See third answer below: the distance IS how far along the trace you got (or atleast, it definitely is for rigidbody.SweepTest]
So I guess my question is, is my interpretation of distance correct?
And given that, has anyone done the maths to find the fraction? Writing it out like this is making a solution clearer, but it's nice to share your working sometimes, and if someone has a quick bit of code to ahem appropriate, that'd be nice.
Answer by Joshua · Apr 28, 2011 at 04:08 PM
Well, assuming in your drawing the y-axis faces upwards and the x-axis faces right (doesn't matter, of course, other then for the phrasing of the answer).
var hitInfo : RaycastHit; var maxDistance : float; //you mentioned a maximum distance in your q.
if(SphereCast (origin : Vector3, radius : float, direction : Vector3, hitInfo, maxDistance , layerMask : int = kDefaultRaycastLayers) : bool
var length = hitInfo.distance - radius;
var angle = Vector3.Angle(direction,hitInfo.point);
var forwardLength = length/Mathf.cos(angle);
var relativeForwardLength = forwardLength/maxDistance; //the fraction you wanted
}
edit for clarification:
what I do is I take three points, the origin of the ray, the (distance of the ray minus the radius) times the angle and x, where the angle between origin, the second point and x is 90 degrees. Because I now have a triangle in a 2d plane with an angle of 90 degrees I can simply use the cosine of the angle and the distance between the origin and the second point to find point x.
If I didn't make any mistakes (I probably did) this should work :) it should at least give you an idea.
Sat down with a pen and pencil and tried to work this out, and I think it clicked! This is really elegant if it's correct! I think I'm going to set up a test scene for it to make sure.
There's a very good change it's not 100% correct though, but if you go through the steps you should be able to get it to work. :)
Needs a Deg2Rad conversion in the Cos...
But the results still have the resultant sphere fairly far away. When I half the radius, I get close, but still no cigar.
http://bezzy.net/UnityTests/SphereCastEndPoint/ here's a little test. You can see that although the cos relationship seems pretty correct, the distance is just slightly off. I wonder if it's some kind of magic number or... hmm.
I'll try the line/sphere intersection approach ins$$anonymous$$d.
Answer by Aubrey Hesselgren · May 17, 2011 at 04:13 PM
I did another check, this time using rigidbody.SweepTraceAll(). I actually double checked what the "RaycastHit.distance" value actually refers to, and I was wrong in the first place. It DOES denote how far along the trace it got. So, don't I feel dumb now? Thanks for being awesome, unity!
I also didn't realize that when working on my kinematic collider I need to use rigidbody.Move() (and then, only inside FixedUpdate), rather than just set its transform.position directly.
So, if you want to position an object as far as a pre-emtive check got, use something like this inside FixedUpdate():
float floorCheckDistance = 1.0f;
RaycastHit hit;
if(rigidbody.SweepTest(-transform.up, out hit, floorCheckDistance )){
//Snap to floor below your rigidbody
rigidbody.MovePosition( transform.position -transform.up * hit.distance );
}
Other notes on SweepTest/All: When using SweepTestAll as opposed to SweepTest, the resulting array of hits will NOT be in order of distance (i will guess it's just collisions in the order they are processed). You should look for the shortest distance to re-position your object to, if you want it to go no further than what it's colliding with. Regular SweepTest DOES return the earliest/closest collision.
All colliders (i.e. on the object itself, and children) on the rigidbody you are testing will be incorporated into the sweep test.
If a child collider has a kinematic rigidbody on it, it will NOT be incorporated in to the sweep test.
SweepTest respects the Physics Layer Collision Matrix: this means that child colliders won't get hung on geometry that you want your kinematic rigidbody to ignore, just because a larger child collider is on another layer.
Moving rigidbody.MovePosition(), and then using rigidbody.SphereCast/All again does not seem to work - as if the position is not upated by PhysX. If you want super accurate iterative slide movement, you may have to start working with the more generic Physics.CapsuleCast, or Physics.SphereCast calls.
Can I take this moment to recommend unity users start up brand new scenes or even projects to check specific tests for themselves? Cos I'm dumb and didn't realize how useful it was.
Answer by Aubrey Hesselgren · Apr 28, 2011 at 03:39 PM
Hmm. So I can actually use "point", "normal" and "radius" to find the end point that the sphere ends up in, since the normal of the collision point with a sphere will ALWAYS point back to the end point, regardless of the other collider's shape.
is that an assumption? {edit} Yes, yes it is. The normal is from the SURFACE, not from the SPHERE, dummy!
So, for the end point, it's quite easy:
Vector3 EndPoint = hit.point + hit.normal * Radius;
For the fraction, you use part of "closest point on line":
float TraceFraction = Vector3.Dot( EndPoint - origin, Direction ) / Distance;
Hmm. The act of asking a question so often answers it. Thanks, me!
{edit} So, the above is wrong, but doing a line trace on a sphere (origin at the collision point, radius equal to the original sphere trace's radius) we can get either a tangent intersection (single solution), or two possibilities, the correct one being the closest to the original trace's position.
[edit]
Based on Joshua's answer, for anyone who wants it. It's not completely perfect due to (i believe) floating point imprecision, but it's a start:
public static Vector3 SphereTraceEndPos(Vector3 startPos, Vector3 endPos, float sphereRadius, RaycastHit tracedInfo) { const float magicNumber = 0.07757358f;//safety margin Vector3 direction = (endPos - startPos).normalized; float maxDistance = (endPos - startPos).magnitude;
float length = tracedInfo.distance - magicNumber * sphereRadius;
float angle = Vector3.Angle(direction, tracedInfo.point);
float forwardLength = length / Mathf.Cos(Mathf.Deg2Rad * angle);
return startPos + direction * forwardLength;
}
Oh, you're right! Its the normal of the surface! Durr! Thanks man.
Yeah, that suddenly makes this trickier.
I think I have it, in theory.
Set up a sphere with the same radius as the sphere trace, then calculate the intersection point of the original trace's ray with that sphere. That gives the end point.
It's a bit of a work around. Would be nice for deltaFraction to be part of RayhitInfo
Your answer
Follow this Question
Related Questions
How can the resultant velocity of an object after a potential collision be predicted? 1 Answer
anyone know the maths to take into account Bounciness when calculating trajectories? 1 Answer
Enemy Sighting Using SphereCast 0 Answers
How to find the velocity of a gameobject that has be hit by the spherecast ? 1 Answer
MY ray cast is stuck 0 Answers