- Home /
Ground-clamping a Kinematic Rigidbody
We have a vehicle we need ground-clamped. We can't use Unity's physics on it because it occasionally gets corrected from a simulation outside of Unity. The vehicle's towing something that is controlled by physics, so we need the Rigidbody.
In FixedUpdate, we move the vehicle along its forward vector (for us, transform.right, because of the way its modeled) using the Rigidbody.MovePosition:
var deltaPos = m_tractorBody.transform.right * speed * Time.deltaTime;
var tractorPos = PutOnGround(m_tractorBody.position + deltaPos);
tractorPos.y += m_tractorHeightAboveGround;
m_tractorBody.MovePosition(tractorPos);
The PutOnGround method returns a Vector3 at the same x,z position as the input vector, with y set to the ground height at that x,z. I've trid sampling ground height both by casting rays and using GetInterpolatedHeight.
After that, we get a normal based on the terrain. I've tried both using the terrain normal, and sampling terrain height at three points around the vehicle, and using the normal of the plane constructed from those 3 ground points. To orient the vehicle to the ground, and then face it in the right direction, I do this:
var deltaHeading = heading - m_heading;
var deltaOrientation = Quaternion.AngleAxis(deltaHeading, Vector3.up);
// Now, get terrain normal, either by the plane or //GetInterpolatedNormal (code not shown) call it tractorNormal
Quaternion newOrientation =
Quaternion.FromToRotation(m_tractorBody.transform.up,
tractorNormal);
var angle = Vector3.Angle(m_tractorBody.transform.up, tractorNormal);
var totalTime = angle / m_angularVelocityDegPerSecond;
//Now, orient the tractor to that normal.
m_tractorBody.MoveRotation( m_tractorBody.rotation
* deltaOrientation
* newOrientation);
This works fine, when it works, but other times the vehicle starts shaking violently. It seems to happen on certain places on the terrain, but I can't see what's different about those places. Our terrain is fairly smooth, rolling hills with a banked road. Oddly, it seems to take the banked road pretty well, but some of the rolling hills on the terrain make it shake violently or start flipping.
I've tried all the Rigidbody interpolation settings, without any affet. The vehicle was behing followed by a camera using SmoothLookAt. I changed its update to FixedUpdate, no effect.
I've tried alleviating the problem by Slerping toward the new normal, rather than just popping to it, but eventually there's a violent shake or pop that messes that up, leaving the vehicle at an odd angle. I've also tried averaging past and predicted normal (based on direction vehicle's heading). It alleviated the problem somewhat, but occasionally I got the violent shaking.
I'm still using Unity 3.5.7. If anybody has an idea of what causes this and how to fix it, I'd appreciate it!
Hi, It's difficult to narrow this kind of physics problem. Possible cause can be float or math functions precision: for example some FromToRotation can return wrong values if called with parameters containing very small float values. I would check for those edge cases to be sure. Also, to help debug this kind of situation, I would use lots of Debug.DrawLine to show all vectors and values that are computed and check visually if there is something not coherent. I also often change the Time in the project settings to slow things down and better see what happens. Lastly, there was a bug in Unity 3.5 (I think, I don't exactly remember what sub version): normals returned by raycast on a terrain and by Terraindata.GetInterpolatedNormal() didn't return the same normal when hitting the same point/ray. I don't think it's the cause here but it could help you choose one method or the other.
Thanks, dns. I had tried the Debug.Line to look at the normals, and never saw any big jumps, but I could well have missed something. I didn't consider slowing down Time. Thanks for the tip.
As for the normal bugs, I saw the effect whether I sampled the terrain by ray casting or getInterpolatedNormal. If I had to say it was a bug in Unity, I'd guess it was a Rigidbody bug. The effect is similar to tampering with a Rigidbody controlled by physics outside of physics, e.g., changing its location. However, the Rigidbody I was working on was kinematic.
I do something similar when I am placing buildings. I am not sure if it is exactly what you want but:
Vector3 rotateTo = new Vector3(0,0,0); // stored
//Update
RaycastHit hit;
Ray ray = Camera.main.ScreenPointToRay (Input.mousePosition);
if (Physics.Raycast (ray, out hit, $$anonymous$$athf.Infinity, canBuildLayers)) {
spawnpoint = hit.point;
}
Vector3 rotateNeg = new Vector3 ( 0, rotateTo.y - 22.50000f, 0);
Vector3 rotatePos = new Vector3 ( 0, rotateTo.y + 22.50000f, 0);
Vector3 slightRotateNeg = new Vector3 (0, rotateTo.y - 5.00000f, 0);
Vector3 slightRotatePos = new Vector3 (0, rotateTo.y + 5.00000f, 0);
if (Input.GetAxis ("$$anonymous$$ouse ScrollWheel") < 0) {
if(Input.Get$$anonymous$$ey ($$anonymous$$eyCode.LeftAlt)){
rotateTo = slightRotateNeg;
}else{
rotateTo= rotateNeg;
}
} else if (Input.GetAxis ("$$anonymous$$ouse ScrollWheel") > 0) {
if(Input.Get$$anonymous$$ey ($$anonymous$$eyCode.LeftAlt)){
rotateTo = slightRotatePos;
}else{
rotateTo = rotatePos;
}
}
thingToPlace.transform.position = spawnpoint;
thingToPlace.transform.rotation = Quaternion.FromToRotation(Vector3.up, hit.normal) * Quaternion.Euler(rotateTo.x, rotateTo.y, rotateTo.z);
Obviously you dont need the ability to rotate with the mousewheel, you would replace it with your own way of turning the vehicle. also you would be casting the ray straight down at the car (ins$$anonymous$$d of the from the mouse position) and colliding with the terrain. use a layer mask to ignore the vehicle. The important part is at the bottom