CGAffineTransformRotate : Detect rotation direction - ios

What I am trying to do :
I have an Image of a wheel and a scroll view. User can drag the wheel to rotate it in either direction.
By detecting the direction of rotation. I have to scroll the images placed over scroll view.
What I am doing is :
Following this tutorial
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
CGPoint touchPoint = [touch locationInView:self];
startTouch = touchPoint; // StartTouch is static varialbel
float dx = touchPoint.x - container.center.x;
float dy = touchPoint.y - container.center.y;
deltaAngle = atan2(dy,dx);
startTransform = container.transform;
return YES;
}
- (BOOL)continueTrackingWithTouch:(UITouch*)touch withEvent:(UIEvent*)event
{
CGPoint pt = [touch locationInView:self];
float dx = pt.x - container.center.x;
float dy = pt.y - container.center.y;
float ang = atan2(dy,dx);
float angleDifference = deltaAngle - ang;
container.transform = CGAffineTransformRotate(startTransform, -angleDifference);
return YES;
}
Main Problem
I am not able to detect the Rotation Direction correctly. I am using the angleDifference varibale from method continueTrackingWithTouch to detect it.
if(angleDifference > 0){
// Positive value move in right direction.
}
else{
// Negative value move in left direction.
}
This is working ok for small drags of less than 360 dgrees, but after that it's not working properly.
Can anyone suggest me correct approach of Detecting the rotation Direction.
Thanks

Related

Why does my rotatable wheel control sometimes spin in the wrong direction?

