I have made a blue ball that can be dragged by the user only on a red circled path:
I want to check how many times the user make a forward or backward lap (the start & end is at the top of the circle path), for example - If he drag the ball in a clockwise way so the laps is +1, and if he drag it to other way the laps is -1.
I tried to do this(This inculdes the dragging of the ball and my try to count laps):
#IBAction func dragBall(recognizer: UIPanGestureRecognizer) {
let point = recognizer.locationInView(self.view);
let earthX = Double(point.x)
let earthY = Double(point.y)
let midViewXDouble = Double(midViewX)
let midViewYDouble = Double(midViewY)
let angleX = (earthX - midViewXDouble)
let angleY = (earthY - midViewYDouble)
let angle = atan2(angleY, angleX)
let earthX2 = midViewXDouble + cos(angle)*100
let earthY2 = midViewYDouble + sin(angle)*100
circlePath2 = UIBezierPath(arcCenter: CGPoint(x: earthX2,y: earthY2), radius: CGFloat(10), startAngle: CGFloat(0), endAngle:CGFloat(M_PI * 2), clockwise: true)
shapeLayer2.path = circlePath2.CGPath
if degrees == 0 {
laps += 1
print(laps)
}
}
And it worked! but when the user drags the ball very fast it do not calculate, and it do not calculate backwards.
Here is a possible solution, following the approach indicated in the comments. First you need some additional instance variables:
var previousAngle = -M_PI_2 // top position if y-coordinate points down
var totalAngle = 0.0
var laps = 0
In dragBall, you calculate how much the angle has changed.
Since the angle can "jump" from -π to π, or vice versa, the difference
is normalised to the range -π ... π:
var delta = angle - previousAngle
if delta > M_PI {
delta -= 2 * M_PI
} else if delta < -M_PI {
delta += 2 * M_PI
}
previousAngle = angle
Then update the total change in the angle:
totalAngle += delta
and from that you can determine the number of laps:
laps = Int(floor(totalAngle/(2 * M_PI)))
Related
I am making a brick breaker style game in Xcode for iOS. There is no paddle and balls are all launched from the same location (determined by where the first ball falls from the last level). There are a varying number of balls launched each time based on the number of balls the player has created. Each game starts with one ball but the user collects more in playing. When applying an impulse to the ball sprites I can only either move one sprite or all ball sprites at once. How do I move the ball sprites with a delay between each sprites launch?
Below I will go over what I have tried already. Thank you in advance for any help!
So far I have tried a couple of things. First I was trying to create all ball sprites prior to allowing the user to launch and having all ball sprites at the same location. Then I enumerated over them and tried to launch them with a delay there. However this simply launched all balls simultaneously after the delay, not each individually.
gameWorldNode.enumerateChildNodes(withName: BallCategoryName) {
node, stop in
let ball = node as! SKSpriteNode
ball.move(toParent: gameWorldNode)
//Calculate Vector
var dx = CGFloat(touchLocation.x - ball.position.x)
var dy = CGFloat(touchLocation.y - ball.position.y)
//Calculate Magnitude
let magnitude = sqrt(dx * dx + dy * dy)
dx /= magnitude
dy /= magnitude
//Create Vector
let vector = CGVector(dx: 30.0 * dx, dy: 30.0 * dy)
let secondsToDelay = 0.25
DispatchQueue.main.asyncAfter(deadline: .now() + secondsToDelay){
//Apply impulse
ball.physicsBody!.applyImpulse(vector)
}
}
Secondly, It occurred to me that it would probably be better for game performance if I didn't create the ball sprite until right before the impulse is applied, after the previous ball is launched. So I tried the following code. However, while I think I may be on the right path now, the newly created ball sprites don't launch. They get created but for some reason they don't apply the impulse.
let ball = gameWorldNode.childNode(withName: BallCategoryName) as! SKSpriteNode
ball.move(toParent: gameWorldNode)
//Calculate Vector
var dx = CGFloat(touchLocation.x - ball.position.x)
var dy = CGFloat(touchLocation.y - ball.position.y)
//Calculate Magnitude
let magnitude = sqrt(dx * dx + dy * dy)
dx /= magnitude
dy /= magnitude
//Create Vector
let vector = CGVector(dx: 30.0 * dx, dy: 30.0 * dy)
ball.physicsBody!.applyImpulse(vector)
var i = 1
while (i != ballsAvailabe) {
//Create new ball sprite
let nextBall: SKSpriteNode = Ball()
nextBall.position = CGPoint(x: locationX, y: locationY)
gameWorldNode.addChild(nextBall)
let secondsToDelay = 0.1
DispatchQueue.main.asyncAfter(deadline: .now() + secondsToDelay) {
//Apply impulse to ball
nextBall.physicsBody!.applyImpulse(vector)
}
//Update i
i += 1
}
I have also tried to add the sprites to an array and iterate over the array. However, this simply launches each ball at once still.
for _ in 0..<ballsAvailabe {
//Create new ball sprite
let ball: SKSpriteNode = Ball()
ball.position = CGPoint(x: locationX, y: locationY)
ball.physicsBody!.categoryBitMask = nextBallCategory
ball.color = nextBallInPlay
ball.colorBlendFactor = 1
ball.physicsBody!.contactTestBitMask = BottomCategory | AddABallCategory | RedBlockCategory | BlueBlockCategory | OrangeBlockCategory | PurpleBlockCategory | GreenBlockCategory | CyanBlockCategory
ballSprites.append(ball)
gameWorldNode.addChild(ball)
}
for sprite in ballSprites {
let secondsToDelay = 0.3
DispatchQueue.main.asyncAfter(deadline: .now() + secondsToDelay) {
sprite.physicsBody!.applyImpulse(vector)
}
}
EDIT:
Following Johns comment below, I have added code to my second attempt to ensure the physics bodies are created properly. Sorry for all the variables in the code they are just global variables I have created and keep in its own file to ensure I am keeping values consistent throughout the code. This works better. When the user only has one ball it works perfectly. However, when the user has collected more balls to launch it will sometimes launch perfectly with the delay to the expected location. Other times it will launch only the first ball. Other times it will launch to an unexpected location.
Heres the updated code:
//Grab existing ball object
let ball = gameWorldNode.childNode(withName: BallCategoryName) as! SKSpriteNode
//Assign to game world node
ball.move(toParent: gameWorldNode)
//Apply ball properties
ball.color = nextBallInPlay
ball.physicsBody = SKPhysicsBody(circleOfRadius: ball.size.width / 2)
ball.physicsBody!.categoryBitMask = RedBallCategory
ball.name = BallCategoryName
ball.colorBlendFactor = 1
ball.physicsBody?.pinned = false
ball.physicsBody?.mass = ballMass
ball.physicsBody?.affectedByGravity = ballGravity
ball.physicsBody?.allowsRotation = ballRotation
ball.physicsBody?.isDynamic = ballDynamic
ball.zPosition = ballZPos
ball.physicsBody?.friction = ballFriction
ball.physicsBody?.restitution = ballRestitution
ball.physicsBody?.linearDamping = ballLinearDaming
ball.physicsBody?.angularDamping = ballAngularDamping
//Stop more launches and accidently using power ups
isFingerOnScreen = true
canBallBeLaunched = false
canUsePowerUps = false
//Ensure physics world speed is accurate
physicsWorld.speed = 1
//Launching location
var dx = CGFloat(touchLocation.x - locationX)
var dy = CGFloat(touchLocation.y - locationY)
//Calculate Magnitude
let magnitude = sqrt(dx * dx + dy * dy)
dx /= magnitude
dy /= magnitude
//Create Vector
let vector = CGVector(dx: 30.0 * dx, dy: 30.0 * dy)
//Apply impule to first ball
ball.physicsBody!.applyImpulse(vector)
//loop to create x number of balls based on what the user has collected
var i = 1
while (i != ballsAvailabe) {
//Create new ball sprite
let nextBall: SKSpriteNode = Ball()
nextBall.position = CGPoint(x: locationX, y: locationY)
gameWorldNode.addChild(nextBall)
//Ball properties
nextBall.color = nextBallInPlay
nextBall.physicsBody = SKPhysicsBody(circleOfRadius: ball.size.width / 2)
nextBall.physicsBody!.categoryBitMask = RedBallCategory
nextBall.name = BallCategoryName
nextBall.colorBlendFactor = 1
nextBall.physicsBody?.pinned = false
nextBall.physicsBody?.mass = ballMass
nextBall.physicsBody?.affectedByGravity = ballGravity
nextBall.physicsBody?.allowsRotation = ballRotation
nextBall.physicsBody?.isDynamic = ballDynamic
nextBall.zPosition = ballZPos
nextBall.physicsBody?.friction = ballFriction
nextBall.physicsBody?.restitution = ballRestitution
nextBall.physicsBody?.linearDamping = ballLinearDaming
nextBall.physicsBody?.angularDamping = ballAngularDamping
//Delay between each ball
let secondsToDelay = 0.1
DispatchQueue.main.asyncAfter(deadline: .now() + secondsToDelay) {
//Apply impulse to ball
nextBall.physicsBody!.applyImpulse(vector)
}
//Update i
i += 1
}
I have a player that when the user taps I want to be able to spawn 8 bullets around the player (with a 45˚ separation between them) and proceed to move them outwards to the edge of the screen.
The circle from where the bullets originate from is correct, but the bullets in the bottom left of the screen seem to be moving faster than the ones in the top right. Also the bullets are facing sideways, not pointing outwards.
func fireSpecialWeapon() {
stride(from: 0, to: 2 * CGFloat.pi, by: 2 * CGFloat.pi / 10 ).forEach { angle in
let bullet = SKSpriteNode(imageNamed: "bulletCircle")
bullet.setScale(3)
bullet.zRotation = angle
bullet.position = player.position
bullet.zPosition = 2
//move outwards to the edge of the screen
let distance: CGFloat = 2000
let endPoint = CGPoint(x: distance * cos(angle), y: distance * sin(angle))
let move = SKAction.move(to: endPoint, duration: 2)
self.addChild(bullet)
bullet.run(move)
}
}
You should use trig to figure out the end point based on the angle. distance * sin is the y component and distance * cos is the x component. The code looks somethign like this:
stride(from: 0, to: 2 * CGFloat.pi, by: 2 * CGFloat.pi / 8).forEach { angle in
let bullet = SKSpriteNode(imageNamed: "bulletCircle")
bullet.setScale(3)
bullet.zRotation = angle
bullet.position = player.position
bullet.zPosition = 2
//move outwards to the edge of the screen
let distance: CGFloat = 500
let endPoint = CGPoint(x: distance * cos(angle), y: distance * sin(angle))
let move = SKAction.move(to: endPoint, duration: 2)
self.addChild(bullet)
bullet.run(move)
}
The first thing I noticed is that your rotation calculation is not correct.
First the rotation of each bullet should be pi / 4 from its neighbours.
So you should not use pi / I but (pi / 4) * I.
That should fix up the rotation but I’m not sure if that’s everything that’s not working.
I have a flying aircraft which I am following and I am also showing the path the aircraft has followed. I am drawing cylinders as a line for drawing the path. Its kind of drawing a line between 2 points. I have a cameraNode which is set to (0,200,200) initially. At that point I can see the aircraft. But when I start my flight. It goes out of the screen. I want 2 things :
Follow just the aircraft (Path won't matter).
Show whole path and also the aircraft.
I tried finding the min ad max x,y and z and taking average but it din't work. If you see below gif its too zoomed and aircraft has moved out of the screen
Here is how I set my camera:
- (void)setUpCamera {
SCNScene *workingScene = [self getWorkingScene];
_cameraNode = [[SCNNode alloc] init];
_cameraNode.camera = [SCNCamera camera];
_cameraNode.camera.zFar = 500;
_cameraNode.position = SCNVector3Make(0, 60, 50);
[workingScene.rootNode addChildNode:_cameraNode];
SCNNode *frontCameraNode = [SCNNode node];
frontCameraNode.position = SCNVector3Make(0, 100, 50);
frontCameraNode.camera = [SCNCamera camera];
frontCameraNode.camera.xFov = 75;
frontCameraNode.camera.zFar = 500;
[_assetActivity addChildNode:frontCameraNode]; //_assetActivity is the aircraft node.
}
Here is how I am changing camera position which is not working:
- (void)showRealTimeFlightPath {
DAL3DPoint *point = [self.aircraftLocation convertCooridnateTo3DPoint];
DAL3DPoint *previousPoint = [self.previousAircraftLocation convertCooridnateTo3DPoint];
self.minCoordinate = [self.minCoordinate findMinPoint:self.minCoordinate currentPoint:point];
self.maxCoordinate = [self.minCoordinate findMaxPoint:self.maxCoordinate currentPoint:point];
DAL3DPoint *averagePoint = [[DAL3DPoint alloc] init];
averagePoint = [averagePoint averageBetweenCoordiantes:self.minCoordinate maxPoint:self.maxCoordinate];
SCNVector3 positions[] = {
SCNVector3Make(point.x,point.y,point.z) ,
SCNVector3Make(previousPoint.x,previousPoint.y,previousPoint.z)
};
SCNScene *workingScene = [self getWorkingScene];
DALLineNode *lineNodeA = [[DALLineNode alloc] init];
[lineNodeA init:workingScene.rootNode v1:positions[0] v2:positions[1] radius:0.1 radSegementCount:6 lineColor:[UIColor greenColor]] ;
[workingScene.rootNode addChildNode:lineNodeA];
self.previousAircraftLocation = [self.aircraftLocation mutableCopy];
self.cameraNode.position = SCNVector3Make(averagePoint.x, averagePoint.y, z);
self.pointOfView = self.cameraNode;
}
Code in swift or objective c are welcomed.
Thanks!!
The first behavior you describe would most easily be achieved by chaining a look-at constraint and a distance constraint, both targeting the aircraft.
let lookAtConstraint = SCNLookAtConstraint(target: aircraft)
let distanceConstraint = SCNDistanceConstraint(target: aircraft)
distanceConstraint.minimumDistance = 10 // set to whatever minimum distance between the camera and aircraft you'd like
distanceConstraint.maximumDistance = 10 // set to whatever maximum distance between the camera and aircraft you'd like
camera.constraints = [lookAtConstraint, distanceConstraint]
For iOS 10 and earlier, you can implement a distance constraint using SCNTransformConstraint. Here's a basic (though slightly ugly 😛) implementation that uses linear interpolation to update the node's position.
func normalize(_ value: Float, in range: ClosedRange<Float>) -> Float {
return (value - range.lowerBound) / (range.upperBound - range.lowerBound)
}
func interpolate(from start: Float, to end: Float, alpha: Float) -> Float {
return (1 - alpha) * start + alpha * end
}
let target = airplane
let minimumDistance: Float = 10
let maximumDistance: Float = 15
let distanceConstraint = SCNTransformConstraint(inWorldSpace: false) { (node, transform) -> SCNMatrix4 in
let distance = abs(sqrt(pow(target.position.x - node.position.x, 2) + pow(target.position.y - node.position.y, 2) + pow(target.position.z - node.position.z, 2)))
let normalizedDistance: Float
switch distance {
case ...minimumDistance:
normalizedDistance = self.normalize(minimumDistance, in: 0 ... distance)
case maximumDistance...:
normalizedDistance = self.normalize(maximumDistance, in: 0 ... distance)
default:
return transform
}
node.position.x = self.interpolate(from: target.position.x, to: node.position.x, alpha: normalizedDistance)
node.position.y = self.interpolate(from: target.position.y, to: node.position.y, alpha: normalizedDistance)
node.position.z = self.interpolate(from: target.position.z, to: node.position.z, alpha: normalizedDistance)
return transform
}
The second behavior could be implemented by determining the bounding box of your aircraft and all of its path segments in the camera's local coordinate space, then updating the camera's distance from the center of that bounding box to frame all of those nodes in the viewport. frameNodes(_:), a convenience method that implements this functionality, was introduced in iOS 11 and is defined on SCNCameraController. I'd recommend using it if possible, unless you want to dive into the trigonometry yourself. You could use your scene view's default camera controller or create a temporary instance, whichever suits the needs of your app.
You need to calculate the angle of the velocity so that the camera points in the direction of the moving SCNNode.
This code will point you in the right direction.
func renderer(_ aRenderer: SCNSceneRenderer, didSimulatePhysicsAtTime time: TimeInterval) {
// get velocity angle using velocity of vehicle
var degrees = convertVectorToAngle(vector: vehicle.chassisBody.velocity)
// get rotation of current camera on X and Z axis
let eX = cameraNode.eulerAngles.x
let eZ = cameraNode.eulerAngles.z
// offset rotation on y axis by 90 degrees
// this needs work, buggy
let ninety = deg2rad(90)
// default camera Y Euler angle facing north at 0 degrees
var eY : Float = 0.0
if degrees != 0 {
eY = Float(-degrees) - Float(ninety)
}
// rotate camera direction using cameraNode.eulerAngles and direction of velocity as eY
cameraNode.eulerAngles = SCNVector3Make(eX, eY, eZ)
// put camera 25 points behind vehicle facing direction of velocity
let dir = calculateCameraDirection(cameraNode: vehicleNode)
let pos = pointInFrontOfPoint(point: vehicleNode.position, direction:dir, distance: 25)
// camera follows driver view from 25 points behind, and 10 points above vehicle
cameraNode.position = SCNVector3Make(pos.x, vehicleNode.position.y + 10, pos.z)
}
func convertVectorToAngle(vector: SCNVector3) -> CGFloat {
let degrees = atan2(vector.z, vector.x)
return CGFloat(degrees)
}
func pointInFrontOfPoint(point: SCNVector3, direction: SCNVector3, distance: Float) -> SCNVector3 {
var x = Float()
var y = Float()
var z = Float()
x = point.x + distance * direction.x
y = point.y + distance * direction.y
z = point.z + distance * direction.z
let result = SCNVector3Make(x, y, z)
return result
}
func calculateCameraDirection(cameraNode: SCNNode) -> SCNVector3 {
let x = -cameraNode.rotation.x
let y = -cameraNode.rotation.y
let z = -cameraNode.rotation.z
let w = cameraNode.rotation.w
let cameraRotationMatrix = GLKMatrix3Make(cos(w) + pow(x, 2) * (1 - cos(w)),
x * y * (1 - cos(w)) - z * sin(w),
x * z * (1 - cos(w)) + y*sin(w),
y*x*(1-cos(w)) + z*sin(w),
cos(w) + pow(y, 2) * (1 - cos(w)),
y*z*(1-cos(w)) - x*sin(w),
z*x*(1 - cos(w)) - y*sin(w),
z*y*(1 - cos(w)) + x*sin(w),
cos(w) + pow(z, 2) * ( 1 - cos(w)))
let cameraDirection = GLKMatrix3MultiplyVector3(cameraRotationMatrix, GLKVector3Make(0.0, 0.0, -1.0))
return SCNVector3FromGLKVector3(cameraDirection)
}
func deg2rad(_ number: Double) -> Double {
return number * .pi / 180
}
Hey I have a ball that gets moved by a force-applied. What im trying to get it to do is basically have the causal effect of gravity acting upon it while its moving through the air to its destination. basically when the "move to" action is playing gravity does not take affect so instead of slowly falling down to the ground it instead moves to its final position then it just falls straight down when the "move to" action stops. do to the gravity in the scene.
Im trying to get the ball to be thrown in an arc and land on the target?
Code:
func CreateBall() {
let BallScene = SCNScene(named: "art.scnassets/Footballs.dae")
Ball = BallScene!.rootNode.childNodeWithName("Armature", recursively: true)! //the Amature/Bones
Ballbody = BallScene!.rootNode.childNodeWithName("Ball", recursively: true)!
let collisionCapsuleRadius3 = CGFloat(0.01) // Width of physicsBody
let collisionCapsuleHeight3 = CGFloat(0.01) // Height of physicsBody
Ball.position = SCNVector3Make(Guy.position.x, Guy.position.y, Guy.position.z)
Ball.scale = SCNVector3Make(5, 5, 5)
Ball.rotation = SCNVector4Make(0.0,0.0,0.0,0.0) // x,y,z,w
Ball.physicsBody = SCNPhysicsBody(type: .Dynamic, shape:SCNPhysicsShape(geometry: SCNCapsule(capRadius: collisionCapsuleRadius3, height: collisionCapsuleHeight3), options:nil))
Ball.physicsBody?.affectedByGravity = true
Ball.physicsBody?.friction = 1 //
Ball.physicsBody?.restitution = 0 //bounceness of the object. 1.0 will boounce forever
Ball.physicsBody?.angularDamping = 1 // ability to rotate
Ball.physicsBody?.mass = 1
Ball.physicsBody?.rollingFriction = 1
Ball.physicsBody!.categoryBitMask = BitmaskCollision4
Ball.physicsBody?.contactTestBitMask = BitmaskCollision3 //| BitmaskCollision2
Ballbody.physicsBody?.collisionBitMask = BitmaskCollision2 | BitmaskCollision3 | BitmaskCollision//| BitmaskCollision2
scnView.scene!.rootNode.addChildNode(Ball)
scnView.scene!.rootNode.addChildNode(Ballbody)
}
CreateBall()
now this is where the magic happens:
scnView.scene!.physicsWorld.gravity = SCNVector3(x: 0, y: -9.8, z: 0)
let location = SCNVector3(Guy2.presentationNode.position.x, 0.0, Guy2.presentationNode.position.z + Float(50) )
let moveAction = SCNAction.moveTo(location, duration: 2.0)
Ball.runAction(SCNAction.sequence([moveAction]))
let forceApplyed = SCNVector3(x: 0.0, y: 100.0 , z: 0.0)
Ball.physicsBody?.applyForce(forceApplyed, atPosition: Ball.presentationNode.position, impulse: true)
Combining SCNActions and physics doesn't work, you need to use one or the other. Using physics you can calculate the exact force needed to propel your node to a target.
I have adapted a solution for Unity found here and utilised an SCNVector3 extension that makes some of the calculations much easier.
Basically you pass in an SCNNode that you want to throw, an SCNVector3 for the target and an angle (in radians) that you want the node to be thrown at. This function will then work out the force required to reach the target.
func shootProjectile() {
let velocity = ballisticVelocity(ball, target: target.position, angle: Float(0.4))
ball.physicsBody?.applyForce(velocity, impulse: true)
}
func ballisticVelocity(projectile:SCNNode, target: SCNVector3, angle: Float) -> SCNVector3 {
let origin = projectile.presentationNode.position
var dir = target - origin // get target direction
let h = dir.y // get height difference
dir.y = 0 // retain only the horizontal direction
var dist = dir.length() // get horizontal distance
dir.y = dist * tan(angle) // set dir to the elevation angle
dist += h / tan(angle) // correct for small height differences
// calculate the velocity magnitude
let vel = sqrt(dist * -scene.physicsWorld.gravity.y / sin(2 * angle))
return dir.normalized() * vel * Float(projectile.physicsBody!.mass)
}
It is also important to set the damping of the physicsBody to 0, otherwise it will be affected by air resistance.
I’m not going to pretend to know exactly how this works, but Wikipedia has articles that explain all the maths behind it.
UPDATE
Since using the code above I've noticed it doesn't always work, especially when the heights of the origin and target are different. From the same forum this function seems more reliable.
func calculateBestThrowSpeed(origin: SCNVector3, target: SCNVector3, timeToTarget:Float) -> SCNVector3 {
let gravity:SCNVector3 = sceneView.scene!.physicsWorld.gravity
let toTarget = target - origin
var toTargetXZ = toTarget
toTargetXZ.y = 0
let y = toTarget.y
let xz = toTargetXZ.length()
let t = timeToTarget
let v0y = y / t + 0.5 * gravity.length() * t
let v0xz = xz / t
var result = toTargetXZ.normalized()
result *= v0xz
result.y = v0y
return result
}
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
}