- Home /
Quaternion rotation with normals bug: Only works into one direction?
Hello. I am working on making a transform stay above the terrain, this means it should basically "hug" it and always be x amount of meters above it and rotate accordingly. The staying above I got working. The rotating too. Except for one odd thing.
I use the normals to rotate the transform, however, this only works into positive z. It won't work into negative z: The transform is facing negative z but the rotation is still positive z. Now, I could just flip the rotation around on the z axis, but this won't work as I don't have z rotation at either 0 or 360 since the transform can move freely in space.
This is my code:
RaycastHit hit;
if (Physics.Raycast(transform.position, -curNormal, out hit)){
// curNormal smoothly follow the terrain normal:
curNormal = Vector3.Lerp(curNormal, hit.normal, 4*Time.deltaTime);
// calculate rotation to follow curNormal:
Quaternion rot = Quaternion.FromToRotation(Vector3.up, curNormal);
// rotate apply rotation to transform except for Y
transform.rotation = new Quaternion(0, transform.rotation.y, 0, transform.rotation.w) * rot * iniRot;
}
iniRot is just a Quaternion of the rotation at the beginning (essentially 0,0,0)
To clarify: Here it is facing positive z. Works: http://snag.gy/ZhAoB.jpg Here it is facing negative z. Doesn't work: http://snag.gy/SvknB.jpg
Answer by JoeStrout · Sep 03, 2015 at 06:50 PM
You can't just take parts out of the x/y/z/w components of a Quaternion like that. When you do, you make an invalid quaternion that doesn't even represent a rotation. I'm amazed it works at all — Unity is clearly doing some error-trapping and recovery here. But you shouldn't rely on it.
Try instead working with the Euler angles... this is not ideal either, but will generally work except for extreme angles:
transform.rotation = Quaternion.Euler(0, transform.rotation.eulerAngles.y, 0) * rot * iniRot;
Even better would probably be to keep track of your desired Y rotation in a separate property, and use that in the Quaternion.Euler call above rather than using the Y component of whatever your current rotation happens to be.
Hi @JoeStrout, thanks for your reply. I replaced my code with yours but sadly the problem still remains: The box rotates correctly when moving towards positive z, but the rotation is inversed when going towards negative z. It's as if the karts transform.forward isn't recognized and it sticks to Vector3.forward.
Furthermore, I tried replacing Vector3.up with transform.up - that broke everything. I don't understand - I even tried Quaternion.Inverse'ing rot and iniRot to no avail.
Oh, I think I see. 'rot' here is being calculated correctly, e.g., a +30° rotation around X would make an object with its up vector pointing straight up, point along the slope normal.
The problem is that you're then doing your object's Y rotation AFTER that rotation (remember that rotations are composed right to left). So, if you could see the intermediate steps, you would find that at rot * iniRot it's lined up with the slope perfectly, and then you rotate that 180° around Y, and get [this result][1].
So I think the solution is to do it in the other order, something like:
transform.rotation = rot * Quaternion.Euler(0, transform.rotation.eulerAngles.y, 0) * iniRot;
(Though I still recommend replacing transform.rotation.eulerAngles.y with some "yRotation" property you keep track of separately.) [1]: http://snag.gy/SvknB.jpg