Okay so I'm making something that basically works as a knob, but only half on the screen -- so I just need horizontal swipes to cause it to rotate. I have it nearly all sorted with one exception: if you change direction of your swipe in mid-swipe, the rotation doesn't change direction. I even can see the problem, but not sure what to do about a solution.
So in my touchesMoved, I get the swipe into radians in the predictable way:
CGFloat radians = atan2f(location.y - centerY, location.x - centerX);
I then store radians, add/subtract it to previous rotation and then give the result to the CATransform3D.
So the prob is that even though the swipe changes direction, there is a "balance" which doesn't allow an immediate change of direction.
Does this make any sense?
First, I hope you get that atan() returns values in the range -π/2..+π/2, so you'll never get values in the other half of the circle. (If you deduce that the answer is on that side, you have to do your own flipping/mirroring.)
In your touches-moved, NSLog() what you've calculated the new difference to be, and be sure that it's the value you're expecting.
Of course, you keep the previous radians on touchesBegan, and save-off the current radians on init, touchesEnded and touchesCancelled, right?
(In many cases, touchesCancelled can just fwd the arguments to touchesEnded.)
If you do all of that, then the scheme you describe ought to work. If it does not, perhaps provide more information, like the NSLog() output for the various values when you think those values should be different.
For interactions like this, what I find usually works best is to save the state at the start of the interaction and the touch down location. Then, whenever you update, compute the new state based on the distance between the original touch and the current touch (applied as a difference from the original state).
For example, in the case you mention, what I would do on a touch down would be to store the current angle of the knob and the location of the touch. Then, on a touch move, I would compute the difference between the current touch location and the starting location. This will give you a delta value, which you can translate into an angle which you can add to the starting angle - update your knob graphic accordingly. This ought to allow you to change direction however you like throughout your behavior without worrying about keeping up with incremental changes to your angle. This also makes it extremely easy to handle things like resistance or dampening after a certain angle - far easier than it would be to handle that when updating things incrementally.
Be careful to use atan2 to avoid quadrant issues and division by zero. That's what it's there for.
// angle with +ve x-axis, in the range [0, 2π)
float getDirection(CGPoint V)
{
double theta = atan2(V.x, V.y); // angle with +ve x-axis, in the range (−π, π]
if (theta < 0)
theta = 2 * M_PI - theta;
return theta;
}
+ (CGPoint) vectorFrom: (CGPoint) A to: (CGPoint) B
{ return CGPointMake(B.x - A.x, B.y - A.y); }
i did something similar to this for simulating a turntable scratching motion. Try:
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithInt:0] forKey:kCATransactionAnimationDuration];
[myLayer setValue:[NSNumber numberWithFloat:radians] forKeyPath:#"transform.rotation.z"];
[CATransaction commit];
on the underlying layer of your view.
Related
I encountered another issue with hand-object interaction using a Leap Motion device. In particular, I use the LM to perform orientation manipulations on virtual objects.
I want to use the difference between the current and last orientation of the hand to manipulate the object:
Quaternion relativeOrientationHands = Quaternion.Inverse(currentHandOrientation) * updatedHand;
transform.rotation = transform.rotation * relativeOrientationHands;
It works fine, however the problem is that, let's say I rotate the object 180 degrees around the x axis (after that, y is pointing downwards). If I release the object afterwards, and grasp it again, the orientation changes get from then on applied to the new orientation of the object which is super confusing to use. (Original: turn hand to the left - object turns counter-clockwise; After: turn hand to the left - object turns clockwise)
Unfortunately, I don't know the math to fix it, but maybe someone can help me out. How can I apply the orientation changes of the hand to my virtual object, with respect to the default object orientation using Quaternions. Maybe, something with normal vectors?
EDIT:
The 3 figures illustrate my problem. The first figure shows the object in its original orientation (x-right, y-upwards). I turn the cube 180 degrees by rotating my hand 180 degrees clockwise around z (blue axis), after I release it, x is pointing to the left and y is pointing downwards (figure 2). If I grasp the object (figure 3) in its current orientation, and let's say I would want to perform a 90 degrees clockwise rotation around the y-axis. In figure one, I could do that by moving my hand 90 degrees clockwise. However, I changed the orientation of the cube with my previous manipulation. Therefore, I would have to perform a 90 degrees counter-clockwise rotation with my hand to move the object in clockwise direction, because the y-axis is flipped (figure 2-3). I always want that turning your hand 90 degrees clockwise results in a 90 degrees clockwise rotation of the object regardless of its current orientation.
Thank you very much
Best
R.Devel
I have a UIImageView that is set to move up and down the screen with the value of the accelerometer, using the following code:
ship.center = CGPointMake(ship.center.x, ship.center.y+shipPosition.y);
Where shipPosition is a CGPoint set in the accelerometerDidAccelerate method using:
shipPosition.y = acceleration.x*60;
Obviously this works fine, it is very simple. I run into trouble when I try to something equally simple, vary the rotation of the image depending on its acceleration. I do this using:
ship.transform = CGAffineTransformMakeRotation(shipPosition.y);
For some reason this causes a very strange thing to happen, in that the image snaps back to its origin every time the main method is called. I can see frames where the image moves to where it should be, but then instantly snaps back.
This problem only happens when I have the rotation line in, commented out it works fine. I have no idea what is going on here, I have done this many times for different apps and i never had such a problem. In fact I copied my code from a different app I created where it works fine.
EDIT:
What really confuses me is when I change the angle of the rotation from the acceleration to the position of the ship using:
ship.transform = CGAffineTransformMakeRotation(ship.center.y/10);
When I do this, the ship actually rotates based on the accelerometer but does not move, which is crazy because a changing ship.center.y means the position of the ship is changing, but it's not!!
You should set the transform of you view back to CGAffineTransformIdentity before you set his center coordinates or frame and after that apply the new transformation.
The frame property returns the transformed coordinates of a view if it is transformed and not the true (well actually the transformed are true) coordinates.
Quote from the docs:
Warning: If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.
Update/Actual Answer:
Well the actual problem is
shipPosition.y = acceleration.x*60;
Since you set the y pos in accelerometerDidAccelerate.
The acceleration won't remember it's old value. So if you move your device it will get a peak and as you slow down it will decelerate again.
Your ship will be +/-60 at the highest acceleration speed but will be 0 when you stop moving your device and shipPosition.y will be 0.
CGAffineTransformMakeRotation expects angle in radians, not in degrees.
1 radian = M_PI / 180.0 degrees
I'm using CGAffineTransformMakeRotation to rotate a subview using its transform property. Later, I need to find out how far the subview has been rotated. I realize that I could simply use an int to keep track of this, but is there a simple way to get the current rotation of the subview?
CGAffineTransformMakeRotation is explicitly defined to return a matrix with cos(angle) in the transform's a value, sin(angle) in b, etc (and, given the way the transform works, that's the only thing it really could do).
Hence you can work out the current rotation by doing some simple inverse trigonometry. atan2 is probably the thing to use, because it'll automatically figure out the quadrants appropriately.
So, e.g.
- (float)currentAngleOfView:(UIView *)view
{
CGAffineTransform transform = view.transform;
return atan2f(transform.b, transform.a);
}
Because that'll do an arctangent that'll involve dividing the a and b fields in an appropriate manner, that method will continue to work even if you apply scaling (as long as it's the same to each axis) or translation.
If you want to apply more complex or arbitrary transformations then things get a lot more complicated. You'll want to look how to calculate normal matrices. From memory I think you'd want the adjugate, which is about as much fun to work out as it sounds.
I am trying to make an app that uses a radial dial. As an example the radial dial features as availabel in this app
http://itunes.apple.com/us/app/kitchen-dial/id448558628?mt=8&ign-mpt=uo%3D4
What I wanted to ask is what method should I use to accomplish this? Guys, I am not asking you to write the code for me what I am asking is how is this done conceptually? I mean when a user swipses left/right on the screen how does it know which radial dial to move and in which direction. I am sure there are multiple buttons and there is some image transition going on here. I need to understand the concept of making such a thing. If anyone know or has done something similar please do share your thoughts. Does apple has a sample code for this? Thank you.
The central concepts are:
Ability to rotate an image around a center point.
Ability to calculate an angle relative to the center point given an x,y coordinate.
So, to move the wheel:
On touch began:
Calculate starting angle based on x,y
On touch moved:
Calculate new angle based on x,y and subtract starting angle. This is the angle delta (or how much more to rotate the image by).
To register button taps on the wheel:
On tap:
Calculate angle based on x,y, add existing rotation, divide by the angle size of each segment to get an index.
You asked for a high-level explanation of the concepts - and that's pretty much it.
This is a complicated question. The simple answer is: use a pan gesture recognizer, with:
- (void)panBegan:(UIPanGestureRecognizer *)pan {
CGRect frame = [self frame];
_central = CGPointMake(frame.origin.x+frame.size.width/2, frame.origin.y+frame.size.height/2);
CGPoint p = [pan locationInView:[self superview]];
p.x -= _central.x;
p.y -= _central.y;
_angle = atan2(p.x, p.y);
}
- (void)panMoved:(UIPanGestureRecognizer *)pan {
CGPoint p = [pan locationInView:[self superview]]];
p.x -= _central.x;
p.y -= _central.y;
CGFloat deltaAngle = _angle - atan2(p.x, p.y);
self.transform = CGAffineTransformMakeRotation(deltaAngle + _finalAngle);
}
where _angle is angle of the initial touch of the pan, and _finalAngle is saved angle at the end of the previous pan.
But: the UIPanGestureRecognizer has a velocity property, and you should be using it at the end of the pan to continue the rotation, as if the dial had inertia. And, if the dial has a fixed integer set of legal stopping positions, then you must adjust the slowdown curve so it stops on a legal stop. And, if you animate the deceleration, and the user starts a new pan before the previous one is finished, then you have to adjust _finalAngle based on the perceived position, not the stored position.
Here is a list of rotary knobs that are open source, so you can look at the code and see whats involved:
http://maniacdev.com/2011/12/open-source-libraries-for-easily-adding-rotary-knob-controls-in-your-ios-apps/
I am using
float theAngle = atan2( location.y-self.center.y, location.x-self.center.x );
to rotate a wheel at a certain angle.
and
self.transform = CGAffineTransformMakeRotation(angleRadians);
for the transformation to take place.
But everytime the user represses the wheel to turn it, it goes back to the original location and starts from there. How may I stop this from happening please?
Thanks!
You may setting the rotation angle value of the wheel rather than adding the value to the current angle.
I would hazzard a guess that the reason is that you are calculating based on the original position of the wheel rather than the previous position of the wheel.
i.e. Presuming that self.center represents the original position of the wheel as first displayed, location represents the users desired new rotation and looking up atan2 on wikipedia (trig is not my strong point :-) I would guess you need self.location to be updated each time to the be location so that the next rotation starts from the last rotation.
Just a guess :-)