How to apply impulse to the node on touch angle - swift

I want the node to move in the right direction but the impulse to be with set strength.
let node: SKSpriteNode!;
node = SKSpriteNode(color: UIColor.greenColor(), size: CGSizeMake(50, 50));
node.physicsBody = SKPhysicsBody(rectangleOfSize: node.size);
node.physicsBody?.affectedByGravity = false;
node.physicsBody?.allowsRotation = false;
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
node.physicsBody?.velocity = CGVectorMake(0, 0);
// ver 1
node.physicsBody?.applyImpulse(CGVectorMake((0.4) * (location.x - node.position.x), (0.4) * (location.y - node.position.y)), atPoint: CGPointMake(position.x,position.y));
// ver 2
let offset:CGPoint = self.vecSub(location, b: ghost.position);
let direction: CGPoint = self.vecNormalize(offset);
var len: CGPoint = self.vecMult(direction, b: 40);
let impulseVector:CGVector = CGVectorMake(len.x, len.y);
ghost.physicsBody?.applyImpulse(impulseVector);
}
func vecAdd(a: CGPoint, b:CGPoint) -> CGPoint {
return CGPointMake(a.x + b.x, a.y + b.y);
}
func vecSub(a: CGPoint, b:CGPoint) -> CGPoint {
return CGPointMake(a.x - b.x, a.y - b.y);
}
func vecMult(a: CGPoint, b:CGFloat) -> CGPoint {
return CGPointMake(a.x * b, a.y * b);
}
func vecLenght(a:CGPoint)->CGFloat{
return CGFloat( sqrtf( CFloat(a.x) * CFloat(a.x) + CFloat(a.y) * CFloat(a.y)));
}
func vecNormalize(a:CGPoint)->CGPoint{
let len : CGFloat = self.vecLenght(a);
return CGPointMake(a.x / len, a.y / len);
}
version 1 is horrible
version 2 is okay, but is too expensive
version 3: something not expensive and to apply impulse with 15-100 strength, because if the touch is at the edges of the screen the node should move only 15-100 of its current possition without reaching the touch position

Both the methods you've detailed above work so I'm not exactly sure what you're problem is. Also, I'm not sure method two is it's too expensive, are you having frame rate drops using it?
But I've got another way you could do what you're after, it's basically the second version but cleaned up. But firstly I just wanted to point out a couple things with your current code:
1. You don't need to ; to the end of each line.
2. Instead of using vecAdd, vecSub etc you could overload the +, -, * and / operators which would make your code cleaner and clearer. This way, the operators would also be global so you could use them anywhere else you need to manipulate vectors.
Anyway, here's my attempt at it:
Firstly, extend CGVector to add the functionality you need. Things like length, which you were defining as functions, could be properties of CGVector:
extension CGVector {
init(p1: CGPoint, p2: CGPoint) {
self.dx = p1.x - p2.x
self.dy = p1.y - p2.y
}
var length: CGFloat {
get { return hypot(dx, dy) }
set {
normalise()
dx *= newValue
dy *= newValue
}
}
mutating func normalise() {
dx /= length
dy /= length
}
}
Secondly, using the new methods:
var vec = CGVector(p1: location, p2: ghost.position)
vec.length = 40
ghost.physicsBody!.applyImpulse(vec)
Alternatively, if you wanted the size of the impulse to relate to how far away from the ghost the user pressed, use the following:
vec.length *= 0.1
Hope that helps!

Related

Keep mouse within area in Swift

