How to create SKPhysicsBody with holes? - ios

I was wondering how I could create a more complex SKPhysicsyBody like this? I want to hittest againt the black parts.
I exported this shape with PaintCode to a CG Rect.
var barrierpath2Path = UIBezierPath()
barrierpath2Path.moveToPoint(CGPointMake(52, 26))
barrierpath2Path.addLineToPoint(CGPointMake(52, 26))
barrierpath2Path.addCurveToPoint(CGPointMake(26, 52), controlPoint1: CGPointMake(52, 40.36), controlPoint2: CGPointMake(40.36, 52))
barrierpath2Path.addLineToPoint(CGPointMake(26, 52))
barrierpath2Path.addCurveToPoint(CGPointMake(0, 26), controlPoint1: CGPointMake(11.64, 52), controlPoint2: CGPointMake(0, 40.36))
barrierpath2Path.addLineToPoint(CGPointMake(0, 26))
barrierpath2Path.addCurveToPoint(CGPointMake(26, 0), controlPoint1: CGPointMake(0, 11.64), controlPoint2: CGPointMake(11.64, 0))
barrierpath2Path.addLineToPoint(CGPointMake(26, 0))
barrierpath2Path.addCurveToPoint(CGPointMake(52, 26), controlPoint1: CGPointMake(40.36, 0), controlPoint2: CGPointMake(52, 11.64))
barrierpath2Path.closePath()
barrierpath2Path.moveToPoint(CGPointMake(46.8, 26))
barrierpath2Path.addLineToPoint(CGPointMake(46.8, 26))
barrierpath2Path.addCurveToPoint(CGPointMake(39, 18.2), controlPoint1: CGPointMake(46.8, 21.69), controlPoint2: CGPointMake(43.31, 18.2))
barrierpath2Path.addLineToPoint(CGPointMake(39, 18.2))
barrierpath2Path.addCurveToPoint(CGPointMake(31.2, 26), controlPoint1: CGPointMake(34.69, 18.2), controlPoint2: CGPointMake(31.2, 21.69))
barrierpath2Path.addLineToPoint(CGPointMake(31.2, 26))
barrierpath2Path.addCurveToPoint(CGPointMake(39, 33.8), controlPoint1: CGPointMake(31.2, 30.31), controlPoint2: CGPointMake(34.69, 33.8))
barrierpath2Path.addLineToPoint(CGPointMake(39, 33.8))
barrierpath2Path.addCurveToPoint(CGPointMake(46.8, 26), controlPoint1: CGPointMake(43.31, 33.8), controlPoint2: CGPointMake(46.8, 30.31))
barrierpath2Path.closePath()
barrierpath2Path.moveToPoint(CGPointMake(27.3, 14.74))
barrierpath2Path.addLineToPoint(CGPointMake(27.3, 14.74))
barrierpath2Path.addCurveToPoint(CGPointMake(19.5, 6.94), controlPoint1: CGPointMake(27.3, 10.43), controlPoint2: CGPointMake(23.81, 6.94))
barrierpath2Path.addLineToPoint(CGPointMake(19.5, 6.94))
barrierpath2Path.addCurveToPoint(CGPointMake(11.7, 14.74), controlPoint1: CGPointMake(15.19, 6.94), controlPoint2: CGPointMake(11.7, 10.43))
barrierpath2Path.addLineToPoint(CGPointMake(11.7, 14.74))
barrierpath2Path.addCurveToPoint(CGPointMake(19.5, 22.54), controlPoint1: CGPointMake(11.7, 19.05), controlPoint2: CGPointMake(15.19, 22.54))
barrierpath2Path.addLineToPoint(CGPointMake(19.5, 22.54))
barrierpath2Path.addCurveToPoint(CGPointMake(27.3, 14.74), controlPoint1: CGPointMake(23.81, 22.54), controlPoint2: CGPointMake(27.3, 19.05))
barrierpath2Path.closePath()
barrierpath2Path.moveToPoint(CGPointMake(27.3, 37.26))
barrierpath2Path.addLineToPoint(CGPointMake(27.3, 37.26))
barrierpath2Path.addCurveToPoint(CGPointMake(19.5, 29.46), controlPoint1: CGPointMake(27.3, 32.95), controlPoint2: CGPointMake(23.81, 29.46))
barrierpath2Path.addLineToPoint(CGPointMake(19.5, 29.46))
barrierpath2Path.addCurveToPoint(CGPointMake(11.7, 37.26), controlPoint1: CGPointMake(15.19, 29.46), controlPoint2: CGPointMake(11.7, 32.95))
barrierpath2Path.addLineToPoint(CGPointMake(11.7, 37.26))
barrierpath2Path.addCurveToPoint(CGPointMake(19.5, 45.06), controlPoint1: CGPointMake(11.7, 41.57), controlPoint2: CGPointMake(15.19, 45.06))
barrierpath2Path.addLineToPoint(CGPointMake(19.5, 45.06))
barrierpath2Path.addCurveToPoint(CGPointMake(27.3, 37.26), controlPoint1: CGPointMake(23.81, 45.06), controlPoint2: CGPointMake(27.3, 41.57))
barrierpath2Path.closePath()
barrierpath2Path.miterLimit = 4;
monster.physicsBody = SKPhysicsBody(polygonFromPath: barrierpath2Path.CGPath)
But the simulator shows just one hole (and the png behind)? What's wrong here?

