Why is rotation and unparenting causing discrepancy between visual position and Inspector? Not a local vs global position confusion.

Hi,

In my scene I have two 1x1x1 unity cubes. The first, “cube”, is at (0,0,0). The second, “cube1”, is at (0,1,0). Both are tagged “Cube” and start in an empty parent object called “cubes”. My script parents them in an empty game object positioned at (0,.5f,0), rotates that around the x axis 90 degrees after key input and then unparents them.

The problem starts after odd numbers of 90 degree rotations, even works great. User hits the key, the cubes are parented. The parent rotates 90 degrees. The cubes are unparented. Everything is visually in place and the Inspector has only a very tiny discrepancy. “cube” is at (0,.5,-.5) and “cube1” is at (0,.499,5). As small as the shift from .5 is it seems to makes all the difference. The cubes are parented again, the parent rotates, the cubes are unparented. Again, visually, everything is fine, the cubes appear to be in the expected places and “cube’s” position in the Inspector is even as expected (0,1,0), but “cube1’s” position in the inspector is (0,-1.192,0) which visually it is definitely not at. Also, when I attempt to adjust the y position, in the Inspector, for “cube1” at this point the number snaps to 0 as if it had been there all the time. After many rotations everything starts to break down.

It always starts with that tiny shift after an unparenting following an odd number of spins. unparenting after an even number of spins is no problem, everything still works great. Also, if I do not unparent the cubes at all everything works great. If this script was all I was trying to do I could just stop unparenting them, but it is a part of a larger script that requires cubes to be changing parents. Changing parents without unparenting first seems to cause the same effect.

Thanks in advance for any help.

using UnityEngine;
using System.Collections;

public class RotTester : MonoBehaviour 
{
	private GameObject[] cubes;

	private GameObject pivot;
	private GameObject endRotation;

	private bool rotate;
	
	private float t;
	private float c;

	void Start () 
	{
		rotate = false;
		t = 0;

		endRotation = new GameObject ("endRotation");

		pivot = new GameObject ("pivot");  
		pivot.transform.position = new Vector3 (.5f, .5f, 0);

		cubes = GameObject.FindGameObjectsWithTag ("Cube");
	}

	void Update () 
	{
		if (Input.GetKeyDown(KeyCode.Q))
		{
			foreach (GameObject cube in cubes) 
			{
				cube.transform.parent = pivot.transform;
			}
			
			endRotation.transform.Rotate (Vector3.right, 90, Space.World);
			rotate = true;
		}

		if (rotate == true) 
		{
			pivot.transform.rotation = Quaternion.RotateTowards (pivot.transform.rotation, endRotation.transform.rotation, Time.deltaTime * 400);

			if (t < 1) 
			{
				t += Time.deltaTime;
			} else 
			{
				foreach(GameObject cube in cubes)
				{
					cube.transform.parent = null;
				}
				
				t = 0;
				rotate = false;
			}
		}
	}
}

If you make the Inspector window wider you’ll see that “-1.192” is not the whole value on that field, it probably is something like “-1.192E-20”, which means… a really small number. Something similar goes for the 0.499 value, it probably is 0.499999… In practice is the same as 0.5.

Unity does some math when you do rotations, translations and parenting/unparenting (usually a product of a vector and a matrix), and those operations produce some errors inherent of the limited precision of floats.

If you really need those values to be exactly “0.5” or “0” you have to assign them manually (in the inspector or through a script), you can’t expect Unity to assign rounded values.

Don’t rely on delta movements/rotations when you need exact possitions.

As the other commenters have pointed out, what’s happening is that you get really small errors due to floating point rounding errors. These are inevitable - floats are implemented as numbers on the form (a * (2 ^ b)), so exact representations of most numbers are not possible.

So whenever you rotate something X degrees, there’s a good chance that you’re not rotating it exactly X degrees, but something very close to it. The small error isn’t a problem when you only rotate once, but when you get many of those errors, the problem grows.

The solution is to not use delta rotations - ie. not add rotations together. So if you have rotated 90 degrees, and want to rotate 90 more degrees, don’t add 90 to your rotation, but set it directly to 180.

If you’re implementing a Rubiks Cube, each of the eight rotation bases has exactly 4 possible rotations. You can find those on start, store them, and then rotate towards the next one whenever the player rotates one of them. In addition, you could store the relative possition the cubes should have, and move the current child-cubes to those locations after a rotation, to compensate for rounding errors.