SpriteKit - Push sprite away from touch location - ios

I am building a game and want one of my sprites to move directly away from the place the player taps.
[+] (tap location)
[+] (player)
In the example above, the player would move to the left and downwards.
Can anyone assist me in the physics?
I realise I’m to use trigonometry to calculate the angle and therefore the vector (for applyImpulse:) but the calculations I have don’t work for each quadrant surrounding the player.
Here is my code (in touchesBegan:)
UITouch *touch = [touches.allObjects objectAtIndex:0];
CGPoint touchPosition = [touch locationInNode:self];
CGPoint playerPosition = self.playerBubble.position;
double oppositeLength = (touchPosition.y - playerPosition.y);
double adjacentLength = (touchPosition.x - playerPosition.x);
double angle = atan(oppositeLength / adjacentLength);
The vector is computed as:
CGFloat playerMass = 0.0000013;
CGVector vector = CGVectorMake(playerMass * cosl(angle), playerMass * sinl(angle));
Here are the vector outputs for each quadrant surrounding the player:
+, +:
O: 133.000000, A: 42.500000, Angle: 72.278778, Theta: 1.261503
{3.9570166773706921e-07, 1.2383134543301224e-06}
-, +:
O: 95.247955, A: -79.580551, Angle: -50.120930, Theta: -0.874775
{8.335201515172361e-07, -9.9761925504652419e-07}
+, -:
O: -145.927795, A: 52.148361, Angle: -70.335281, Theta: -1.227582
{4.3747011829674742e-07, -1.2241813250586403e-06}
-, -:
O: -138.968933, A: -92.015755, Angle: 56.490189, Theta: 0.985940
{7.1770369806877642e-07, 1.0839286982100348e-06}
EDIT:
Here is my updated answer which gives the right vectors (it’s called on a SKSpriteNode instance):
- (void)moveWithTouchPosition:(CGPoint)touchPosition
{
double heightLength = (self.position.y - touchPosition.y);
double widthLength = (self.position.x - touchPosition.x);
double angle = 0.0f;
if (touchPosition.y > self.position.y && touchPosition.x > self.position.x) {
//
// +, +
//
angle = M_PI + atan(heightLength / widthLength);
} else if (touchPosition.y > self.position.y && touchPosition.x < self.position.x) {
//
// -, +
//
angle = M_PI + M_PI_2 + atan(widthLength / heightLength);
} else if (touchPosition.y < self.position.y && touchPosition.x > self.position.x) {
//
// +, -
//
angle = M_PI_2 + atan(widthLength / heightLength);
} else if (touchPosition.y < self.position.y && touchPosition.x < self.position.x) {
//
// -, -
//
angle = atan(heightLength / widthLength);
}
CGVector vector = CGVectorMake(self.physicsBody.mass * cosl(angle), self.physicsBody.mass * sinl(angle));
[self.physicsBody applyImpulse:vector];
}

To simplify your code, you could use atan2 instead of atan. Atan2 does the quadrants for you.
Another option is to translate and multiply vectors instead of using trigonometry at all, which might look something like:
CGVector vector = CGVectorMake(
(playerPosition.x - touchPosition.x) * playerMass + touchPosition.x,
(playerPosition.y - touchPosition.y) * playerMass + touchPosition.y
);

Thanks to #Bemmu it appears that all I really needed to do was to use the atan2(y, x) function and parse in the Opp and Adj lengths of the triangle which were the differences between the y and x values respectively.
E.g:
double heightLength = (self.position.y - touchPosition.y);
double widthLength = (self.position.x - touchPosition.x);
double angle = atan2(heightLength, widthLength);
CGFloat acceleration = 100.0f;
CGVector vector = CGVectorMake(self.physicsBody.mass * (cosl(angle) * acceleration), self.physicsBody.mass * (sinl(angle) * acceleration));
[self.physicsBody applyImpulse:vector];

Related

Angle betwee three points ios xcode

