- Home /
Rotating an object to equal normals of object below
Hello, I'm using a modified version of the car tutorial script to control a hovering vehicle along a pipe shaped track that goes up/down/left/right. I need the vehicle's x rotation to match the track below preferably a smooth rotation from it's current x to the x of the normal below it. I have a ray casting down to the track with the following:
function FixedUpdate() {
var hit : RaycastHit;
if (Physics.Raycast (transform.position, -Vector3.up, hit)) { //this part is to match the vehicles x to the tracks x// transform.rotation = Quaternion.FromToRotation (Vector3.right, hit.normal); distanceToGround = hit.distance;
if (distanceToGround >.9) {
rigidbody.AddForce (-Vector3.up * 350000);
}
}
}
This causes the vehicle to lay on it's side and jump around. I know i need to extract the x rotation of hit.normal and apply it to the x rotation of the vehicle but I'm missing something here. Any ideas would be greatly appreciated. Thanks
Answer by skovacs1 · Sep 14, 2010 at 06:12 PM
There are lots of meanings to floating along a surface and many ways to implement them. Your code really only aligns an axis of rotation to the surface normal beneath it in world space rather than actually floating.
If you actually wanted to follow the contours of the surface, you could actually cast rays down the object's axis rather than the world axis and smooth between the rotations and positions to align properly. If using physics, you could apply appropriate forces/torque relative to the surface normal and the casting direction. You could setup a collider for the space beneath the object and let it slide along the surface using physics. To calculate it based on collisions, you could apply forces or changes along contact point normals with something like rigidBody.AddForceAtPosition(10 * contact.normal, contact.point). With a collider, you could scale the collider to indicate the amount of hover desired. There are numerous ways to go about this, including forcing an orbit above a surface by determining the position based on the orientation as the mouseOrbit camera script does (sticking the object to a rail essentially). If that's more of what you actually wanted, then please rephrase the question to reflect that.
What the posted code does:
function FixedUpdate() { var hit : RaycastHit;
//cast a ray down in the world from our current position
if(Physics.Raycast(transform.position, -Vector3.up, hit)) {
//sets the rotation so the x-axis aligns to the normal of the surface beneath us
//this aligns the x to the surface's outward direction not the surface's x-axis
transform.rotation = Quaternion.FromToRotation(Vector3.right, hit.normal);
//distance to the ground
distanceToGround = hit.distance;
//if too far away force it towards the down direction
if(distanceToGround >.9) rigidbody.AddForce(-Vector3.up * 350000);
}
}
Alignment
You are aligning the x axis (which indicates the right hand side of a properly aligned object) with the surface normal (meaning that the left side of the object will point towards the surface) and you complain that the vehicle is laying on its side. With this code, the side will align to the surface if the geometry is properly aligned or if differently aligned, the side may still align to the surface if you are aligning the wrong axis for your misaligned geometry.
To correct this, you must correct the axis you are aligning on from Vector3.right to some other axis.
Jumping Around
Because you are assigning the rotation directly, each time the calculated rotation is different from the current rotation, the assignment will make it jump to the orientation specified. If by jump around, you are referring to the actual position as well, please bear in mind that all rotations are relative to your pivot and if your geometry is offset from your pivot, it will appear to move as well. Also, if the transform pivot is offset from the geometry, you should also realize that the raycast is being cast from this pivot and you might not get the results desired in some cases.
To correct this, you could use Quaternion.Slerp or Quaternion.Lerp to smoothly interpolate between orientations or you could calculate the euler angles and do your own interpolation. Of course, with these approaches, there are some things to consider:
If you are interpolating from the current orientation to the target orientation with the same weight each FixedUpdate you will quickly begin the adjustment and smooth to the end (possibly never being 100% aligned, but always approaching it). It may sort of jump at the start if the change was large enough, but will logarithmically slow down.
var adjustRatio : float = 0.1; //amount of the difference to rotate by private var hit : RaycastHit;
function FixedUpdate() { if(Physics.Raycast(transform.position, -Vector3.up, hit)) { if(hit.distance >.9) rigidbody.AddForce(-Vector3.up * 350000); if(hit.normal == transform.up) return; //May never happen... tranform.rotation = Quaternion.Slerp(transform.up, hit.normal, adjustRatio); } }
If you are controlling the angles, you could interpolate by a fixed amount of your discretion. This way, while the change will still be done through assignment, it will never exceed a certain amount and the speed of the adjustment will be more or less constant.
var adjustSpeed : float = 10; //degrees private var hit : RaycastHit; private var newUp : Vector3;
function FixedUpdate() { if(Physics.Raycast(transform.position, -Vector3.up, hit)) { if(hit.distance >.9) rigidbody.AddForce(-Vector3.up 350000); if(hit.normal == transform.up) return; newUp = Vector3.RotateTowards(transform.up, hit.normal, adjustSpeed Mathf.Deg2Rad, 0); transform.rotation = Vector3.LookRotation(transform.forward, newUp); } }
If you store the start orientation and previous normal, you could start your interpolations whenever the normal changes and continue/end it whenever it doesn't. This will linearly interpolate the rotation about a sphere. Using SmoothStep will force all changes to smooth in and out, but can change from quick adjustments to a sudden smoothing in another direction if the surface normal changed suddenly during the interpolation - this could be corrected by adding some inertial correction based on the current weight.
var adjustSpeed : float = 1; private var fromRotation : Quaternion; private var toRotation : Quaternion; private var targetNormal : Vector3; private var hit : raycastHit; private var weight : float = 1;
function Start() { targetNormal = transform.up; }
function FixedUpdate() { if(Physics.Raycast(transform.position, -Vector3.up, hit)) { if(hit.distance >.9) rigidbody.AddForce(-Vector3.up 350000); if(hit.normal == transform.up) return; if(hit.normal != targetNormal) { targetNormal = hit.normal; fromRotation = transform.rotation; torotation = Quaternion.FromToRotation(Vector3.up, hit.normal); weight = 0; } if(weight <= 1) { weight += Time.deltaTime adjustSpeed; tranform.rotation = Quaternion.Slerp(fromRotation, toRotation, weight); //Or to smooth the weight //tranform.rotation = Quaternion.Slerp(fromRotation, toRotation, // Mathf.SmoothStep(weight)); } } }
I couldn't have asked for a better response! I've just learned a ton of stuff and you've cleared up some things I've been trying and failing at. $$anonymous$$uch appreciated, thanks.