views:

45

answers:

2

I am currently rotating a UIImageView by touching, dragging, and releasing. I am rotating the object by the function:

-(void)rotateForTime:(CGFloat)time distance:(CGFloat)distance
{
    CABasicAnimation* rotationAnimation;
    rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
    rotationAnimation.toValue = [NSNumber numberWithFloat:distance];
    rotationAnimation.removedOnCompletion = NO;
    rotationAnimation.fillMode = kCAFillModeForwards;
    rotationAnimation.duration = time;
    rotationAnimation.repeatCount = 1.0;
    rotationAnimation.timingFunction = [CAMediaTimingFunctionfunctionWithName:
                                                kCAMediaTimingFunctionEaseOut];
    rotationAnimation.delegate = self;
    [bottle.layer addAnimation:rotationAnimation forKey:@"rotationAnimation"];
}

Since after a rotation, the bounds of the rectangular image change, in order to tell if the user touches inside the image or not, I keep track of the corners of the image by rotating them the same distance I rotate the image and then use vector->edge intersection detection methods to check if the user touched in the bounds of the rectangle made by the corners. I use this function to rotate the corners:

-(void) rotateImageBounds:(CGFloat) angle
{
    // Translate the center and the corners of the image to the origin and their respective positions.
    corner1.x = corner1.x - bottle.center.x;
    corner1.y = corner1.y - bottle.center.y;
    corner2.x = corner2.x - bottle.center.x;
    corner2.y = corner2.y - bottle.center.y;
    corner3.x = corner3.x - bottle.center.x;
    corner3.y = corner3.y - bottle.center.y;
    corner4.x = corner4.x - bottle.center.x;
    corner4.y = corner4.y - bottle.center.y;

    // Rotates the point around the origin/center.
    // x = x1cos(angle) - x1sin(angle)
    // y = x1sin(angle) + y1cos(angle)
    CGFloat tempX;
    tempX = (corner1.x*cos(angle)) - (corner1.y*sin(angle));
    corner1.y = (corner1.x*sin(angle)) + (corner1.y*cos(angle));
    corner1.x = tempX;
    tempX = (corner2.x*cos(angle)) - (corner2.y*sin(angle));
    corner2.y = (corner2.x*sin(angle)) + (corner2.y*cos(angle));
    corner2.x = tempX;
    tempX = (corner3.x*cos(angle)) - (corner3.y*sin(angle));
    corner3.y = (corner3.x*sin(angle)) + (corner3.y*cos(angle));
    corner3.x = tempX;
    tempX = (corner4.x*cos(angle)) - (corner4.y*sin(angle));
    corner4.y = (corner4.x*sin(angle)) + (corner4.y*cos(angle));
    corner4.x = tempX;

    // Translates the points back to the original center of the image.
    corner1.x = corner1.x + bottle.center.x;
    corner1.y = corner1.y + bottle.center.y;
    corner2.x = corner2.x + bottle.center.x;
    corner2.y = corner2.y + bottle.center.y;
    corner3.x = corner3.x + bottle.center.x;
    corner3.y = corner3.y + bottle.center.y;
    corner4.x = corner4.x + bottle.center.x;
    corner4.y = corner4.y + bottle.center.y;
}

This works fine after the first rotation, the corners look like the are still at the corners of the image. However, after more rotations, the image and the corners no longer line up. As far as I know, the rotation method is accurate and the math for the corners is right. I've been trying to figure out this problem for days now. Any help would be greatly appreciated.

EDIT * Problem solved: I ended up letting the animation remove itself when it was done and just rotate the image into the right position when I was done (it was easier this way because I also need to rotate the image during mouse motion). For the rotation function: (angle is the angle that the mouse (or touch) was currently at, gotten using atan2(dy,dx) where dy is the height between the touch and center and dx is the width. Distance is the distance it needs to rotate.

-(void)rotateForTime:(CGFloat)time distance:(CGFloat)distance {

animating = TRUE;
CABasicAnimation* rotationAnimation;
rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
rotationAnimation.fromValue = [NSNumber numberWithFloat:angle];
rotationAnimation.toValue = [NSNumber numberWithFloat:distance + angle];
rotationAnimation.removedOnCompletion = YES;
rotationAnimation.fillMode = kCAFillModeForwards;
rotationAnimation.duration = time;
rotationAnimation.repeatCount = 1.0;
rotationAnimation.timingFunction = [CAMediaTimingFunction
                                      functionWithName:kCAMediaTimingFunctionEaseOut];
rotationAnimation.delegate = self;

[bottle.layer addAnimation:rotationAnimation forKey:@"rotationAnimation"];

}

And then at the end of the animation:

-(void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag {

animating = FALSE;
    // Rotates the image to the last spot that the animation rotated to.
[bottle.layer setValue:[NSNumber numberWithFloat:angle + distance]
                                    forKeyPath:@"transform.rotation.z"];

    // Gives the corners of the image after rotation (Thanks to mustISignUp) check to make sure 
    // that the touch is within these bounds (I use a ray line intersection algorithm in another part of my code)
ptInLayerCoords1 = [[bottle.layer presentationLayer] convertPoint:corner1 toLayer:self.view.layer];
ptInLayerCoords2 = [[bottle.layer presentationLayer] convertPoint:corner2 toLayer:self.view.layer];
ptInLayerCoords3 = [[bottle.layer presentationLayer] convertPoint:corner3 toLayer:self.view.layer];
ptInLayerCoords4 = [[bottle.layer presentationLayer] convertPoint:corner4 toLayer:self.view.layer];

}

A: 

I'm not familiar with the IPhone, but this usually happens due to accumulating rounding errors (which are especially apparent if corner.x/corner.y are integers).

To fix, rather than having a rotated rectangle that you rotate again every frame (or every time the user touches the screen, or whatever), you store only the angle-offset from the rectangle's original orientation and recalculate the new coordinates every frame.

BlueRaja - Danny Pflughoeft
Thank you very much, things just got too messy with the additive rotations in the program so I used your idea and stored my own angle and added to it and it helped me fix the problem.
Scotty
A: 

As BlueRaja says, the maths isn't accurate - it's inherently approximate, and you are accumulating errors. You need to recalculate the position of the corners from the layer's absolute dimensions and rotation each time, not try to track it.

Fortunately CALayer can do this for you without too much hassle

ptInLayerCoords = [bottle.layer convertPoint:pt fromLayer:self.layer];
didHit = [layer containsPoint:ptInLayerCoords];
mustISignUp
This is just what I was looking for. I only used the math because I couldn't find a way to do this. Thank you.
Scotty