CGPoint pointA = [self.appDelegate.points[0] CGPointValue];//first point
CGPoint pointB = [self.appDelegate.points[1] CGPointValue];// second point
CGPoint pointC = [self.appDelegate.points[2] CGPointValue];//third point
CGFloat slopeAB = (pointB.y - pointA.y)/(pointB.x - pointA.x);//slope ab
CGFloat slopeBC = (pointC.y - pointB.y)/(pointC.x - pointB.x);//slope bc
self.ang=(slopeAB-slopeBC)/(1+(slopeAB)*(slopeBC));//slope
CGFloat finalAngle = atanf(self.ang);// angle tan inverse slope
CGFloat angle = (finalAngle * (180.0/M_PI));
NSLog(#"The angle is: %.2f degrees",angle);
calculated the slope of each line
calculated angle by tan inverse
Use the atan2() function. From the manual page:
#include <math.h>
double
atan2(double y, double x);
The atan2() function computes the principal value of the arc tangent of
y/x, using the signs of both arguments to determine the quadrant of the
return value.
To do this for the three points you'll need to call atan2() twice: once to find the angle of AB, and once for the angle of BC. Take the difference between these two to find the angle between AB and BC:
double angle_ab = atan2(pointA.y - pointB.y, pointA.x - pointB.x);
double angle_cb = atan2(pointC.y - pointB.y, pointC.x - pointB.x);
double angle_abc = angle_ab - angle_cb;
Note that this is assuming that B is the "center" point of the angle you're interested in. Adjust appropriately if I've assumed wrongly.

Finding a desired point based on an objects velocity and rotation

I'm looking to find the point that is 200 pixels in front of an enemy object. My method to try calculate this point is this:
//all sprites start facing down, so to begin with the point 200 pixels infront of the sprite is its current pos -200 on the y axis.
CGPoint predictedPoint = CGPointMake(self.position.x, self.position.y - 200);
//get the direction of this vector from the current position.
predictedPoint = [Utilities MinusVector:predictedPoint Vector2:self.position];
predictedPoint = [Utilities CGPointNormalize:predictedPoint];
//multiply it by 200 to get 200 pixels ahead.
predictedPoint = [Utilities MultiplyVector:predictedPoint Scalar:200];
//work out which way to rotate the enemy based on its velocity. (this code works as the enemies face the way they move!)
CGPoint facingVector = [Utilities MinusVector:self.position Vector2:CGPointMake(self.position.x + self.velocity.x, self.position.y + self.velocity.y)];
float theta = (atan2f(facingVector.y, facingVector.x) - SK_DEGREES_TO_RADIANS(90.0f));
//rotate
float cs = cosf(theta);
float sn = sinf(theta);
float px = predictedPoint.x * cs - predictedPoint.y * sn;
float py = predictedPoint.x * sn + predictedPoint.y * cs;
CGPoint thePoint = CGPointMake(px, py);
NSLog(#"Player x: %f. thePoint x: %f. Player y: %f. thePoint y: %f.", self.position.x, px, self.position.y, py);
So the calculation should be
green.center.x = triangle.center.x + 200 * cos( theta );
green.center.y = triangle.center.y + 200 * sin( theta );
where theta is the current rotation angle of the triangle. This assumes that theta == 0 has the triangle pointing to the right. If the 0 angle has the sprite pointing down, then I think you need to subtract M_PI_2, e.g.
green.center.x = triangle.center.x + 200 * cos( theta - M_PI_2 );
green.center.y = triangle.center.y + 200 * sin( theta - M_PI_2 );

Finding an angle with 3 CGPoints

In my application, a user taps 3 times and an angle will be created by the 3 points that were tapped. It draws the angle perfectly. I am trying to calculate the angle at the second tap, but I think I am doing it wrong (probably a math error). I haven't covered this in my calculus class yet, so I am going off of a formula on wikipedia.
http://en.wikipedia.org/wiki/Law_of_cosines
Here is what I am trying:
Note: First, Second, and Third are CGPoints created at the user's tap.
CGFloat xDistA = (second.x - third.x);
CGFloat yDistA = (second.y - third.y);
CGFloat a = sqrt((xDistA * xDistA) + (yDistA * yDistA));
CGFloat xDistB = (first.x - third.x);
CGFloat yDistB = (first.y - third.y);
CGFloat b = sqrt((xDistB * xDistB) + (yDistB * yDistB));
CGFloat xDistC = (second.x - first.x);
CGFloat yDistC = (second.y - first.y);
CGFloat c = sqrt((xDistC * xDistC) + (yDistC * yDistC));
CGFloat angle = acos(((a*a)+(b*b)-(c*c))/((2*(a)*(b))));
NSLog(#"FULL ANGLE IS: %f, ANGLE IS: %.2f",angle, angle);
Sometimes, it gives the angle as 1 which doesn't make sense to me. Can anyone explain why this is, or how to fix it please?
Not sure if this is the main problem but it is a problem
Your answer gives the angle at the wrong point:
To get the angle in green (which is probably angle you want based on your variable names "first", "second" and "third), use:
CGFloat angle = acos(((a*a)+(c*c)-(b*b))/((2*(a)*(c))));
Here's a way that circumvents the law of cosines and instead calculates the angles of the two vectors. The difference between the angles is the searched value:
CGVector vec1 = { first.x - second.x, first.y - second.y };
CGVector vec2 = { third.x - second.x, third.y - second.y };
CGFloat theta1 = atan2f(vec1.dy, vec1.dx);
CGFloat theta2 = atan2f(vec2.dy, vec2.dx);
CGFloat angle = theta1 - theta2;
NSLog(#"angle: %.1f°, ", angle / M_PI * 180);
Note the atan2 function that takes the x and y components as separate arguments and thus avoids the 0/90/180/270° ambiguity.
The cosine formula implementation looks right; did you take into account that acos() returns the angle in radians, not in degrees? In order to convert into degrees, multiply the angle by 180 and divide by Pi (3.14159...).
The way I have done it is to calculate the two angles separately using atan2(y,x) then using this function.
static inline double
AngleDiff(const double Angle1, const double Angle2)
{
double diff = 0;
diff = fabs(Angle1 - Angle2);
if (diff > <Pi>) {
diff = (<2Pi>) - diff;
}
return diff;
}
The function deals in radians, but you can change <Pi> to 180 and <2Pi> to 360
Using this answer to compute angle of the vector:
CGFloat angleForVector(CGFloat dx, CGFloat dy) {
return atan2(dx, -dy) * 180.0/M_PI;
}
// Compute angle at point Corner, that is between AC and BC:
CGFloat angle = angleForVector(A.x - Corner.x, A.y - Corner.y)
- angleForVector(B.x - Corner.x, B.y - Corner.y);
NSLog(#"FULL ANGLE IS: %f, ANGLE IS: %.2f",angle, angle);

Animation with an image in iPad

Hi I have an image like a round top of a table.
I want to move it clockwise when ever user swipes from left to right and counter clockwise when user swipes from right to left.
Like moving a round table top in real time.
How can I do this in the app?
I am using the following code for rotation. Its from the TrackBall example.
The problem I am having is the when ever the image spins, it changes its position.
- (CATransform3D)rotationTransformForLocation:(CGPoint)location
{
CGFloat trackBallCurrentPoint[3] = {location.x - trackBallCenter.x, location.y - trackBallCenter.y, 0.0f};
if(fabs(trackBallCurrentPoint[0] - trackBallStartPoint[0]) < kTol && fabs(trackBallCurrentPoint[1] - trackBallStartPoint[1]) < kTol)
{
return CATransform3DIdentity;
}
CGFloat dist = trackBallCurrentPoint[0] * trackBallCurrentPoint[0] + trackBallCurrentPoint[1] * trackBallCurrentPoint[1];
if(dist > trackBallRadius * trackBallRadius)
{
// outside the center of the sphere so make it zero
trackBallCurrentPoint[2] = 0.0f;
}
else
{
trackBallCurrentPoint[2] = sqrt(trackBallRadius * trackBallRadius - dist);
}
// cross product yields the rotation vector
CGFloat rotationVector[3];
rotationVector[0] = trackBallStartPoint[1] * trackBallCurrentPoint[2] - trackBallStartPoint[2] * trackBallCurrentPoint[1];
rotationVector[1] = -trackBallStartPoint[0] * trackBallCurrentPoint[2] + trackBallStartPoint[2] * trackBallCurrentPoint[0];
rotationVector[2] = trackBallStartPoint[0] * trackBallCurrentPoint[1] - trackBallStartPoint[1] * trackBallCurrentPoint[0];
// calc the angle between the current point vector and the starting point vector
// use arctan so we get all eight quadrants instead of just the positive ones
// cos(a) = (start . current) / (||start|| ||current||)
// sin(a) = (||start X current||) / (||start|| ||current||)
// a = atan2(sin(a), cos(a))
CGFloat startLength = sqrt(trackBallStartPoint[0] * trackBallStartPoint[0] + trackBallStartPoint[1] * trackBallStartPoint[1] + trackBallStartPoint[2] * trackBallStartPoint[2]);
CGFloat currentLength = sqrt(trackBallCurrentPoint[0] * trackBallCurrentPoint[0] + trackBallCurrentPoint[1] * trackBallCurrentPoint[1] + trackBallCurrentPoint[2] * trackBallCurrentPoint[2]);
CGFloat startDotCurrent = trackBallStartPoint[0] * trackBallCurrentPoint[0] + trackBallStartPoint[1] * trackBallCurrentPoint[1] + trackBallStartPoint[2] * trackBallCurrentPoint[2]; // (start . current)
// start X current we have already calcualted in the rotation vector
CGFloat rotationLength = sqrt(rotationVector[0] * rotationVector[0] + rotationVector[1] * rotationVector[1] + rotationVector[2] * rotationVector[2]);
CGFloat angle = atan2(rotationLength / (startLength * currentLength), startDotCurrent / (startLength * currentLength));
// normalize the rotation vector
rotationVector[0] = rotationVector[0] / rotationLength;
rotationVector[1] = rotationVector[1] / rotationLength;
rotationVector[2] = rotationVector[2] / rotationLength;
CATransform3D rotationTransform = CATransform3DMakeRotation(angle, rotationVector[0], rotationVector[1], rotationVector[2]);
return CATransform3DConcat(baseTransform, rotationTransform);
}
Thanks in advance.
Take a look at a question I posed... you might be trying to do the same thing (I don't think the question covered it, but after getting rotation working I implemented pan gesture to allow the user to spin the disc in either direction)
How to rotate a flat object around its center in perspective view?

Get angle from 2 positions

I have 2 objects and when I move one, I want to get the angle from the other.
For example:
Object1X = 211.000000, Object1Y = 429.000000
Object2X = 246.500000, Object2Y = 441.500000
I have tried the following and every variation under the sun:
double radians = ccpAngle(Object1,Object2);
double degrees = ((radians * 180) / Pi);
But I just get 2.949023 returned where I want something like 45 degrees etc.
Does this other answer help?
How to map atan2() to degrees 0-360
I've written it like this:
- (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;
}
Running the code:
CGPoint p1 = CGPointMake(10, 10);
CGPoint p2 = CGPointMake(20,20);
CGFloat f = [self pointPairToBearingDegrees:p1 secondPoint:p2];
And this returns 45.
Hope this helps.
Here's how I'm doing it in Swift for those interested, it's based on #bshirley's answer above w/ a few modifications to help match to the calayer rotation system:
extension CGFloat {
var degrees: CGFloat {
return self * CGFloat(180) / .pi
}
}
extension CGPoint {
func angle(to comparisonPoint: CGPoint) -> CGFloat {
let originX = comparisonPoint.x - x
let originY = comparisonPoint.y - y
let bearingRadians = atan2f(Float(originY), Float(originX))
var bearingDegrees = CGFloat(bearingRadians).degrees
while bearingDegrees < 0 {
bearingDegrees += 360
}
return bearingDegrees
}
}
This provides a coordinate system like this:
90
180 0
270
Usage:
point.angle(to: point2)
CGPoint.zero.angle(to: CGPoint(x: 0, y: 1)) // 90
I modified #tomas' solution to be streamlined. It's likely (it was for me) that this math is going to be called frequently.
In my incarnation, you have to perform the difference between the two points yourself (or if you're lucky, (0,0) is already one of your points). The value being calculated is the direction of the point from (0,0). Yes, that's simple enough and you could inline it if you really want to. My preference is for more readable code.
I also converted it to a function call:
CGFloat CGPointToDegree(CGPoint point) {
// Provides a directional bearing from (0,0) to the given point.
// standard cartesian plain coords: X goes up, Y goes right
// result returns degrees, -180 to 180 ish: 0 degrees = up, -90 = left, 90 = right
CGFloat bearingRadians = atan2f(point.y, point.x);
CGFloat bearingDegrees = bearingRadians * (180. / M_PI);
return bearingDegrees;
}
If you don't want negative values, you need to convert it yourself. Negative values were fine for me - no need to make unneeded calculations.
I was using this in a cocos2d environment, this is how I call it: (Mathematically, we are translating the plane to make p0 the origin. Thus subtracting p0 from p1 (p0 - p0 = {0,0}). The angles are unchanged when the plane is translated.)
CGPoint p0 = self.position;
CGPoint p1 = other.position;
CGPoint pnormal = ccpSub(p1, p0);
CGFloat angle = CGPointToDegree(pnormal);
ccpSub is provided by cocos2d, it's subtraction of a tuple - you can do that yourself if you don't have that available
aside: it's generally not polite style to name the method as above with the CG___ naming scheme, which identifies the function as part of CoreGraphics - so if you want to rename it to MyConvertCGPointToBearing() or FredLovesWilma() then you should do that.
Tomas' answer in Swift 5
func angle(between starting: CGPoint, ending: CGPoint) -> CGFloat {
let center = CGPoint(x: ending.x - starting.x, y: ending.y - starting.y)
let radians = atan2(center.y, center.x)
let degrees = radians * 180 / .pi
return degrees > 0 ? degrees : 360 + degrees
}
There is no angle between two points. If you want to know the angle between the vectors from the origin (0,0) to the objects, use the scalar (dot) product:
theta = arccos ( (veca dot vecb) / ( |veca| * |vecb| )
The math std lib of the language your are using surely provides functions for arcus cosine, scalar product and length.
The vertex of the angle is the point (0,0).
Consider object1X=x1 ....object2Y=y2.
Angle(object1-object2) =
90 * ( (1 + sign(x1)) * (1 - sign(y1^2))
- (1 + sign(x2)) * (1 - sign(y2^2)) )
+ 45 * ( (2 + sign(x1)) * sign(y1)
- (2 + sign(x2)) * sign(y2) )
+ 180/pi() * sign(x1*y1) * atan( (abs(x1) - abs(y1)) / (abs(x1) + abs(y1)) )
- 180/pi() * sign(x2*y2) * atan( (abs(x2) - abs(y2)) / (abs(x2) + abs(y2)) )
Will leave it here. Corrected code, plus with rotation of the axis by 90 degrees counterclockwise. I've used it for touches. viewCenter is just center of the view
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let location = touch.location(in: self)
guard let viewCenter = self.viewCenter else { return }
let angle = angle(between: CGPoint(x: location.x, y: location.y) , ending:viewCenter)
print(angle)
}
}
func angle(between starting: CGPoint, ending: CGPoint) -> CGFloat {
let center = CGPoint(x: ending.x - starting.x, y: ending.y - starting.y)
let angle90 = deg2rad(90)
//Rotate axis by 90 degrees counter clockwise
let rotatedX = center.x * cos(angle90) + center.y * sin(angle90)
let rotatedY = -center.x * sin(angle90) + center.y * cos(angle90)
let radians = atan2(rotatedY, rotatedX)
let degrees = radians * 180 / .pi
return degrees > 0 ? degrees : degrees + 360
}
func deg2rad(_ number: CGFloat) -> CGFloat {
return number * .pi / 180
}

Resources