You most likely want to make the holes child nodes with their own physics bodies. This will allow everything to have their own respective physics bodies and move in accordance to each other without any problems
To further elaborate on this answer, you might also be able to use the SKPhysicsBody convenience initializer to create a physics body off of the alpha values in a texture. This will incur a performance hit, but should do what you want it to.
l.physicsBody = SKPhysicsBody(texture: l.texture, size: l.size)

Related

'CGContextAddLineToPoint' is unavailable : Swift

What is replace of the CGContextAddLineToPoint this is what I'm trying to do
for i in aerr {
//0.5f offset lines up line with pixel boundary
CGContextMoveToPoint(context, self.bounds.origin.x, self.font!.lineHeight * CGFloat(x) + baselineOffset)
CGContextAddLineToPoint(context, CGFloat(self.bounds.size.width), self.font!.lineHeight * CGFloat(x) + baselineOffset)
}
and I'm getting the following error. I know they clearly mention using move(to:) but how to use that?
'CGContextMoveToPoint' is unavailable: Use move(to:) instead
In Swift, you can simply use directly from your context because it methods is part of CGContext.
So CGContextMoveToPoint(context, x, y) will be context.move(to:CGPoint(x, y))
As same as, CGContextAddLineToPoint(context, x, y) will be context.addLine(to: CGPoint(x, y))
So your code will be
guard let context = UIGraphicsGetCurrentContext() else { return }
for i in aerr {
//0.5f offset lines up line with pixel boundary
context.move(to: CGPoint(x: self.bounds.origin.x, y: self.font!.lineHeight * CGFloat(x) + baselineOffset))
context.addLine(to: CGPoint(x: CGFloat(self.bounds.size.width), y: self.font!.lineHeight * CGFloat(x) + baselineOffset))
}
you can do it like this
context.move(to: .init(x: self.bounds.origin.x, y: self.font!.lineHeight * CGFloat(x) + baselineOffset))

How to find intersection point (CGPoint) between QuadCurve and Line UIBezierPaths in swift?

