I am trying use the accelerometer to move an image within a circle. I am having issue that when the image hits the edge of the circle, it just moves the other side of the circle. My code is below:
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration {
//NSLog(#"x : %g", acceleration.x);
//NSLog(#"y : %g", acceleration.y);
//NSLog(#"z : %g", acceleration.z);
delta.x = acceleration.x * 10;
delta.y = acceleration.y * 10;
joypadCap.center = CGPointMake(joypadCap.center.x + delta.x, joypadCap.center.y - delta.y);
distance = sqrtf(((joypadCap.center.x - 160) * (joypadCap.center.x - 160)) +
((joypadCap.center.y -206) * (joypadCap.center.y - 206)));
//NSLog(#"Distance : %f", distance);
touchAngle = atan2(joypadCap.center.y, joypadCap.center.x);
NSLog(#"Angle : %f", touchAngle);
if (distance > 50) {
joypadCap.center = CGPointMake(160 - cosf(touchAngle) * 50, 206 - sinf(touchAngle) * 50);
}
I was having the same issue when attempting to implement a circular spirit level using CMDeviceMotion. I found it was an issue with the coordinates passed to atan2(y,x). This function requires cartesian coordinates, with (0,0) in the centre of the view. However, the screen coordinates have (0,0) in the top left corner. I created methods to convert a point between the two coordinate systems, and now it's working well.
I put up a sample project here on github, but here's the most important part:
float distance = sqrtf(((point.x - halfOfWidth) * (point.x - halfOfWidth)) +
((point.y - halfOfWidth) * (point.y - halfOfWidth)));
if (distance > maxDistance)
{
// Convert point from screen coordinate system to cartesian coordinate system,
// with (0,0) located in the centre of the view
CGPoint pointInCartesianCoordSystem = [self convertScreenPointToCartesianCoordSystem:point
inFrame:self.view.frame];
// Calculate angle of point in radians from centre of the view
CGFloat angle = atan2(pointInCartesianCoordSystem.y, pointInCartesianCoordSystem.x);
// Get new point on the edge of the circle
point = CGPointMake(cos(angle) * maxDistance, sinf(angle) * maxDistance);
// Convert back to screen coordinate system
point = [self convertCartesianPointToScreenCoordSystem:point inFrame:self.view.frame];
}
And:
- (CGPoint)convertScreenPointToCartesianCoordSystem:(CGPoint)point
inFrame:(CGRect)frame
{
float x = point.x - (frame.size.width / 2.0f);
float y = (point.y - (frame.size.height / 2.0f)) * -1.0f;
return CGPointMake(x, y);
}
- (CGPoint)convertCartesianPointToScreenCoordSystem:(CGPoint)point
inFrame:(CGRect)frame
{
float x = point.x + (frame.size.width / 2.0f);
float y = (point.y * -1.0f) + (frame.size.height / 2.0f);
return CGPointMake(x, y);
}
Related
I currently have 2 circles. One big circle and one little circle. The little circle has a tap gesture recognizer that allows it to be dragged by the user. I would like the little circle's center to go no further than the big circle's radius. I have 4 auto layout constraints on the inner circle. 1 for fixed width, 1 for fixed height, 1 for distance from center for x, and 1 for distance from center for y. Here is how I am going about this:
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {
if (recognizer.state == UIGestureRecognizerStateChanged) {
CGPoint translation = [recognizer translationInView:self.view];
CGFloat x = recognizer.view.center.x + translation.x;
CGFloat y = recognizer.view.center.y + translation.y;
CGPoint desiredPoint = CGPointMake(x, y);
//check if point the user is trying to get to is outside the radius of the outer circle
//if it is, set the center of the inner circle to the right position at the distance of the radius and with the same angle
if ([self distanceBetweenStartPoint:self.outerCircleView.center endPoint:desiredPoint] > self.outerCircleRadius) {
CGFloat angle = [self angleBetweenStartPoint:self.outerCircleView.center endPoint:actualPosition];
desiredPoint = [self findPointFromRadius:self.outerCircleRadius startPoint:self.outerCircleView.center angle:angle];
}
//adjust the constraints to move the inner circle
self.innerCircleCenterXConstraint.constant += actualPosition.x - recognizer.view.center.x;
self.innerCircleCenterYConstraint.constant += actualPosition.y - recognizer.view.center.y;
[recognizer setTranslation:CGPointMake(0.0, 0.0) inView:self.view];
}
}
- (CGFloat)distanceBetweenStartPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint {
CGFloat xDif = endPoint.x - startPoint.x;
CGFloat yDif = endPoint.y - startPoint.y;
//pythagorean theorem
return sqrt((xDif * xDif) + (yDif * yDif));
}
- (CGPoint)findPointFromRadius:(CGFloat)radius startPoint:(CGPoint)startPoint angle:(CGFloat)angle {
CGFloat x = radius * cos(angle) + startPoint.x;
CGFloat y = radius * sin(angle) + startPoint.y;
return CGPointMake(x, y);
}
- (CGFloat)angleBetweenStartPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint {
CGPoint originPoint = CGPointMake(endPoint.x - startPoint.x, endPoint.y - startPoint.y);
return atan2f(originPoint.y, originPoint.x);
}
This works almost perfectly. The problem is I try to find the percentage that the user moved towards the outside of the circle. So I use the distanceBetweenStartPoint(center of outer circle) endPoint(center of inner circle) method and divide that by the radius of the outer circle. This should give me a value of 1 when the circle has been dragged as far to one side as it can go. Unfortunately I am getting values like 0.9994324 or 1.000923. What could be causing this? Thanks for any insight!
I'm encountering some problems with rotating a uiview again. This time, im trying to rotate a uiview 1/12 the speed of another uiview I'm rotating at normal speed. However, when I try to accomplish this task, the uiview I'm trying to move slower moves like this:
1st Update
https://www.youtube.com/watch?v=wj3nRJo5CMM&feature=youtu.be
2nd Update
https://www.youtube.com/watch?v=YLRkUzXSDtQ&feature=youtu.be
Here's my code:
- (void)rotateHand:(UIPanGestureRecognizer *)panGesture {
if ([(UIPanGestureRecognizer*)panGesture state] == UIGestureRecognizerStateBegan) {
CGPoint touchPoint = [panGesture locationInView:[self view]];
float dx = touchPoint.x - minHandContainer.center.x;
float dy = touchPoint.y - minHandContainer.center.y;
arcTanMin = atan2(dy,dx);
arcTanHour = atan2(hourHand.center.x - minHandContainer.center.x, hourHand.center.y - minHandContainer.center.y);
if (arcTanMin < 0) {
arcTanMin = 2 * M_PI + arcTanMin;
}
if (arcTanHour < 0) {
arcTanHour = 2 * M_PI + arcTanMin;
}
NSLog(#"arcTanMin %f", arcTanMin);
startTransformMin = minHandContainer.transform;
startTransformHour = hourHandContainer.transform;
}
if ([(UIPanGestureRecognizer*)panGesture state] == UIGestureRecognizerStateChanged) {
CGPoint pt = [panGesture locationInView:[self view]];
float dx = pt.x - minHandContainer.center.x;
float dy = pt.y - minHandContainer.center.y;
float ang = atan2(dy,dx);
if (ang < 0) {
ang = 2 * M_PI + ang;
}
float angleDifferenceM = arcTanMin - ang;
float angleDifferenceH = arcTanHour + angleDifferenceM * (1.0/12.0);
NSLog(#"angleDiffM %f", angleDifferenceM);
NSLog(#"angleDiffH %f", angleDifferenceH);
minHandContainer.transform = CGAffineTransformRotate(startTransformMin, -angleDifferenceM);
hourHandContainer.transform = CGAffineTransformRotate(startTransformHour, -angleDifferenceH);
}
}
It appears that you're using arcTanMin as the starting reference angle for both the minute hand and hour hand. So when you make the jump across the x-axis, both angleDifferenceM and angleDifferenceH are making the jump (which is why at the moment of the jump the angle of the hour hand to the y-axis is the same as the angle of the minute hand to the x-axis), but angleDifferenceH doesn't need to make the jump. Change this:
float angleDifferenceH = angleDifferenceM * (1.0/12.0);
to
float angleDifferenceH = arcTanHour + angleDifferenceM * (1.0/12.0);
with an appropriate starting value for arcTanHour.
I have a latitude/longitude decimal coordinates that I would like to convert to points that will fit within the area of the iPad coordinate system.
My code takes in a latitude/longitude coordinates and converts them to cartesian coordinates (based on this question: convert to cartesian coordinates) The results of the conversion are (2494.269287, 575.376465).
Here is my code (no compile or runtime errors):
#define EARTH_RADIUS 6371
- (void)drawRect:(CGRect)rect
{
CGPoint latLong = {41.998035, -116.012215};
CGPoint newCoord = [self convertLatLongCoord:latLong];
NSLog(#"Cartesian Coordinate: (%f, %f)",newCoord.x, newCoord.y);
//Draw dot at coordinate
CGColorRef darkColor = [[UIColor colorWithRed:21.0/255.0
green:92.0/255.0
blue:136.0/255.0
alpha:1.0] CGColor];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, darkColor);
CGContextFillRect(context, CGRectMake(newCoord.x, newCoord.y, 100, 100));
}
-(CGPoint)convertLatLongCoord:(CGPoint)latLong
{
CGFloat x = EARTH_RADIUS * cos(latLong.x) * cos(latLong.y);
CGFloat y = EARTH_RADIUS * cos(latLong.x) * sin(latLong.y);
return CGPointMake(x, y);
}
How can I convert the decimal coordinates to coordinates that will show up on an iPad screen?
Your convertLatLongCoord: method makes no attempt to adjust the resulting point to screen coordinates. As written, you can get x and y values in the range -EARTH_RADIUS to +EARTH_RADIUS. These need to be scaled to fit the screen.
Something like the following should help:
- (CGPoint)convertLatLongCoord:(CGPoint)latLong {
CGFloat x = EARTH_RADIUS * cos(latLong.x) * cos(latLong.y) * SCALE + OFFSET;
CGFloat y = EARTH_RADIUS * cos(latLong.x) * sin(latLong.y) * SCALE + OFFSET;
return CGPointMake(x, y);
}
SCALE and OFFSET should be a value determined as follows:
CGSize screenSize = [UIScreen mainScreen].applicationFrame.size;
CGFloat SCALE = MIN(screenSize.width, screenSize.height) / (2.0 * EARTH_RADIUS);
CGFloat OFFSET = MIN(screenSize.width, screenSize.height) / 2.0;
This assumes you want the map to fill the smallest screen dimension.
In my app, I'm the game is set for landscape mode allowing the user to be able to flip the device with the game auto-rotating to match the angle. When the user tilts the device to its right, the hero will travel to the right, and when tilted to the left, the hero goes left.
I have that part figured out, but the problem comes in when the user flips the iDevice over (because of the headphone jack or placement of the speaker), the app is auto-rotated, but the rollingY is setting the hero's position.x to be inverted. Tilting left makes hero go right, and tilting right makes hero go left. After looking at the NSLogs of the accelY value when flipping around the iDevice, it doesn't seem possible to be able to reverse the position.x when rotated?
I guess this is more of a math question.
Here is my code:
-(void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
{
#define kFilteringFactor 0.75
static UIAccelerationValue rollingX = 0, rollingY = 0, rollingZ = 0;
rollingX = (acceleration.x * kFilteringFactor) + (rollingX * (1.0 - kFilteringFactor));
rollingY = (acceleration.y * kFilteringFactor) + (rollingY * (1.0 - kFilteringFactor));
rollingZ = (acceleration.z * kFilteringFactor) + (rollingZ * (1.0 - kFilteringFactor));
float accelX = rollingX;
float accelY = rollingY;
float accelZ = rollingZ;
NSLog(#"accelX: %f, accelY: %f, accelZ: %f", accelX, accelY, accelZ);
CGSize winSize = [CCDirector sharedDirector].winSize;
#define kRestAccelX 0.6
#define kShipxMaxPointsPerSec (winSize.height * 1.0)
#define kMaxDiffX 0.2
#define kRestAccelY 0.0
#define kShipyMaxPointsPerSec (winSize.width * 0.5)
#define kMaxDiffY 0.2
float accelDiffX = kRestAccelX - ABS(accelX);
float accelFractionX = accelDiffX / kMaxDiffX;
float pointsPerSecX = kShipxMaxPointsPerSec * accelFractionX;
float accelDiffY = -accelY;
float accelFractionY = accelDiffY / kMaxDiffY;
float pointsPerSecY = kShipyMaxPointsPerSec * accelFractionY;
_shipPointsPerSecX = pointsPerSecY;
_shipPointsPerSecY = pointsPerSecX;
CCLOG(#"accelX: %f, pointsPerSecX: %f", accelX, pointsPerSecX);
CCLOG(#"accelY: %f, pointsPerSecY: %f", accelY, pointsPerSecY);
}
-(void)updateShipPos:(ccTime)dt
{
CGSize winSize = [CCDirector sharedDirector].winSize;
float maxX = winSize.width - _ship.contentSize.width;
float minX = _ship.contentSize.width / 2;
float maxY = winSize.height - _ship.contentSize.height / 2;
float minY = _ship.contentSize.height / 2;
float newX = _ship.position.x + (_shipPointsPerSecX * dt);
float newY = _ship.position.y + (_shipPointsPerSecY * dt);
newX = MIN(MAX(newX, minX), maxX);
newY = MIN(MAX(newY, minY), maxY);
_ship.position = ccp(newX, newY);
}
The idea is, I'm trying to make it so that pointsPerSecY is positive when tilting to the right and negative when tilted to the left. The problem is when the user flips the device, they are inverted since the device is flipped. Is there a way to make it so that it will stay positive when tilted to the right and negative to the left, no matter the orientation, when flipped while being in landscape?
How about to do something like this?
if ([[UIApplication sharedApplication] statusBarOrientaion]==UIInterfaceOrientationLandscapeLeft) {
pointsPerSecY *= -1;
}
What are the coordinates used in UIViews and their corresponding superviews? I have this code which i would like to detect a 'corridor' where the user can touch... similar to this image:alt text http://img17.imageshack.us/img17/4416/bildschirmfoto20100721u.png
This is the code i have:
CGPoint touch = [recognizer locationInView:[shuttle superview]];
CGPoint centre = shuttle.center;
int outerRadius = shuttle.bounds.size.width/2;
int innerRadius = (shuttle.bounds.size.width/2) - 30;
if ((touch.x < outerRadius && touch.y <outerRadius)){
NSLog(#"in outer");
if(touch.x > innerRadius && touch.y > innerRadius) {
NSLog(#"in corridor");
}
}
The radii are approximately 500 and 600, and the touch x and y are 100 and 200...
Thus, the NSLog "in corridor" never gets called.
Thanks
Your condition is wrong. The corridor according to it is a square, with its center at (0, 0) instead of shuttle.center. Try
CGFloat dx = touch.x - centre.x;
CGFloat dy = touch.y - centre.y;
CGFloat r2 = dx*dx + dy*dy;
if (r2 < outerRadius*outerRadius) {
NSLog(#"in outer");
if (r2 > innerRadius*innerRadius)
NSLog(#"in corridor")
}
instead.
Even if the corridor is indeed expected to be a square, you should check with fabs(dx), fabs(dy) not touch.x, touch.y.