Rotation woes: going past 90deg on any axis, forces others to 180 or -180, why??

Solved: only write to Euler angles! (at least in this case)*
Hi all-

I’ve been struggling in vain for some time now. Going a bit crazy.

Trying to make a simple script to rotate an object based on dragging the mouse on the screen- if the user lets go past the 45deg point, it auto-flips to 180 deg mark, or flips back to original point if not rotates past that point.

Unfortunatly, this isn’t working at all, and the best reason I can see is that Unity is (in an INCREDIBLY annoying fashion) messing with the rotation values in a way I don’t understand. Like the title says, it seems that once I rotate one axis past 90degrees, it starts counting backd down to 0, and the other two axis snap to 180 or -180. AHHGGG.

I’ve spent 6 hours trying to figure this out. ridiculous, right? I am completly stumped. Any ideas how to get a proper reading from from this thing?

I’ll post the entire script I’m using- I’m very much an amateur coder, so please try not to laugh :wink: lol…and if you have additional improvments, feel free to drop those.

thanks a bunch…please help!! :frowning:

Code:

//click position, drag, etc variables
var clickStartPos : Vector3;
var dragging : boolean = false;
var offset : float = 0.00;
var rotationStart : float = 0.00;
var divFactor : float = 0.00;
var finalRotation : float = 0.00;
var dragAmount : float = 0.00;

//rotation state variables
var autoFlipping : boolean = false;
var autoReset : boolean = false;
var backFlipToBack : boolean = false;
var fwdFlipToBack : boolean = false;
var backFlipToFront : boolean = false;
var fwdFlipToFront : boolean = false;

//front or back facing states
var facingFront : boolean = true;
var reversed : boolean = false;

//rotation target variables
private var startRotation : Quaternion;
var frontRotationTarget : Transform;
private var frontRotation : Quaternion;
var backRotationTarget : Transform;
private var backRotation : Quaternion;

//rotation timing variables
var animateTime : float = 1.00;
var lerpAmount : float = 0.00;
var timePassed : float = 0.00;


function Start()
{
	if(frontRotationTarget)
		frontRotation = frontRotationTarget.rotation;
	if(backRotationTarget)
		backRotation = backRotationTarget.rotation;
	startRotation = transform.rotation;
	
	divFactor = Screen.height / 65;
}