I am making a rotatable wheel control. You can drag anywhere on it to spin the control by it's central point.
It's not behaving correctly. It usually works well, but for some portion of a rotation, it starts rotating in the opposite direction for a brief period, and then it starts rotating with your finger again. EDIT: Upon further testing, I realized that you don't need to rotate it 360 degrees for it to rotate in the wrong direction.
A possibly unrelated problem is that the control seems to move slower then your finger. If a solution to the original problem doesn't solve this problem, I'll ask a separate question.
I posted an example project. If you would prefer to reproduce the project yourself, here are the steps with code:
Create a new single view application.
Create a new UIView subclass (in Objective-C) called NNReadyIndicator
Copy and paste this code in `NNReadyIndicator.m
#import "NNReadyIndicator.h"
#interface NNReadyIndicator ()
#property CGAffineTransform startTransform;
#property double originalAngle;
#end
#implementation NNReadyIndicator
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
}
*/
#pragma mark tracking
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
CGPoint touchPoint = [touch locationInView:self];
[self setOriginalAngle:[NNReadyIndicator calculateAngleWithX1:touchPoint.x x2:self.center.x y1:touchPoint.y y2:self.center.y]];
[self setStartTransform:self.transform];
NSLog(#"BEGIN TRACKING | originalAngle: %f", self.originalAngle);
return YES;
}
- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
CGPoint touchPoint = [touch locationInView:self];
double angle = [NNReadyIndicator calculateAngleWithX1:touchPoint.x x2:self.center.x y1:touchPoint.y y2:self.center.y];
CGFloat angleDifference = (CGFloat)(self.originalAngle - angle);
[self setTransform: CGAffineTransformRotate(self.startTransform, -angleDifference)];
NSLog(#"CONTINUE TRACKING | angle: %f angleDifference: %f", angle, angleDifference);
return YES;
}
#pragma mark -- Calculate Angle
+ (double)calculateAngleWithX1:(double)x1 x2:(double)x2 y1:(double)y1 y2:(double)y2 {
double distanceX = [NNReadyIndicator calculateDistanceWithPoint:x1 point:x2];
double distanceY = [NNReadyIndicator calculateDistanceWithPoint:y1 point:y2];
return [self calculateAngleWithDistanceX:distanceX distanceY:distanceY];
}
+ (double)calculateDistanceWithPoint:(double)pointA point:(double)pointB {
double distance = pointA - pointB;
if (distance < 0) {
distance = -distance;
}
return distance;
}
+ (double)calculateAngleWithDistanceX:(double)distanceX distanceY:(double)distanceY {
return atan2(distanceY, distanceX);
}
#end
EDIT: Added logging
Why is the control not consistently rotating in the same direction (and at the same speed?) as a finger that is dragging it?
You need to flip the angle if it is above or below 360 degrees. Also, to make it easier to understand, you will have to convert it into degrees and then back. Based off of this answer: https://stackoverflow.com/a/19617316/2564682
#define DEGREES_TO_RADIANS(angle) ((angle) / 180.0 * M_PI)
- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
CGPoint touchPoint = [touch locationInView:self];
double angle = [NNReadyIndicator calculateAngleWithX1:touchPoint.x x2:self.center.x y1:touchPoint.y y2:self.center.y];
double angleInDegrees = angle * 180 / M_PI;
// Flip angle if above or below 360 degrees
if (angleInDegrees > 180) {
angleInDegrees -= 360;
} else if (angleInDegrees < -180) {
angleInDegrees += 360;
}
CGFloat angleDifference = (CGFloat)(self.originalAngle - angleInDegrees);
[self.layer setTransform: CATransform3DRotate(self.layer.transform, (float)DEGREES_TO_RADIANS(-angleDifference), .0, .0, 1.0)];
NSLog(#"CONTINUE TRACKING | angle: %f angleDifference: %f", angleInRadians, angleDifference);
return YES;
}

onTouch Events not being called, when sprite moves off initial part of map?

It appears no onTouch events are being called (put NSLog statements in to confirm) when my player is moved above or to the right of my map. Map is big enough, and you can still see the map slightly when I get to the top or right, as the layer shifts as intended, but when clicking any further then what's initially loaded on screen, no touch events are being fired and char is stuck. Any ideas?
- (void)setCenterOfScreen:(CGPoint) position {
CGSize screenSize = [[CCDirector sharedDirector] viewSize];
int x = MAX(position.x, screenSize.width/2);
int y = MAX(position.y, screenSize.height/2);
x = MIN(x, theMap.mapSize.width * theMap.tileSize.width - screenSize.width/2);
y = MIN(y, theMap.mapSize.height * theMap.tileSize.height - screenSize.height/2);
CGPoint goodPoint = ccp(x,y);
CGPoint centerOfScreen = ccp(screenSize.width/2, screenSize.height/2);
CGPoint difference = ccpSub(centerOfScreen, goodPoint);
self.position = difference;
}
-(void) touchEnded:(UITouch *)touch withEvent:(UIEvent *)event
{
CGPoint touchLoc = [touch locationInNode:self];
// NOT CALLED WHEN PLAYER REACHES TOP OR RIGHT OF MAP (INITIAL SCREEN LOAD PART OF MAP)
// When clicking any furhter or top on the map, no touch event is fired and character (sprite) doesn't move
NSLog(#"Touch fired");
CGPoint playerPos = mainChar.position;
CGPoint diff = ccpSub(touchLoc, playerPos);
// Move horizontal or vertical?
if (abs(diff.x) > abs(diff.y))
{
if (diff.x > 0)
{
playerPos.x += theMap.tileSize.width;
}
else
{
playerPos.x -= theMap.tileSize.width;
}
}
else
{
if (diff.y > 0)
{
playerPos.y += theMap.tileSize.height;
}
else
{
playerPos.y -= theMap.tileSize.height;
}
}
if (playerPos.x <= (theMap.mapSize.width * theMap.tileSize.width) &&
playerPos.y <= (theMap.mapSize.height * theMap.tileSize.height) &&
playerPos.y >= 0 &&
playerPos.x >= 0)
{
mainChar.position = playerPos;
}
[self setCenterOfScreen:mainChar.position];
}
It is most likely your scene's content size. I believe you are moving the scene (CCScene) with the call to setCenterOfScreen as you move the player right? If I remember correctly the content size and bounding box for the scene doesn't change as you add children to it. If that is the case (or still the case) then you'll need to set the content size of the scene to the size of your entire level (map). You'll also want to ensure that the lower-left or your map aligns with the lower-left of the scene at (0,0) so that your scene's new bounding box encloses all of your map.
To check, after you've setup the scene CCLOG the scene's content size. If it is not the size of your map then you know that is the problem if you are indeed moving the scene with the call to setCenterOfScreen.
Another solution is to create a root content node that you add to the scene, then you add your game elements to that content node. That way the touch occurs on the scene but when you do the movements you move the content node, not the scene. For example:
#implementation YourScene
- (void)onEnter
{
[super onEnter];
self.contentNode = [CCNode node];
[self addChild:self.contentNode];
CCSprite* bg = ...;
CCSprite* player = ...;
[self.contentNode addChild:bg];
[self.contentNode addChild:player];
...
}
- (void)setCenterOfScreen:(CGPoint)position
{
CGSize screenSize = [[CCDirector sharedDirector] viewSize];
int x = MAX(position.x, screenSize.width/2);
int y = MAX(position.y, screenSize.height/2);
x = MIN(x, theMap.mapSize.width * theMap.tileSize.width - screenSize.width/2);
y = MIN(y, theMap.mapSize.height * theMap.tileSize.height - screenSize.height/2);
CGPoint goodPoint = ccp(x,y);
CGPoint centerOfScreen = ccp(screenSize.width/2, screenSize.height/2);
CGPoint difference = ccpSub(centerOfScreen, goodPoint);
self.contentNode.position = ccpAdd(self.contentNode.position, difference);
}
Hope this helps.

Pan resistance when dragging UIImageView?

some applications have this "drag resistance" when you drag an image. The further away you drag the image from the origin point, the less it becomes drag-able.
How can I implement this feature?
How about subclassing UIImageView and doing something like below: (Not perfect , but something along this lines maybe?)
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
CGPoint currentPosition = [[touches anyObject] locationInView:self.superview];
CGFloat dx = currentPosition.x - initialCentre.x;//delta x with sign
CGFloat dy = currentPosition.y - initialCentre.y;//delta y with sign
CGFloat distance = sqrt(dx*dx + dy*dy);//distance
CGFloat weightedX = initialCentre.x + dx * (1/log(distance)); //new x as inverse function of distance
CGFloat weightedY = initialCentre.y + dy * (1/log(distance)); //new y as inverse function of distance
self.center = CGPointMake(weightedX, weightedY);//set the center
}

UIImageView rotation animation in the touched direction

I have one UIImageView having an image of an arrow. When user taps on the UIView this arrow should point to the direction of the tap maintaing its position it should just change the transform. I have implemented following code. But it not working as expected. I have added a screenshot. In this screenshot when i touch the point upper left the arrow direction should be as shown.But it is not happening so.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch=[[event allTouches]anyObject];
touchedPoint= [touch locationInView:touch.view];
imageViews.transform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(rangle11));
previousTouchedPoint = touchedPoint ;
}
- (CGFloat) pointPairToBearingDegrees:(CGPoint)startingPoint secondPoint:(CGPoint) endingPoint
{
CGPoint originPoint = CGPointMake(endingPoint.x - startingPoint.x, endingPoint.y - startingPoint.y); // get origin point to origin by subtracting end from start
float bearingRadians = atan2f(originPoint.y, originPoint.x); // get bearing in radians
float bearingDegrees = bearingRadians * (180.0 / M_PI); // convert to degrees
bearingDegrees = (bearingDegrees > 0.0 ? bearingDegrees : (360.0 + bearingDegrees)); // correct discontinuity
return bearingDegrees;
}
I assume you wanted an arrow image to point to where ever you touch, I tried and this is what i could come up with. I put an image view with an arrow pointing upwards (haven't tried starting from any other position, log gives correct angles) and on touching on different locations it rotates and points to touched location. Hope it helps ( tried some old math :-) )
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch=[[event allTouches]anyObject];
touchedPoint= [touch locationInView:touch.view];
CGFloat angle = [self getAngle:touchedPoint];
imageView.transform = CGAffineTransformMakeRotation(angle);
}
-(CGFloat) getAngle: (CGPoint) touchedPoints
{
CGFloat x1 = imageView.center.x;
CGFloat y1 = imageView.center.y;
CGFloat x2 = touchedPoints.x;
CGFloat y2 = touchedPoints.y;
CGFloat x3 = x1;
CGFloat y3 = y2;
CGFloat oppSide = sqrtf(((x2-x3)*(x2-x3)) + ((y2-y3)*(y2-y3)));
CGFloat adjSide = sqrtf(((x1-x3)*(x1-x3)) + ((y1-y3)*(y1-y3)));
CGFloat angle = atanf(oppSide/adjSide);
// Quadrant Identifiaction
if(x2 < imageView.center.x)
{
angle = 0-angle;
}
if(y2 > imageView.center.y)
{
angle = M_PI/2 + (M_PI/2 -angle);
}
NSLog(#"Angle is %2f",angle*180/M_PI);
return angle;
}
-anoop4real
Given what you told me, I think the problem is that you are not resetting your transform in touchesBegan. Try changing it to something like this and see if it works better:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch=[[event allTouches]anyObject];
touchedPoint= [touch locationInView:touch.view];
imageViews.transform = CGAffineTransformIdentity;
imageViews.transform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(rangle11));
previousTouchedPoint = touchedPoint ;
}
Do you need the line to "remove the discontinuity"? Seems atan2f() returns values between +π to -π. Won't those work directly with CATransform3DMakeRotation()?
What you need is that the arrow points to the last tapped point. To simplify and test, I have used a tap gesture (but it's similar to a touchBegan:withEvent:).
In the viewDidLoad method, I register the gesture :
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapped:)];
[self.view addGestureRecognizer:tapGesture];
[tapGesture release];
The method called on each tap :
- (void)tapped:(UITapGestureRecognizer *)gesture
{
CGPoint imageCenter = mFlecheImageView.center;
CGPoint tapPoint = [gesture locationInView:self.view];
double deltaY = tapPoint.y - imageCenter.y;
double deltaX = tapPoint.x - imageCenter.x;
double angleInRadians = atan2(deltaY, deltaX) + M_PI_2;
mFlecheImageView.transform = CGAffineTransformMakeRotation(angleInRadians);
}
One key is the + M_PI_2 because UIKit coordinates have the origin at the top left corner (while in trigonometric, we use a bottom left corner).

ipad: how to move a sprite by sliding it and continue to go according to sliding speed

I have a sprite(image of a ball) I can move it using touch and move. I have also identified the rate of change of location(x-axis,y-axis) of that sprite depending on sliding period.
Now I need to continue that sprite to go according to its speed and direction. Here is my code-
Touch Event
- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
CGPoint location = [touch locationInView: [touch view]];
CGPoint convertedLocation = [[CCDirector sharedDirector] convertToGL:location];
self.touchStartTime = [event timestamp];
self.touchStartPosition = location;
if (YES == [self isItTouched:self.striker touchedAt:convertedLocation]) {
self.striker.isInTouch = YES;
}
return YES;
}
- (void)ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event {
CGPoint location = [touch locationInView: [touch view]];
CGPoint convertedLocation = [[CCDirector sharedDirector] convertToGL:location];
self.touchLastMovedTime = [event timestamp];
self.touchMovedPosition = convertedLocation;
if(self.striker.isInTouch == YES){
self.striker.position = self.touchMovedPosition;
}
}
- (void)ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event {
CGPoint location = [touch locationInView: [touch view]];
CGPoint convertedLocation = [[CCDirector sharedDirector] convertToGL:location];
self.touchEndTime = [event timestamp];
self.touchEndPosition = location;
if( self.striker.isInTouch == YES
&& ( self.touchEndTime - self.touchLastMovedTime ) <= MAX_TOUCH_HOLD_DURATION )
{
float c = sqrt( pow( self.touchStartPosition.x - self.touchEndPosition.x, 2 )
+ pow( self.touchStartPosition.y - self.touchEndPosition.y, 2 ) );
self.striker.speedx = ( c - ( self.touchStartPosition.y - self.touchEndPosition.y ) )
/ ( ( self.touchEndTime - self.touchStartTime ) * 1000 );
self.striker.speedy = ( c - ( self.touchStartPosition.x - self.touchEndPosition.x ) )
/ ( ( self.touchEndTime - self.touchStartTime ) * 1000 );
self.striker.speedx *= 4;
self.striker.speedy *= 4;
self.striker.isInTouch = NO;
[self schedule:#selector( nextFrame ) interval:0.001];
}
}
Scheduled Method to move Sprite
- (void) nextFrame {
[self setPieceNextPosition:self.striker];
[self adjustPieceSpeed:self.striker];
if( abs( self.striker.speedx ) <= 1 && abs( self.striker.speedy ) <= 1 ){
[self unschedule:#selector( nextFrame )];
}
}
SET next Position
- (void) setPieceNextPosition:(Piece *) piece{
CGPoint nextPosition;
float tempMod;
tempMod = ( piece.position.x + piece.speedx ) / SCREEN_WIDTH;
tempMod = (tempMod - (int)tempMod)*SCREEN_WIDTH;
nextPosition.x = tempMod;
tempMod = ( piece.position.y + piece.speedy ) / SCREEN_HEIGHT;
tempMod = (tempMod - (int)tempMod)*SCREEN_HEIGHT;
nextPosition.y = tempMod;
piece.position = nextPosition;
}
Set new Speed
- (void) adjustPieceSpeed:(Piece *) piece{
piece.speedx =(piece.speedx>0)? piece.speedx-0.05:piece.speedx+0.05;
piece.speedy =(piece.speedy>0)? piece.speedy-0.05:piece.speedy+0.05;
}
Though, currently I am using static speed adjusting technique, but I hope to make it dynamic depending on initial speed( I appreciate any idea)
It looks like you have it pretty close. You're scheduling a selector on the touch end, computing the velocity, and moving the sprite according to that velocity until it damps out, so all the mechanical pieces are there.
What looks iffy is the physics. For instance, you're doing some odd things with the speed to slow the ball down which aren't going to look realistic at all.
What you want to do is find the "velocity vector" for the sprite when the finger is lifted. While you can attempt to do an instantaneous velocity for this, I've found that it works better to sample a few cycles back and average over them to get a final speed.
Then, once you've got that vector (it will look like an x and y speed, like you have now), you want to dampen the speed by doing something like this (untested pseudocode ahead):
float speed = sqrt( speedx * speedx + speedy * speedy );
if (speed == 0) {
// kill motion here, unschedule or do whatever you need to
}
// Dampen the speed.
float newSpeed = speed * .9;
if (newSpeed < SOME_THRESHOLD) newSpeed = 0;
speedx = speedx * newSpeed / speed;
speedy = speedy * newSpeed / speed;
// Move sprite according to the new speed
This will keep the ball going in the same direction as when it was released, slowing down gradually until it stops. For more information, especially if you want to do some bouncing or anything, google for an introduction to vector algebra.
thanks #all.
I have got my solution as below-
//in adjustSpeed method
piece.speedx -= piece.speedx * DEACCLERATION_FACTOR;
piece.speedy -= piece.speedy * DEACCLERATION_FACTOR;
//and simply, free the sprite to move from ccTouchMoved method not ccTouchEnd
if(self.striker.isInTouch == YES && distance_passed_from_touch_start>=a_threashold){
//calculate speed and position here as it was in ccTouchEnd method
self.striker.isInTouch = NO;
[self schedule:#selector( nextFrame ) interval:0.001];
}

Resources