I'm trying to prevent the mouse cursor from leaving a specific area of the screen. I can't find a native method to do this, so I'm trying to do it manually.
So far I have this:
NSEvent.addGlobalMonitorForEvents(matching: [.mouseMoved, .leftMouseDragged], handler: {(event: NSEvent) in
let x = event.locationInWindow.flipped.x;
let y = event.locationInWindow.flipped.y;
if (x <= 100) {
CGWarpMouseCursorPosition(CGPoint(x: 100, y: y))
}
})
// elsewhere to flip y coordinates
extension NSPoint {
var flipped: NSPoint {
let screenFrame = (NSScreen.main?.frame)!
let screenY = screenFrame.size.height - self.y
return NSPoint(x: self.x, y: screenY)
}
}
This stops the cursor from going off the X axis. Great. But it also stops the cursor from sliding along the y axis at X=100.
So I tried to add the delta:
NSEvent.addGlobalMonitorForEvents(matching: [.mouseMoved, .leftMouseDragged], handler: {(event: NSEvent) in
let x = event.locationInWindow.flipped.x;
let y = event.locationInWindow.flipped.y;
let deltaY = event.deltaY;
if (x <= 100) {
CGWarpMouseCursorPosition(CGPoint(x: 100, y: y + deltaY))
}
})
Now it does slide along the Y axis. But the acceleration is way off, it's too fast. What I don't get is that if I try to do y - deltaY it slides like I expect, but reversed:
NSEvent.addGlobalMonitorForEvents(matching: [.mouseMoved, .leftMouseDragged], handler: {(event: NSEvent) in
let x = event.locationInWindow.flipped.x;
let y = event.locationInWindow.flipped.y;
let deltaY = event.deltaY;
if (x <= 100) {
CGWarpMouseCursorPosition(CGPoint(x: 100, y: y - deltaY))
}
})
Now the cursor is sliding along the Y axis at X=100 with proper acceleration (like sliding the cursor against the edge of the screen), but it's reversed. Moving the mouse up, moves the cursor down.
How do I get proper smooth sliding of the cursor, in the proper direction, at the edge of my custom area?
Or is there a better way to achieve what I'm trying to do?
I figured it out. I need to subtract the previous deltas.
So now I have this instead:
var oldDeltaX: CGFloat = 0;
var oldDeltaY: CGFloat = 0;
NSEvent.addGlobalMonitorForEvents(matching: [.mouseMoved, .leftMouseDragged, .rightMouseDragged], handler: {(event: NSEvent) in
let deltaX = event.deltaX - oldDeltaX;
let deltaY = event.deltaY - oldDeltaY;
let x = event.locationInWindow.flipped.x;
let y = event.locationInWindow.flipped.y;
let window = (NSScreen.main?.frame.size)!;
let width = CGFloat(1920);
let height = CGFloat(1080);
let widthCut = (window.width - width) / 2;
let heightCut = (window.height - height) / 2;
let xPoint = clamp(x + deltaX, minValue: widthCut, maxValue: window.width - widthCut);
let yPoint = clamp(y + deltaY, minValue: heightCut, maxValue: window.height - heightCut);
oldDeltaX = xPoint - x;
oldDeltaY = yPoint - y;
CGWarpMouseCursorPosition(CGPoint(x: xPoint, y: yPoint));
});
public func clamp<T>(_ value: T, minValue: T, maxValue: T) -> T where T : Comparable {
return min(max(value, minValue), maxValue)
}
extension NSPoint {
var flipped: NSPoint {
let screenFrame = (NSScreen.main?.frame)!
let screenY = screenFrame.size.height - self.y
return NSPoint(x: self.x, y: screenY)
}
}
This will restrict the mouse in a 1920x1080 square of the display.
I found Godot's source code to be good resource: https://github.com/godotengine/godot/blob/51a00c2855009ce4cd6475c09209ebd22641f448/platform/osx/display_server_osx.mm#L1087
Is this the best or most perfomant way to do it? I don't know, but it works.

Is it possible to add a UILabel or CATextLayer to a CGPath in Swift, similar to Photoshop's type to path feature?