I've a QuadCurve and Line drawn using UIBezierPath in my custom view class. How can I get their intersection point as CGPoint?
For QuadCurve:
let path = UIBezierPath()
path.lineWidth = 3.0
path.move(to: CGPoint(x: 0, y: self.frame.size.height))
path.addQuadCurve(to: CGPoint(x: self.frame.size.width, y: 0), controlPoint: CGPoint(x: self.frame.size.width-self.frame.size.width/3, y: self.frame.size.height))
For Line:
let path2 = UIBezierPath()
path2.lineWidth = 3.0
path2.move(to: CGPoint(x: 250, y: 0))
path2.addLine(to: CGPoint(x: 250, y: self.frame.size.height))
If your line is always vertical, calculations are quite simple: x-coordinate is known, so you task is to find y-coordinate. Quadratic Bezier curve has parametric representation:
P(t) = P0*(1-t)^2 + 2*P1*(1-t)*t + P2*t^2 =
t^2 * (P0 - 2*P1 + P2) + t * (-2*P0 + 2*P1) + P0
where P0, P1, P2 are starting, control and ending points.
So you have to solve quadratic equation
t^2 * (P0.X - 2*P1.X + P2.X) + t * (-2*P0.X + 2*P1.X) + (P0.X - LineX) = 0
for unknown t, get root in range 0..1, and apply t value to the similar expression for Y-coordinate
Y = P0.Y*(1-t)^2 + 2*P1.Y*(1-t)*t + P2.Y*t^2
For arbitrary line make equation system for line parametric representation and curve, and solve that system

Dynamic UIBezierPath for Animations

I want to create an animation where an object animates its position along a path. The trouble I am having is making this path dynamic meaning that it works on all screen sizes.
The spaceship in the gif above follows a "U" shape path initially.
I use this "U" shape path and it works, here is the code:
func animateAlongPath() {
// Create and add image to view
let myImage = UIImage(named: "myImage")!
let myImageView = UIImageView(image: myImage)
myImageView.frame = CGRect(x: 100, y: 100, width: myImage.size.width, height: myImage.size.height)
holderView.addSubview(myImageView)
// Create "U" shape path for animation (path code created in PaintCode)
let path = UIBezierPath()
path.moveToPoint(CGPointMake(59.5, 81.5))
path.addCurveToPoint(CGPointMake(66.5, 91.5), controlPoint1: CGPointMake(63.64, 84.13), controlPoint2: CGPointMake(62.62, 85.9))
path.addCurveToPoint(CGPointMake(81.5, 110.5), controlPoint1: CGPointMake(72.31, 99.88), controlPoint2: CGPointMake(75.91, 103.08))
path.addCurveToPoint(CGPointMake(109.5, 142.5), controlPoint1: CGPointMake(88.44, 119.71), controlPoint2: CGPointMake(102.39, 135.23))
path.addCurveToPoint(CGPointMake(145.5, 156.5), controlPoint1: CGPointMake(122.47, 155.76), controlPoint2: CGPointMake(129.15, 157.54))
path.addCurveToPoint(CGPointMake(237.5, 81.5), controlPoint1: CGPointMake(185.04, 153.99), controlPoint2: CGPointMake(237.5, 81.5))
let pathAnimation = CAKeyframeAnimation(keyPath: "position")
pathAnimation.path = path.CGPath // Use "U" shape path for animation
pathAnimation.calculationMode = kCAAnimationPaced
pathAnimation.timingFunctions = [CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)]
pathAnimation.duration = 1
pathAnimation.removedOnCompletion = false
pathAnimation.fillMode = kCAFillModeForwards
myImageView.layer.addAnimation(pathAnimation, forKey: nil)
myImageView.layer.position = path.currentPoint
}
The problem is that the UIBezierPath is not dynamic. It works great on the iPhone 6 device screen size, but does not work great on any other device size since it is static. I tried using PaintCode to create dynamic paths with variables, but just can't get it to work.
Any ideas?
PaintCode can generate code for resizable UIBezierPaths using Frames.
Put the Bezier inside a Frame object and then set desired autoresizing springs (all flexible). The resulting code will be parametrized with CGRect/NSRect and will use relative coefficients to calculate bezier points.
There’s also a video tutorial about Dynamic Shapes if you want to get more into depth.

Inserting PaintCode code into UIImageView in Swift

I am experimenting with PaintCode. As an example, I've imported an abstract Illustrator drawing file, which has produced the below bezier Swift code.
On my storyboard I have an UIImageView named exampleImageView that I want to display the PaintCode code and have the ability to scale the resulting image like a vector graphic at different sizes, e.g. 1x, 2.5x 5x etc (i.e. smooth non pixellated lines and curves).
Questions:
1 - How do I display the below code as an image in an UIImageView named exampleImageView in Xcode?
2 - How do I display the below code in an UIView named exampleView in Xcode?
3 - Is it possible to scale up or down the below code (i.e. like a vector graphic), and if so, how in Xcode?
Expected image:
PaintCode code:
//// General Declarations
let context = UIGraphicsGetCurrentContext()
//// Color Declarations
let fillColor = UIColor(red: 0.087, green: 0.086, blue: 0.083, alpha: 1.000)
//// Group 2
CGContextSaveGState(context)
CGContextSetAlpha(context, 0.75)
CGContextBeginTransparencyLayer(context, nil)
//// Bezier Drawing
let bezierPath = UIBezierPath()
bezierPath.moveToPoint(CGPointMake(26.08, 23.65))
bezierPath.addCurveToPoint(CGPointMake(61.71, 12.5), controlPoint1: CGPointMake(38.43, 23.65), controlPoint2: CGPointMake(54.42, 18.61))
bezierPath.addCurveToPoint(CGPointMake(52.65, 1.35), controlPoint1: CGPointMake(69, 6.38), controlPoint2: CGPointMake(64.99, 1.35))
bezierPath.addCurveToPoint(CGPointMake(16.91, 12.5), controlPoint1: CGPointMake(40.2, 1.35), controlPoint2: CGPointMake(24.2, 6.38))
bezierPath.addCurveToPoint(CGPointMake(26.08, 23.65), controlPoint1: CGPointMake(9.63, 18.61), controlPoint2: CGPointMake(13.63, 23.65))
bezierPath.closePath()
bezierPath.moveToPoint(CGPointMake(48.14, 25))
bezierPath.addLineToPoint(CGPointMake(0.79, 25))
bezierPath.addCurveToPoint(CGPointMake(0.24, 24.35), controlPoint1: CGPointMake(-0.02, 25), controlPoint2: CGPointMake(-0.21, 24.73))
bezierPath.addCurveToPoint(CGPointMake(2.41, 23.65), controlPoint1: CGPointMake(0.76, 23.92), controlPoint2: CGPointMake(1.59, 23.65))
bezierPath.moveToPoint(CGPointMake(14.36, 12.5))
bezierPath.addCurveToPoint(CGPointMake(54.26, 0), controlPoint1: CGPointMake(22.62, 5.57), controlPoint2: CGPointMake(40.59, 0))
bezierPath.addCurveToPoint(CGPointMake(64.36, 12.5), controlPoint1: CGPointMake(67.93, 0), controlPoint2: CGPointMake(72.62, 5.57))
bezierPath.addCurveToPoint(CGPointMake(37.3, 23.65), controlPoint1: CGPointMake(58.56, 17.37), controlPoint2: CGPointMake(47.81, 21.59))
bezierPath.addLineToPoint(CGPointMake(49.75, 23.65))
fillColor.setFill()
bezierPath.fill()
CGContextEndTransparencyLayer(context)
CGContextRestoreGState(context)
1 - How do I display the below code as an image in an UIImageView named exampleImageView?
Image can be created in PaintCode, but I don't think it is the right fit here. For starters create a drawing method in PaintCode.
2 - Is it possible to scale up or down the below code (i.e. like a vector graphic), and if so, how?
In PaintCode you can make the thing resizable by using frames.
Look at the Dynamic Shapes tutorial that addresses both.

How to achieve my drawing using a bezier path?

I would like to get something like this:
I guess one way to do it would be to draw my rectangle with the two rounded angle in a subpath. Then add another subpass to draw the arrow.
My question is: is there a simpler way to draw the arrow than drawing 4 lines and some curves? I tried doing it by using 2 lines and bezierPath.lineJoinStyle = kCGLineJoinRound but when I fill it, I get a triangle.
This is the code I get from PaintCode when I draw the path.
//// Rectangle Drawing
var rectanglePath = UIBezierPath()
rectanglePath.moveToPoint(CGPointMake(26.71, 14))
rectanglePath.addLineToPoint(CGPointMake(85.29, 14))
rectanglePath.addCurveToPoint(CGPointMake(90.18, 14.39), controlPoint1: CGPointMake(87.8, 14), controlPoint2: CGPointMake(89.05, 14))
rectanglePath.addLineToPoint(CGPointMake(90.4, 14.45))
rectanglePath.addCurveToPoint(CGPointMake(93.57, 17.79), controlPoint1: CGPointMake(91.87, 15.01), controlPoint2: CGPointMake(93.04, 16.24))
rectanglePath.addCurveToPoint(CGPointMake(94, 23.17), controlPoint1: CGPointMake(94, 19.21), controlPoint2: CGPointMake(94, 20.53))
rectanglePath.addLineToPoint(CGPointMake(94, 54))
rectanglePath.addLineToPoint(CGPointMake(18, 54))
rectanglePath.addLineToPoint(CGPointMake(18, 23.17))
rectanglePath.addCurveToPoint(CGPointMake(18.37, 18.02), controlPoint1: CGPointMake(18, 20.53), controlPoint2: CGPointMake(18, 19.21))
rectanglePath.addLineToPoint(CGPointMake(18.43, 17.79))
rectanglePath.addCurveToPoint(CGPointMake(21.6, 14.45), controlPoint1: CGPointMake(18.96, 16.24), controlPoint2: CGPointMake(20.13, 15.01))
rectanglePath.addCurveToPoint(CGPointMake(26.71, 14), controlPoint1: CGPointMake(22.95, 14), controlPoint2: CGPointMake(24.2, 14))
rectanglePath.closePath()
UIColor.grayColor().setFill()
rectanglePath.fill()
//// Bezier Drawing
var bezierPath = UIBezierPath()
bezierPath.moveToPoint(CGPointMake(31.5, 37.5))
bezierPath.addLineToPoint(CGPointMake(56.5, 30.5))
bezierPath.addLineToPoint(CGPointMake(80.5, 37.5))
bezierPath.addLineToPoint(CGPointMake(80.5, 37.5))
bezierPath.addLineToPoint(CGPointMake(56.5, 30.5))
bezierPath.addLineToPoint(CGPointMake(31.5, 37.5))
bezierPath.addLineToPoint(CGPointMake(31.5, 37.5))
bezierPath.closePath()
bezierPath.lineJoinStyle = kCGLineJoinRound;
UIColor.grayColor().setFill()
bezierPath.fill()
UIColor.whiteColor().setStroke()
bezierPath.lineWidth = 5
bezierPath.stroke()
UPDATE:
The arrow is actually some "void" drawing. It's the shape of an arrow but there is nothing inside (we can see through it)
Here's some Core Graphics code that makes a shape like what you're looking for. You would have to translate it into the equivalent BezierPath commands, but that shouldn't be too difficult. You'll of course also have to tweak the coordinates and the colors to your preferences for the size and color. As you can see, it consists of two line shapes with the CGContextSetLineCap command used to round out the ends of each the two line shapes:
CGContextRef ctx = UIGraphicsGetCurrentContext(); // iOS
/* Line Shape 1 */
CGMutablePathRef pathRef = CGPathCreateMutable();
CGPathMoveToPoint(pathRef, NULL, 137, 192);
CGPathAddLineToPoint(pathRef, NULL, 300, 145);
CGContextSetLineWidth(ctx, 30);
CGContextSetLineCap(ctx, kCGLineCapRound);
CGContextSetRGBStrokeColor(ctx, 0.129, 0.992, 1, 1);
CGContextAddPath(ctx, pathRef);
CGContextStrokePath(ctx);
CGPathRelease(pathRef);
/* Line Shape 2 */
CGMutablePathRef pathRef2 = CGPathCreateMutable();
CGPathMoveToPoint(pathRef2, NULL, 463, 192);
CGPathAddLineToPoint(pathRef2, NULL, 300, 145);
CGContextSetLineWidth(ctx, 30);
CGContextSetLineCap(ctx, kCGLineCapRound);
CGContextSetRGBStrokeColor(ctx, 0.129, 0.992, 1, 1);
CGContextAddPath(ctx, pathRef2);
CGContextStrokePath(ctx);
CGPathRelease(pathRef2);

Resources