function Update()
{
	if(!autoFlipping)
	{
		if(Input.GetMouseButtonDown(0))
		{
			clickStartPos = Input.mousePosition;
			rotationStart = transform.eulerAngles.x;
			dragging = true;
		}
		
		if(dragging)
		{
			curserPos = Input.mousePosition;
			dragAmount = curserPos.y - clickStartPos.y;
			factoredDrag = dragAmount / divFactor;
			finalRotation = rotationStart + factoredDrag;
			transform.eulerAngles = Vector3(finalRotation,0,0);
				
			if(Input.GetMouseButtonUp(0))
			{
				//no longer dragging
				dragging = false;
				//we are about to do some auto-rotation- so reset all timing values, and set starting variables!
				startRotation = transform.rotation;
				timePassed = 0;
				lerpAmount = 0;
				
				//check if the card was rotated fare enough to initiate flip
				//if yes, decide which way to flip, and where to
				if(facingFront)
				{
					if(finalRotation >= 45)
					{
						backFlipToBack = true;
					}
					if(finalRotation <= -45)
					{
						fwdFlipToBack = true;
					}
				}
				if(reversed)
				{
					if(finalRotation >= 45)
					{
						backFlipToFront = true;
					}
					if(finalRotation <= -45)
					{
						fwdFlipToFront = true;
					}
				}
				
				//if we not about to flip to a new position, set autoReset state, else start the autoFlipping
				//NOTE: "autoFlipping" is a generic state, which is active whenever the card is flipping on its own,
				//hence, autoFlipping is active when autoReset is also active- it simply lets the game know the card
				// is flipping- whether to reset or toward a new position.
				if(!backFlipToBack  !fwdFlipToBack  !backFlipToFront  !fwdFlipToFront)
				{
					autoReset = true;
					autoFlipping = true;
				}
				else
				{
					autoFlipping = true;
				}
			}
		}
	}
	
	if(autoReset)
	{
		//lerp the rotation back to last angle
		if(facingFront)
		{
			//lerp back to front
			timePassed += Time.deltaTime;
			lerpAmount = (timePassed/animateTime) ;
			transform.rotation = Quaternion.Slerp (startRotation, frontRotation, lerpAmount);
			if(lerpAmount >= 1)
			{
				transform.rotation = frontRotation;
				autoReset = false;
				autoFlipping = false;
				facingFront = true;
				reversed = false;
			}
		}
		
		if(reversed)
		{
			//lerp back to reversed
			timePassed += Time.deltaTime;
			lerpAmount = (timePassed/animateTime) ;
			transform.rotation = Quaternion.Slerp (startRotation, backRotation, lerpAmount);
			if(lerpAmount >= 1)
			{
				transform.rotation = backRotation;
				autoReset = false;
				autoFlipping = false;
				facingFront = false;
				reversed = true;
			}		
		}
	}
	
	if(backFlipToBack)
	{
		//perform backwards flip to back side
		timePassed += Time.deltaTime;
		lerpAmount = (timePassed/animateTime) ;
		transform.rotation = Quaternion.Slerp (startRotation, backRotation, lerpAmount);
		if(lerpAmount >= 1)
		{
			transform.rotation = frontRotation;
			autoFlipping = false;
			backFlipToBack = false;
			facingFront = false;
			reversed = true;
			
		}
	}
	if(fwdFlipToBack)
	{
		//perform backwards flip to back side
		timePassed += Time.deltaTime;
		lerpAmount = (timePassed/animateTime) ;
		transform.rotation = Quaternion.Slerp (startRotation, backRotation, lerpAmount);
		if(lerpAmount >= 1)
		{
			transform.rotation = frontRotation;
			autoFlipping = false;
			fwdFlipToBack = false;
			facingFront = false;
			reversed = true;
		}
	}
	if(backFlipToFront)
	{
		//perform backwards flip to back side
		timePassed += Time.deltaTime;
		lerpAmount = (timePassed/animateTime) ;
		transform.rotation = Quaternion.Slerp (startRotation, frontRotation, lerpAmount);
		if(lerpAmount >= 1)
		{
			transform.rotation = frontRotation;
			autoFlipping = false;
			backFlipToFront = false;
			facingFront = true;
			reversed = false;
		}
	}
	if(fwdFlipToFront)
	{
		//perform backwards flip to back side
		timePassed += Time.deltaTime;
		lerpAmount = (timePassed/animateTime) ;
		transform.rotation = Quaternion.Slerp (startRotation, frontRotation, lerpAmount);
		if(lerpAmount >= 1)
		{
			transform.rotation = frontRotation;
			autoFlipping = false;
			fwdFlipToFront = false;
			facingFront = true;
			reversed = false;
		}
	}
}

why not. It is an equivalent rotation.
Here are 2 tips for you:

  1. Learn about coroutines NOW. Your code will look a lot better. There is no need for all these states in the Update().
  2. Represent your rotations with quaternions. You don’t need to know the theory behind quaternions to use them. When you want to rotate x degrees around w axis, create a quaternion using Quaternion.AngleAxis and multiply by it. This way you don’t have to worry about the Euler angle reprsentation of your rotations.

Just to clarify what ivkoni said a bit, Unity uses quaternions for rotations, so whatever you get back as eulerAngles is somewhat arbitrary. Hence the aggravation when trying to read eulerAngle values. I’m not sure what your script is doing exactly, but sometimes it’s better to keep track of rotations yourself instead of reading it, like the standard MouseLook script does.

–Eric

thanks guys- haha, I’ll look into those coroutines now. Figured I was doing something silly there…

Not certain on the quaternions. Didn’t follow what you meant, sry. I’ll try to keep track of those values seperatly maybe.

Basically, this script just needs to let a user flip a flash card between front/back. That is all. Seems to get complicated once I want the user to be able to “drag” on iPhone screen/click with mouse, and have the card follow that, then either auto-flip to other side or rotate back to start, depending on how far rotated it was when the click/touch is released.

oi.

thanks, off to try those as best possible.