I would like to add text, whether it be a UILabel or CATextLayer to a CGPath. I realize that the math behind this feature is fairly complicated but wondering if Apple provides this feature out of the box or if there is an open-source SDK out there that makes this possible in Swift. Thanks!
Example:
You'll need to do this by hand, by computing the Bezier function and its slope at each point you care about, and then drawing a glyph at that point and rotation. You'll need to know 4 points (traditionally called P0-P3). P0 is the starting point of the curve. P1 and P2 are the control points. And P3 is the ending point in the curve.
The Bezier function is defined such that as the "t" parameter moves from 0 to 1, the output will trace the desired curve. It's important to know here that "t" is not linear. t=0.25 does not necessarily mean "1/4 of the way along the curve." (In fact, that's almost never true.) This means that measuring distances long the curve is a little tricky. But we'll cover that.
First, you'll need the core functions and a helpful extension on CGPoint:
// The Bezier function at t
func bezier(_ t: CGFloat, _ P0: CGFloat, _ P1: CGFloat, _ P2: CGFloat, _ P3: CGFloat) -> CGFloat {
(1-t)*(1-t)*(1-t) * P0
+ 3 * (1-t)*(1-t) * t * P1
+ 3 * (1-t) * t*t * P2
+ t*t*t * P3
}
// The slope of the Bezier function at t
func bezierPrime(_ t: CGFloat, _ P0: CGFloat, _ P1: CGFloat, _ P2: CGFloat, _ P3: CGFloat) -> CGFloat {
0
- 3 * (1-t)*(1-t) * P0
+ (3 * (1-t)*(1-t) * P1) - (6 * t * (1-t) * P1)
- (3 * t*t * P2) + (6 * t * (1-t) * P2)
+ 3 * t*t * P3
}
extension CGPoint {
func distance(to other: CGPoint) -> CGFloat {
let dx = x - other.x
let dy = y - other.y
return hypot(dx, dy)
}
}
t*t*t is dramatically faster than using the pow function, which is why the code is written this way. These functions will be called a lot, so they need to be reasonably fast.
Then there is the view itself:
class PathTextView: UIView { ... }
First it includes the control points, and the text:
var P0 = CGPoint.zero
var P1 = CGPoint.zero
var P2 = CGPoint.zero
var P3 = CGPoint.zero
var text: NSAttributedString {
get { textStorage }
set {
textStorage.setAttributedString(newValue)
locations = (0..<layoutManager.numberOfGlyphs).map { [layoutManager] glyphIndex in
layoutManager.location(forGlyphAt: glyphIndex)
}
lineFragmentOrigin = layoutManager
.lineFragmentRect(forGlyphAt: 0, effectiveRange: nil)
.origin
}
}
Every time the text is changed, the layoutManager recomputes the locations of all of the glyphs. We'll later adjust those values to fit the curve, but these are the baseline. The positions are the positions of each glyph relative to the fragment origin, which is why we need to keep track of that, too.
Some odds and ends:
private let layoutManager = NSLayoutManager()
private let textStorage = NSTextStorage()
private var locations: [CGPoint] = []
private var lineFragmentOrigin = CGPoint.zero
init() {
textStorage.addLayoutManager(layoutManager)
super.init(frame: .zero)
backgroundColor = .clear
}
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
The Bezier function is actually a one-dimensional function. In order to use it in two dimensions, we call it twice, once for x and once for y, and similarly to compute the rotations at each point.
func getPoint(forOffset t: CGFloat) -> CGPoint {
CGPoint(x: bezier(t, P0.x, P1.x, P2.x, P3.x),
y: bezier(t, P0.y, P1.y, P2.y, P3.y))
}
func getAngle(forOffset t: CGFloat) -> CGFloat {
let dx = bezierPrime(t, P0.x, P1.x, P2.x, P3.x)
let dy = bezierPrime(t, P0.y, P1.y, P2.y, P3.y)
return atan2(dy, dx)
}
One last piece of housekeeping, and it'll be time to dive into the real function. We need a way to compute how much we must change "t" (the offset) in order to move a certain distance along the path. I do not believe there is any simple way to compute this, so instead we iterate to approximate it.
// Simplistic routine to find the offset along Bezier that is
// aDistance away from aPoint. anOffset is the offset used to
// generate aPoint, and saves us the trouble of recalculating it
// This routine just walks forward until it finds a point at least
// aDistance away. Good optimizations here would reduce the number
// of guesses, but this is tricky since if we go too far out, the
// curve might loop back on leading to incorrect results. Tuning
// kStep is good start.
func getOffset(atDistance distance: CGFloat, from point: CGPoint, offset: CGFloat) -> CGFloat {
let kStep: CGFloat = 0.001 // 0.0001 - 0.001 work well
var newDistance: CGFloat = 0
var newOffset = offset + kStep
while newDistance <= distance && newOffset < 1.0 {
newOffset += kStep
newDistance = point.distance(to: getPoint(forOffset: newOffset))
}
return newOffset
}
OK, finally! Time to draw something.
override func draw(_ rect: CGRect) {
let context = UIGraphicsGetCurrentContext()!
var offset: CGFloat = 0.0
var lastGlyphPoint = P0
var lastX: CGFloat = 0.0
// Compute location for each glyph, transform the context, and then draw
for (index, location) in locations.enumerated() {
context.saveGState()
let distance = location.x - lastX
offset = getOffset(atDistance: distance, from: lastGlyphPoint, offset: offset)
let glyphPoint = getPoint(forOffset: offset)
let angle = getAngle(forOffset: offset)
lastGlyphPoint = glyphPoint
lastX = location.x
context.translateBy(x: glyphPoint.x, y: glyphPoint.y)
context.rotate(by: angle)
// The "at:" in drawGlyphs is the origin of the line fragment. We've already adjusted the
// context, so take that back out.
let adjustedOrigin = CGPoint(x: -(lineFragmentOrigin.x + location.x),
y: -(lineFragmentOrigin.y + location.y))
layoutManager.drawGlyphs(forGlyphRange: NSRange(location: index, length: 1),
at: adjustedOrigin)
context.restoreGState()
}
}
And with that you can draw text along any cubic Bezier.
This doesn't handle arbitrary CGPaths. It's explicitly for cubic Bezier. It's pretty straightforward to adjust this to work along any of the other types of paths (quad curves, arcs, lines, and even rounded rects). However, dealing with multi-element paths opens up a lot more complexity.
For a complete example using SwiftUI, see CurvyText.

Bounce rays with enumerateBodies alongRayStart

I want to trace the path where a bullet will move in my SpriteKit GameScene.
I'm using "enumerateBodies(alongRayStart", I can easily calculate the first collision with a physics body.
I don't know how to calculate the angle of reflection, given the contact point and the contact normal.
I want to calculate the path, over 5 reflections/bounces, so first I:
Cast a ray, get all the bodies it intersects with, and get the closest one.
I then use that contact point as the start of my next reflection/bounce....but I'm struggling with what the end point should be set to....
What I think I should be doing is getting the angle between the contact point and the contact normal, and then calculating a new point opposite to that...
var points: [CGPoint] = []
var start: CGPoint = renderComponent.node.position
var end: CGPoint = crossHairComponent.node.position
points.append(start)
var closestNormal: CGVector = .zero
for i in 0...5 {
closestNormal = .zero
var closestLength: CGFloat? = nil
var closestContact: CGPoint!
// Get the closest contact point.
self.physicsWorld.enumerateBodies(alongRayStart: start, end: end) { (physicsBody, contactPoint, contactNormal, stop) in
let len = start.distance(point: contactPoint)
if closestContact == nil {
closestNormal = contactNormal
closestLength = len
closestContact = contactPoint
} else {
if len <= closestLength! {
closestLength = len
closestNormal = contactNormal
closestContact = contactPoint
}
}
}
// This is where the code is just plain wrong and my math fails me.
if closestContact != nil {
// Calculate intersection angle...doesn't seem right?
let v1: CGVector = (end - start).normalized().toCGVector()
let v2: CGVector = closestNormal.normalized()
var angle = acos(v1.dot(v2)) * (180 / .pi)
let v1perp = CGVector(dx: -v1.dy, dy: v1.dx)
if(v2.dot(v1perp) > 0) {
angle = 360.0 - angle
}
angle = angle.degreesToRadians
// Set the new start point
start = closestContact
// Calculate a new end point somewhere in the distance to cast a ray to, so we can repeat the process again
let x = closestContact.x + cos(angle)*100
let y = closestContact.y + sin(-angle)*100
end = CGPoint(x: x, y: y)
// Add points to array to draw them on the screen
points.append(closestContact)
points.append(end)
}
}
I guess you are looking for something like this right?
1. Working code
First of all let me post the full working code. Just create a new Xcode project based SpriteKit and
In GameViewController.swift set
scene.scaleMode = .resizeFill
Remove the usual label you find in GameScene.sks
Replace Scene.swift with the following code
>
import SpriteKit
class GameScene: SKScene {
override func didMove(to view: SKView) {
self.physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
}
var angle: CGFloat = 0
override func update(_ currentTime: TimeInterval) {
removeAllChildren()
drawRayCasting(angle: angle)
angle += 0.001
}
private func drawRayCasting(angle: CGFloat) {
let colors: [UIColor] = [.red, .green, .blue, .orange, .white]
var start: CGPoint = .zero
var direction: CGVector = CGVector(angle: angle)
for i in 0...4 {
guard let result = rayCast(start: start, direction: direction) else { return }
let vector = CGVector(from: start, to: result.destination)
// draw
drawVector(point: start, vector: vector, color: colors[i])
// prepare for next iteration
start = result.destination
direction = vector.normalized().bounced(withNormal: result.normal.normalized()).normalized()
}
}
private func rayCast(start: CGPoint, direction: CGVector) -> (destination:CGPoint, normal: CGVector)? {
let endVector = CGVector(
dx: start.x + direction.normalized().dx * 4000,
dy: start.y + direction.normalized().dy * 4000
)
let endPoint = CGPoint(x: endVector.dx, y: endVector.dy)
var closestPoint: CGPoint?
var normal: CGVector?
physicsWorld.enumerateBodies(alongRayStart: start, end: endPoint) {
(physicsBody:SKPhysicsBody,
point:CGPoint,
normalVector:CGVector,
stop:UnsafeMutablePointer<ObjCBool>) in
guard start.distanceTo(point) > 1 else {
return
}
guard let newClosestPoint = closestPoint else {
closestPoint = point
normal = normalVector
return
}
guard start.distanceTo(point) < start.distanceTo(newClosestPoint) else {
return
}
normal = normalVector
}
guard let p = closestPoint, let n = normal else { return nil }
return (p, n)
}
private func drawVector(point: CGPoint, vector: CGVector, color: SKColor) {
let start = point
let destX = (start.x + vector.dx)
let destY = (start.y + vector.dy)
let to = CGPoint(x: destX, y: destY)
let path = CGMutablePath()
path.move(to: start)
path.addLine(to: to)
path.closeSubpath()
let line = SKShapeNode(path: path)
line.strokeColor = color
line.lineWidth = 6
addChild(line)
}
}
extension CGVector {
init(angle: CGFloat) {
self.init(dx: cos(angle), dy: sin(angle))
}
func normalized() -> CGVector {
let len = length()
return len>0 ? self / len : CGVector.zero
}
func length() -> CGFloat {
return sqrt(dx*dx + dy*dy)
}
static func / (vector: CGVector, scalar: CGFloat) -> CGVector {
return CGVector(dx: vector.dx / scalar, dy: vector.dy / scalar)
}
func bounced(withNormal normal: CGVector) -> CGVector {
let dotProduct = self.normalized() * normal.normalized()
let dx = self.dx - 2 * (dotProduct) * normal.dx
let dy = self.dy - 2 * (dotProduct) * normal.dy
return CGVector(dx: dx, dy: dy)
}
init(from:CGPoint, to:CGPoint) {
self = CGVector(dx: to.x - from.x, dy: to.y - from.y)
}
static func * (left: CGVector, right: CGVector) -> CGFloat {
return (left.dx * right.dx) + (left.dy * right.dy)
}
}
extension CGPoint {
func length() -> CGFloat {
return sqrt(x*x + y*y)
}
func distanceTo(_ point: CGPoint) -> CGFloat {
return (self - point).length()
}
static func - (left: CGPoint, right: CGPoint) -> CGPoint {
return CGPoint(x: left.x - right.x, y: left.y - right.y)
}
}
2. How does it work?
Lets have a look at what this code does. We'll start from the bottom.
3. CGPoint and CGVector extensions
These are just simple extensions (mainly taken from Ray Wenderlich's repository on GitHub) to simplify the geometrical operations we are going to perform.
4. drawVector(point:vector:color)
This is a simple method to draw a vector with a given color starting from a given point.
Nothing fancy here.
private func drawVector(point: CGPoint, vector: CGVector, color: SKColor) {
let start = point
let destX = (start.x + vector.dx)
let destY = (start.y + vector.dy)
let to = CGPoint(x: destX, y: destY)
let path = CGMutablePath()
path.move(to: start)
path.addLine(to: to)
path.closeSubpath()
let line = SKShapeNode(path: path)
line.strokeColor = color
line.lineWidth = 6
addChild(line)
}
5. rayCast(start:direction) -> (destination:CGPoint, normal: CGVector)?
This method perform a raycasting and returns the ALMOST closest point where the ray enter in collision with a physics body.
private func rayCast(start: CGPoint, direction: CGVector) -> (destination:CGPoint, normal: CGVector)? {
let endVector = CGVector(
dx: start.x + direction.normalized().dx * 4000,
dy: start.y + direction.normalized().dy * 4000
)
let endPoint = CGPoint(x: endVector.dx, y: endVector.dy)
var closestPoint: CGPoint?
var normal: CGVector?
physicsWorld.enumerateBodies(alongRayStart: start, end: endPoint) {
(physicsBody:SKPhysicsBody,
point:CGPoint,
normalVector:CGVector,
stop:UnsafeMutablePointer<ObjCBool>) in
guard start.distanceTo(point) > 1 else {
return
}
guard let newClosestPoint = closestPoint else {
closestPoint = point
normal = normalVector
return
}
guard start.distanceTo(point) < start.distanceTo(newClosestPoint) else {
return
}
normal = normalVector
}
guard let p = closestPoint, let n = normal else { return nil }
return (p, n)
}
What does it mean ALMOST the closets?
It means the the destination point must be at least 1 point distant from the start point
guard start.distanceTo(point) > 1 else {
return
}
Ok but why?
Because without this rule the ray gets stuck into a physics body and it is never able to get outside of it.
6. drawRayCasting(angle)
This method basically keeps the local variables up to date to properly generate 5 segments.
private func drawRayCasting(angle: CGFloat) {
let colors: [UIColor] = [.red, .green, .blue, .orange, .white]
var start: CGPoint = .zero
var direction: CGVector = CGVector(angle: angle)
for i in 0...4 {
guard let result = rayCast(start: start, direction: direction) else { return }
let vector = CGVector(from: start, to: result.destination)
// draw
drawVector(point: start, vector: vector, color: colors[i])
// prepare next direction
start = result.destination
direction = vector.normalized().bounced(withNormal: result.normal.normalized()).normalized()
}
}
The first segment has starting point equals to zero and a direction diving my the angle parameter.
Segments 2 to 5 use the final point and the "mirrored direction" of the previous segment.
update(_ currentTime: TimeInterval)
Here I am just calling drawRayCasting every frame passing the current angle value and the increasing angle by 0.001.
var angle: CGFloat = 0
override func update(_ currentTime: TimeInterval) {
removeAllChildren()
drawRayCasting(angle: angle)
angle += 0.001
}
6. didMove(to view: SKView)
Finally here I create a physics body around the scene in order to make the ray bounce over the borders.
override func didMove(to view: SKView) {
self.physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
}
7. Wrap up
I hope the explanation is clear.
Should you have any doubt let me know.
Update
There was a bug in the bounced function. It was preventing a proper calculation of the reflected ray.
It is now fixed.

Applying an impulse to bullets

I have player (SKSpriteNode) he can move and rotate, and I want to shoot five bullets from him, but with another angle. I use this code:
let sinus = sin(player.zRotation)
let cosinus = cos(player.zRotation)
bullet.physicsBody!.applyImpulse(CGVector(dx: -sinus * 100, dy: cosinus * 100))
But, i do not know how to correctly set the vector with angle. I try to make something like this:
Can anyone help me please!
According with this diagram:
you must change the zRotation of your bullets.
Remember that zRotation is expressed in radians so if you need:
extension CGFloat {
var radiansToDegrees: CGFloat {
return self * CGFloat(180.0 / M_PI)
}
var degreesToRadians: CGFloat {
return self * CGFloat(M_PI / 180.0)
}
}
To rotate your bullets you can also use (also this one is expressed in radians) :
let rotate = SKAction.rotateByAngle(angle, duration: 0.01)
You can calculate also the speed and the angle of your bullet impulses :
extension CGVector {
func speed() -> CGFloat {
return sqrt(dx*dx+dy*dy)
}
func angle() -> CGFloat {
return atan2(dy, dx)
}
}
If you need you can connect these element with the screen touches as explained in detail here
About your comments you can use this formula (already explained in my link):
let angle = atan2(location.y - cannon.position.y , location.x - cannon.position.x)
cannon.zRotation = angle - CGFloat(M_PI_2)

How to find the distance between two CG points?

When we do multitouch with two fingers in a UIScrollView, we get two CG points. I want to find the distance between them. Then when again we do the pinch(inside or outside), Then we will again get two points. Then after finding the distance again between these two points , I want to decide whether I pinched in or out. If i have pinched in, surely the new distance will be lesser and vice versa.
But don't know how to find an accurate measurement for the distance between 2 points for doing comparison ? Is anyone having idea about this ?
You can use the hypot() or hypotf() function to calculate the hypotenuse. Given two points p1 and p2:
CGFloat distance = hypotf(p1.x - p2.x, p1.y - p2.y);
And that's it.
Distance between p1 and p2:
CGFloat xDist = (p2.x - p1.x);
CGFloat yDist = (p2.y - p1.y);
CGFloat distance = sqrt(xDist * xDist + yDist * yDist);
Put in a function:
func distance(_ a: CGPoint, _ b: CGPoint) -> CGFloat {
let xDist = a.x - b.x
let yDist = a.y - b.y
return CGFloat(sqrt(xDist * xDist + yDist * yDist))
}
Background: Pythagorean theorem
If you only need to calculate if the distance between the points increases or decreases, you can omit the sqrt() which will make it a little faster.
For swift users
extension CGPoint {
func distance(to point: CGPoint) -> CGFloat {
return sqrt(pow(x - point.x, 2) + pow(y - point.y, 2))
}
}
With Swift 4, you may choose one of the 5 following Playground codes in order to get the distance between two CGPoint instances.
1. Using Darwin sqrt(_:) function
import CoreGraphics
func distance(from lhs: CGPoint, to rhs: CGPoint) -> CGFloat {
let xDistance = lhs.x - rhs.x
let yDistance = lhs.y - rhs.y
return sqrt(xDistance * xDistance + yDistance * yDistance)
}
let point1 = CGPoint(x: -10, y: -100)
let point2 = CGPoint(x: 30, y: 600)
distance(from: point1, to: point2) // 701.141925718324
2. Using CGFloat squareRoot() method
import CoreGraphics
func distance(from lhs: CGPoint, to rhs: CGPoint) -> CGFloat {
let xDistance = lhs.x - rhs.x
let yDistance = lhs.y - rhs.y
return (xDistance * xDistance + yDistance * yDistance).squareRoot()
}
let point1 = CGPoint(x: -10, y: -100)
let point2 = CGPoint(x: 30, y: 600)
distance(from: point1, to: point2) // 701.141925718324
3. Using CGFloat squareRoot() method and Core Graphics pow(_:_:) function
import CoreGraphics
func distance(from lhs: CGPoint, to rhs: CGPoint) -> CGFloat {
return (pow(lhs.x - rhs.x, 2) + pow(lhs.y - rhs.y, 2)).squareRoot()
}
let point1 = CGPoint(x: -10, y: -100)
let point2 = CGPoint(x: 30, y: 600)
distance(from: point1, to: point2) // 701.141925718324
4. Using Core Graphics hypot(_:_:) function
import CoreGraphics
func distance(from lhs: CGPoint, to rhs: CGPoint) -> CGFloat {
return hypot(lhs.x - rhs.x, lhs.y - rhs.y)
}
let point1 = CGPoint(x: -10, y: -100)
let point2 = CGPoint(x: 30, y: 600)
distance(from: point1, to: point2) // 701.141925718324
5. Using Core Graphics hypot(_:_:) function and CGFloat distance(to:) method
import CoreGraphics
func distance(from lhs: CGPoint, to rhs: CGPoint) -> CGFloat {
return hypot(lhs.x.distance(to: rhs.x), lhs.y.distance(to: rhs.y))
}
let point1 = CGPoint(x: -10, y: -100)
let point2 = CGPoint(x: 30, y: 600)
distance(from: point1, to: point2) // 701.141925718324
-(float)distanceFrom:(CGPoint)point1 to:(CGPoint)point2
{
CGFloat xDist = (point2.x - point1.x);
CGFloat yDist = (point2.y - point1.y);
return sqrt((xDist * xDist) + (yDist * yDist));
}
If you are using cocos2d
float distance = ccpDistance(point1, point2);
I wrote this, I use it a lot:
- (float) distanceBetween : (CGPoint) p1 and: (CGPoint) p2
{
return sqrt(pow(p2.x-p1.x,2)+pow(p2.y-p1.y,2));
}
Call like this:
float distanceMoved = [self distanceBetween touchStart and: touchEnd];
I normally use cocos2d, but I still use my own function for some things because when I was learning I wrote a bunch of my own functions for simple stuff rather than searching for the "official" higher order functions, and additionally I'm not a big fan of functions(vars, vars), I prefer [self functions vars and: vars]
#define rw_pointOffset(point1, point2) CGPointMake(point2.x - point1.x, point2.y - point1.y)
#define rw_pointDistance(point1, point2) sqrtf( powf(point2.x - point1.x, 2.0f) + powf(point2.y - point1.y, 2.0f))
And that´s how you use it:
CGPoint offset = rw_pointOffset(view1.center, view2.center);
float distance = rw_pointDistance(view1.center, view2.center);
If you want to find the absolute distance value between two points then you can use (for Cocos2d):
float distance = abs(ccpDistance(point1, point2));