views:

569

answers:

3

Hey there

For sure this is very trivial math stuff for some of you.

But: When I rotate a view, lets say starting at 0 degrees and rotating forward 0.1, 1, 10, 100, 150, 160, 170, 179, 179,999, and keeping on rotating in the same direction, this happens, to say it loud and clear: BANG !!!! BAAAAAAAAANNNNNGGGG!!!! -179,9999, -170, -150, -50, 0, 50, 100, 170, 179,99, and again: BBBBAAAANNNGGG!!! -179,999, -170, -100, and so on... for sure you know what I mean ;-)

Imagine you driving on a road and suddenly your miles-o-meter jumps negative like that. You wold freak out, right?

And you know what? This is VERY bad for my algorithm. I have no idea how to resolve this other than checking in if-else-blocks if my value suddenly swapped over. Something tells me I have to look at some math functions like sinus waves and other stuff. But my math knowledge sucks to the highest degree.

How can I solve this out? I try to calculate distances between angles, i.e. I have two views where one resides on the other, and both are rotated. And I want to calculate the overall rotation of both views. This little but mad thingy destroys my calculations as soon as values swap over suddenly from -179,99999 to 179,99999. Also I don't know if a 180 exists or if things swap somewhere at fabsf(179,999999999999999999999) if you know what I mean.

For example: What would be -170 degrees minus 50 degrees? Well, I think -220 degrees. But instead when I rotate -170 - 50 I end up getting this value: 40 degrees.

How can I receive what I expect, the -220 instead of 40, without any kind of unsecure swapping-if-else logic? Sometimes my selfmade-swapper works, but sometimes it doesnt due to math incprecisions.

Edit: I calculate the angles from the transform.rotation.z property of the view's layers.

+5  A: 

The view is not a canonical representation of your data, querying tells you about visual state of the system, nothing more. If you care about a particular representation of the angle then you shouldn't be reading it from transform, you should be storing it in your model or controller as appropriate, and setting your view's state based on that.

@interface MyViewController: UIViewController {
  CGFloat rotation;
}

@property (nonatomic) CGFloat rotation;

@end

@implementation MyViewController
@synthesize rotation;

- (void)setRotation:(CGFloat)rotation_ {
  rotation = rotation_;
  // Do whatever math you want and set the transform of the view based on that;
}
@end
Louis Gerbarg
A: 

What your seeing here is that angle math is modular like a clock. On a clock 11+2==1 not 13. Likewise, you can represent all angles as either negative or positive rotations. So, +180==(-180). So, 179+2=(-179). The transform is calculating the shortest distant rotation starting from the original zero rotation. Obviously, the shortest rotation of 181 degrees is actually a negative rotation of -179 degrees.

Transforms are not accumulative unless you feed the same transform into the transform calculation. This means that if you create a transform to rotate a view 45 degrees and then apply it twice you only get 45 degrees of rotation. To get accumulative rotation you need to use a function like CGAffineTransformRotate and keep feeding it the transform from the previous rotation.

This will produce a smooth rotation in the same direction as long as you feed it angles <180.

TechZen
A: 

For your UIView's CALayer, the transform property is a CATransform3D struct. There is no rotation member of that struct, but Apple has provided a convenience keypath to set and read rotation from that struct. The change from positive to negative degrees is an artifact of the calculations used to determine rotation from the struct. This math is described for the 2-D equivalent of CATransform3D, CGAffineTransform, in the Quartz 2D Programming Guide section "The Math Behind the Matrices".

To account for this, you could do the calculations yourself using code that I provide in this answer:

CATransform3D rotationTransform = [(CALayer *)[self.layer presentationLayer] transform];
float angle;
if (rotationTransform.m11 < 0.0f)
        angle = 180.0f - (asin(rotationTransform.m12) * 180.0f / M_PI);
else
        angle = asin(rotationTransform.m12) * 180.0f / M_PI;

This should yield angle values ranging from 0 to 360. This will flip over once you cross 0 or 360, due to the cyclical nature of the rotation.

Brad Larson