Uhg, I realize this borders on whining- but really, can there be any reason for what’s going on here? It’s terribly annoying that rotating past a 90deg mark will make the numbers go completely insane. I would consider this a serious bug…

trying other options…just extremely aggravated…

Definitely not a bug; as ivkoni said, there are multiple correct ways of representing the quaternion as euler angles. (0, 0, -90) is exactly the same thing as (180, 180, 90). I’d suggest thinking of euler angles as write-only…it’s OK to set rotations from euler angles, but because of the quaternion → euler angle conversion, it’s frequently pointless to try to read them and expect consistent results. Just stick to quaternions or self-tracking.

–Eric

Just reiterating what others have already said here, but no, not a bug. On the contrary, it’s normal, expected behavior as far as Euler angles are concerned.

As suggested previously, it’s best not to rely on a transform’s Euler angles having any particular values. (And in general, whenever it seems like reading back the Euler angles is needed for some purpose or other, there’s usually a better alternative solution that can be used instead.)

there’s nothing silly in what you are doing. But unity supports this special type of functions called coroutines which will make your life a lot easier. Until Eric taught me how to use coroutines I was writing the same ‘spaghetti’ code as you are and I wished I had l looked into coroutines much earlier. This is why the capitalized NOW.

below is a short script that does kinda what you want. Also the project attached.

var sensitivity : float = 1;  // constant to convert the distance travelled by the mouse to degrees of rotation
var timeToCompleteRotation : float = 0.5; 
private var ray : Ray;
private var hit : RaycastHit;
private var cardToFlip : Transform;
private var axisOfRotation : Vector3 = Vector3.up;

private var startDragMousePosition : Vector2 = Vector2.zero;
private var currentMousePosition : Vector2 = Vector2.zero;
private var targetCardRotation : Quaternion = Quaternion.identity;
private var initialCardRotation : Quaternion = Quaternion.identity;
private var dragDistance : float = 0;
private var rotationAngle : float = 0;

function Update () {

if(Input.GetMouseButtonDown(0)) {
	 ray = Camera.main.ScreenPointToRay (Input.mousePosition);
		if (Physics.Raycast (ray, hit, 100)) {
			cardToFlip = hit.transform;
			startDragMousePosition = Input.mousePosition;
			initialCardRotation = cardToFlip.rotation;
		}
	}
	
if(Input.GetMouseButton(0)) {
		if(cardToFlip) {
			dragDistance = Input.mousePosition.x - startDragMousePosition.x;
			rotationAngle = dragDistance*sensitivity;
			rotationAngle = Mathf.Clamp(rotationAngle,-180,180);
			cardToFlip.rotation = initialCardRotation*Quaternion.AngleAxis(-rotationAngle, axisOfRotation);
		}
	}
if(Input.GetMouseButtonUp(0)) {
	if(cardToFlip) {
		if(rotationAngle > 90) {
			rotate(cardToFlip,initialCardRotation*Quaternion.AngleAxis(180, axisOfRotation), timeToCompleteRotation);
		}
		else if(rotationAngle < -90) {
			rotate(cardToFlip,initialCardRotation*Quaternion.AngleAxis(-180, axisOfRotation), timeToCompleteRotation);
		} else {
			rotate(cardToFlip,initialCardRotation, timeToCompleteRotation);
		}
	}
	cardToFlip = null;
}
}


// this function is a coroutine. Why? Because it suspends its execution to the next frame (the yield statement). In the next update cycle it will resume where it left.
function rotate(trans : Transform, endRotation : Quaternion, s : float) {
	var t : float = 0.0;
	var startRotation : Quaternion = trans.rotation;
	var r = 1.0/s;
	while (t < 1.0){
		t+= Time.deltaTime * r;
		trans.localRotation = Quaternion.Slerp(startRotation,endRotation,t);
		yield;
	}
	trans.rotation = endRotation;
}

442449–15373–$FlipCards.zip (44.5 KB)

Thanks a bunch everybody- I did finally get it working, as eric suggested, by only writing to the euler angles. Big thanks for that!

ivkoni- wish I had seen this earlier, I’ll have to dig through it now, looks like some good teachings in there :slight_smile: Thanks for sending the full project too, really helps!

thanks for sharing, from here i can modify it to my needs!

awesome! :smile: