- Home /
Snapping Quaternion Rotations in Script
Hi. I have a bit of a problem concerning quaternions. In the game we're making, the player can position and rotate objects on all axes which is all done in script. But we want these positions and rotations to be snapped to certain increments (0.1 for movement and 5 for rotation). The movement is fine as can just do the good old:
Vector3 position = SelectedObject.position;
position.x = Mathf.Round(position.x / MovementIncrement) * MovementIncrement;
position.y = Mathf.Round(position.y / MovementIncrement) * MovementIncrement;
position.z = Mathf.Round(position.z / MovementIncrement) * MovementIncrement;
SelectedObject.position = position;
But the rotations are being a bit more tricky to snap. I'm currently rotating the objects via:
SelectedObject.Rotate(RotateIncrement * direction);
Where direction is a vector (Vector.up for example) that describes the axis to rotate around. This does kinda work but after running the game, playing with the rotations and then trying to put the object back to (0,0,0) the rotations are actually something like (1.567, 0.2345, -1.234) which are of course no longer multiples of five. I thought all I'd need to do is the same as the position snapping:
Vector3 rotation = SelectedObject.eulerAngles;
rotation.x = Mathf.Round(rotation.x / RotateIncrement) * RotateIncrement;
rotation.y = Mathf.Round(rotation.y / RotateIncrement) * RotateIncrement;
rotation.z = Mathf.Round(rotation.z / RotateIncrement) * RotateIncrement;
SelectedObject.eulerAngles = rotation;
But this makes the rotations look very weird as they snap when they shouldn't due to the way Quaternions work and makes it look very jittery.
I tried using AngleAxis but this created the same problem as using just the Rotate function. I also tried storing and manipulating good old Eular vectors and then converting to quaternion after doing vector rotations but then this caused gimbal lock to happen.
Is there any way to snap quaternion rotations to multiples of 5 on all axes in script?
I can you post the Input code you are using to do the rotation?
There's nothing really special about the input code. There's literally just "Action" scripts on NGUI buttons that call a method passing through an enum. Then that function has a switch statement which calls a "DoRotation" method passing through the direction (Vecotr3.up, Vector3.right, etc) and the code in that function that is literally in the second bit of code I put up top.
I've managed to get it somewhat working. I've resigned to the fact that I'm probably gonna have to use eular for storing and manipulating the rotation so that I can snap it as above. So now just trying to find a way to stop the gimbal lock happening. Using any of Unitys built in methods (Quaternion.Eular(), Rotate()) and passing in values all at once or individually seem to be failing. Any ideas?
So after a lot of digging around it seems it really is impossible not to encounter gimbal lock when using Eular angles or when applying 3 separate quaternion rotations one after another. It literally has to be a single quaternion rotation to avoid gimbal lock. So it looks as if I do have to find a way to snap the quaternion (x,y,z,w) values while still keeping it normalized...
Answer by robertbu · Mar 17, 2014 at 08:07 PM
I'm not sure of what you need in terms of rotations, but try this script. Start with a new scene, add a cube, and add this script. While running, use the Arrow keys and the A/D keys for rotation.
using UnityEngine;
using System.Collections;
public class Example : MonoBehaviour
{
private Vector3 vCurr = Vector3.zero;
void Start() {
transform.eulerAngles = vCurr;
}
void Update() {
Vector3 vNext = vCurr;
if (Input.GetKeyDown (KeyCode.UpArrow)) {
vNext.x += 5.0f;
}
if (Input.GetKeyDown (KeyCode.DownArrow)) {
vNext.x -= 5.0f;
}
if (Input.GetKeyDown (KeyCode.LeftArrow)) {
vNext.y += 5.0f;
}
if (Input.GetKeyDown (KeyCode.RightArrow)) {
vNext.y -= 5.0f;
}
if (Input.GetKeyDown (KeyCode.A)) {
vNext.z += 5.0f;
}
if (Input.GetKeyDown (KeyCode.D)) {
vNext.z -= 5.0f;
}
if (vNext != vCurr) {
transform.eulerAngles = vNext;
vCurr = vNext;
}
}
}
Note that I never read Transform.eulerAngles. The rotation is always taken from my vCurr Vector3.
Just tried it and it does indeed cause gimbal lock similar to what I'd tried before :(
Answer by eightbitstev · Mar 17, 2014 at 10:30 PM
If I recall Quaternion math correctly, I believe multiplying them together is effectively what we would observe as adding them. Maybe try something like..
transform.rotation *= Quaternion.Euler(new Vector3(RotateIncrement,0,0));
Since this way we're assigning the rotation as Quaternions instead of from Euler values, it might work better?
I could be totally off on this. It's been a while since I've dealt with rotations. I could also be completely misunderstanding your problem. I apologize in advance if either of these are the case! :)
Unfortunately this methods creates the same problem as using the Rotate method (which I assume pretty much does this for you). It works fine for one axis rotation, but as soon as you start rotating all axes and then try to return to (0,0,0) the rotations are no longer multiple of five again. :(
Answer by Scribe · Mar 21, 2014 at 12:38 AM
This should work hopefully, this is a slightly edited version of my answer here.
Just check the Update method on how to call my RotateAround function and then attach this to your object you want to rotate:
private var targetAngle : Quaternion;
private var startAngle : Quaternion;
private var forward : Vector3;
private var right : Vector3;
private var up : Vector3;
var increment : float = 5;
var speed : float = 1.0;
private var startTime : float;
function RoundAngle(v : Quaternion) : Quaternion{
var vE : Vector3 = v.eulerAngles;
vE.x = Mathf.Round(vE.x/increment)*increment;
vE.y = Mathf.Round(vE.y/increment)*increment;
vE.z = Mathf.Round(vE.z/increment)*increment;
v = Quaternion.Euler(vE);
return v;
}
function RotateAround(vF : Vector3, vR : Vector3, vU : Vector3, axis: Vector3, angle: float) : Quaternion{
var rot: Quaternion = Quaternion.AngleAxis(angle, axis); // get the desired rotation
forward = (rot*vF);
right = (rot*vR);
up = (rot*vU);
startTime = Time.time;
startAngle = transform.rotation;
targetAngle = RoundAngle(rot * targetAngle);
}
function Start(){
startAngle = transform.rotation;
targetAngle = startAngle;
forward = (transform.forward);
right = (transform.right);
up = (transform.up);
}
function Update () {
if(Input.GetKeyDown(KeyCode.RightArrow)){
RotateAround(forward, right, up, Vector3.up, -increment);
}else if(Input.GetKeyDown(KeyCode.LeftArrow)){
RotateAround(forward, right, up, Vector3.up, increment);
}
if(Input.GetKeyDown(KeyCode.UpArrow)){
RotateAround(forward, right, up, Vector3.right, increment);
}else if(Input.GetKeyDown(KeyCode.DownArrow)){
RotateAround(forward, right, up, Vector3.right, -increment);
}
transform.rotation = Quaternion.Slerp(startAngle, targetAngle, (Time.time-startTime)/speed);
}
Hope that does what you want, it seems to work for me, you can change 'increment' to whatever angle (in degrees) you want to snap to, and you can change speed to snap to the angle faster (measured in seconds, currently it will take 1 second to snap).
Scribe
Are you having the same problem as before? Ive tested this and it seems to work for me :/
This gives similar results to what I originally did. Do quaternion rotations and then snap the Eular vectors to multiples of five but it creates very strange jittering when rotating. You may not have noticed it when doing it in multiples of 90 and the slerp definitely hides this somewhat but it's still noticeable. Especially when I took the slerp out.
Thanks for trying though :)
Also I was doing it on all 3 axes which seems to be a factor in the phenomenon I'm experiencing.
Your answer
![](https://koobas.hobune.stream/wayback/20220613140501im_/https://answers.unity.com/themes/thub/images/avi.jpg)
Follow this Question
Related Questions
Rotating wrong 0 Answers
Use Lerp to rotate object 2 Answers
Rotate object in the direction where it is going 1 Answer
How to instantiate on custom rotation? 1 Answer
w in Quaternion? 1 Answer