Drawing a wedge in ARKit using SceneKit - ios

I am trying to draw a wedge of a circle using UIBezierPath, but when drawing it it shows a triangle and instead of drawing a curve through my points, it draws a straight line.
I am constructing the SCNNode Like so:
let path = UiBezierPath()
path.move(to: CGPoint.zero)
path.addArc(withCenter: CGPoint.zero, radius: 0.5, startAngle: 0, endAngle: .pi/2, clockwise: true)
path.close()
let shape = SCNShape(path: path, extrusionDepth: 0.1)
let mat = SCNMaterial()
mat.diffuse.contents = UIColor.orange
shape.materials = [mat]
let node = SCNNode(geometry: shape)
When I position this node in the world, and add it to the scene it draws a triangle. How can I make it so there is a curve instead of just a straight line?

Setting the flatness of the path to 0 fixed this issue:
path.flatness = 0

Related

Semi circle between 2 CGPoints

I want to draw a semi circle between 2 points on a circle. The main represents a clock and i want to draw another line to represent a progress from one hour to another so the points position may vary. First of all i know the X and Y of the 2 points i am interested in. This is how i try to add angles in UIBezierPath. My problem is that the new circle starts correctly but ends at a totally random location
let firstAngle = atan2(redPoint.y - circleCenter.y, redPoint.x - circleCenter.x)
let secondAngle = atan2(bluePoint.y - circleCenter.y, bluePoint.x - circleCenter.x) ```
let circlePath1 = UIBezierPath(arcCenter: circleCenter,
radius: circleRadius,
startAngle: firstAngle,
endAngle: secondAngle,
clockwise: true) ```
Wherever i set the redPoint, the circle starts at a correct location but the circle never ands at bluePoint.
I have tried your code and it works for me if points are actually on that circumference
let redPoint = CGPoint(x: 100.0, y: 200.0)
let bluePoint = CGPoint(x: 100.0, y: 0.0)
let circleCenter = CGPoint(x: 100.0, y: 100.0)
let circleRadius = CGFloat(100.0)
let firstAngle = atan2(redPoint.y - circleCenter.y, redPoint.x - circleCenter.x)
let secondAngle = atan2(bluePoint.y - circleCenter.y, bluePoint.x - circleCenter.x)
let path = UIBezierPath(arcCenter: circleCenter,
radius: circleRadius,
startAngle: firstAngle,
endAngle: secondAngle,
clockwise: true)
//Path in layer
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.lineWidth = 1.0
self.view.layer.addSublayer(shapeLayer)
or for example:
let redPoint = CGPoint(x: 200.0, y: 100.0)
let bluePoint = CGPoint(x: 100.0, y: 0.0)
let circleCenter = CGPoint(x: 100.0, y: 100.0)
let circleRadius = CGFloat(100.0)
You get:
But if you use coordinates that are not actually on your circumference, you get wrong results. My suggestion is to check your inputs and eventually if points are belonging to your desired circumference or not.
You're doing this backwards. Don't try to get the angle by starting with the location of the little red and blue filled circles. Use the angle to place the little red and blue filled circles.
In that example, my angles are -1 and 2.8 (radians). First I draw the arc (just as you did); then I superimpose the circles, which is trivial because I know where their centers are (the endpoints of the arc) by converting polar to cartesian coordinates.

In SceneKit is there a way not to fill UIBezierPath?

I am trying to draw circle in a sceneView but it fills the inside.
Also is there a way to increase segment number it looks like a polygon.
In case you wonder why I use UIBezierPath to draw circle, I want to change its end angle dynamically according to some parameters.
let r:CGFloat = 5
let center = CGPoint(x: 0, y: 0)
let path = UIBezierPath(arcCenter:center,
radius: r,
startAngle: CGFloat(0),
endAngle:CGFloat(CGFloat.pi * 2),
clockwise: true)
path.lineWidth = 1
UIColor.clear.setFill()
path.fill()
UIColor.white.setStroke()
path.stroke()
let shape = SCNShape(path: path, extrusionDepth: 0)
shape.firstMaterial?.diffuse.contents = UIColor.white
let shapeNode = SCNNode(geometry: shape)
shapeNode.position = SCNVector3(0, 0, 0)
rootNode.addChildNode(shapeNode)

UIView pauses during animation along UIBezierPath

I'm trying to animate a UIView(a square) to move along a UIBezierPath using a CAKeyframeAnimation. The square pauses at two points along the bezier path, both points being right before the path begins to arc.This is my code for the UIBezierPath and Animation:
func createTrack() -> UIBezierPath {
let path = UIBezierPath()
path.move(to: CGPoint(x: layerView.frame.size.width/2, y: 0.0))
path.addLine(to: CGPoint(x: layerView.frame.size.width - 100.0, y: 0.0))
path.addArc(withCenter: CGPoint(x: layerView.frame.size.width - 100.0,y: layerView.frame.height/2), radius: layerView.frame.size.height/2, startAngle: CGFloat(270).toRadians(), endAngle: CGFloat(90).toRadians(), clockwise: true)
path.addLine(to: CGPoint(x: 100.0, y: layerView.frame.size.height))
path.addArc(withCenter: CGPoint(x: 100.0,y: layerView.frame.height/2), radius: layerView.frame.size.height/2, startAngle: CGFloat(90).toRadians(), endAngle: CGFloat(270).toRadians(), clockwise: true)
path.close()
return path
}
#IBAction func onAnimatePath(_ sender: Any) {
let square = UIView()
square.frame = CGRect(x: 55, y: 300, width: 20, height: 20)
square.backgroundColor = UIColor.red
layerView.addSubview(square)
let animation = CAKeyframeAnimation(keyPath: "position")
animation.path = trackPath.cgPath
animation.rotationMode = kCAAnimationRotateAuto
animation.repeatCount = Float.infinity
animation.duration = 60
square.layer.add(animation, forKey: "animate position along path")
}
layerView is just a UIView. Any ideas on why this happens and how I can fix this?
The path you are using consists of 5 segments (two arcs and three lines (including the one when you close the path)) and the animation spends the same time on each of the segments. If this path if too narrow, these lines segments will have no length and the square will appear still for 12 seconds in each of them.
You probably want to use a "paced" calculation mode to achieve a constant velocity
animation.calculationMode = kCAAnimationPaced
This way, the red square will move at a constant pace – no matter how long each segment of the shape is.
This alone will give you the result you are after, but there's more you can do to simplify the path.
The addArc(...) method is rather smart and will add a line from the current point to the start of the arc itself, so the two explicit lines aren't needed.
Additionally, if you change the initial point you're moving the path to to have the same x component as the center of the second arc, then the path will close itself. Here, all you need are the two arcs.
That said, if the shape you're looking to create is a rounded rectangle like this, then you can use the UIBezierPath(roundedRect:cornerRadius:) convenience initializer:
let radius = min(layerView.bounds.width, layerView.bounds.height) / 2.0
let path = UIBezierPath(roundedRect: layerView.bounds, cornerRadius: radius)

circle from UIBezierPath drawn in wrong direction in SKShapeNode

Following UIBezierPath reference, I tried drawing a dashed path which should end up as dashed arc. However the drawing direction is wrong. clockwise was set to true but the top half of the circle was drawn as opposed to what was mentioned in the apple's page
let arcForCompleted =
UIBezierPath(arcCenter: origin, radius: radius, startAngle: 0, endAngle: CGFloat(M_PI), clockwise: true)
let pattern = getPattern(self.circumference, segments: involved)
let dashedPathForCompleted = CGPathCreateCopyByDashingPath(arcForCompleted.CGPath, nil, 0, pattern, pattern.count)
let dashedCircleForCompleted = SKShapeNode(path: dashedPathForCompleted!)
I am guessing this is because UIKit and SpriteKit has different coordinate system.
UIBezierPath is written with UIKit in mind thus it uses the UIKit coordinate system, (0,0) is in the upper left with positive y values running down. For the SKNode it has different coordinate system, (0,0) is in the center with positive y running up. You should keep that in mind when drawing arcs as it will affect the clockwise parameter. You can find a discussion of SKNode coordinate system here.
You can paste this code in a playground to see the difference as well
let bezierPath = UIBezierPath(arcCenter: CGPoint(x: 50.0,y: 50.0), radius: 50, startAngle: 0, endAngle: CGFloat(M_PI), clockwise: true)
class ArcView:UIView
{
override func drawRect(rect: CGRect) {
let arcForCompleted = bezierPath
let pattern:[CGFloat] = [10.0,10.0]
arcForCompleted.setLineDash(pattern, count: 2, phase: 0.0)
arcForCompleted.stroke()
}
}
let arcView = ArcView(frame: CGRect(x: 0.0, y: 0.0, width: 100.0, height: 100.0))
arcView.backgroundColor = UIColor.whiteColor()
let arcForCompleted = bezierPath
let shape = SKShapeNode()
shape.path = arcForCompleted.CGPath

SCNShape with bezier path

I am tying to draw a 3d line in Scenekit iOS, i am trying below code to draw line,
func path() -> UIBezierPath
{
var bPath = UIBezierPath()
bPath.moveToPoint(CGPointMake(0, 0))
bPath.addLineToPoint(CGPointMake(10, 10))
bPath.addLineToPoint(CGPointMake(5, 5))
bPath.closePath()
return bPath
}
and the below code is to load the line in SCNNode,
let sap = SCNShape()
sap.path = path()
let carbonNode = SCNNode(geometry: sap)
carbonNode.position = SCNVector3Make(0, 0, 15)
scene.rootNode.addChildNode(carbonNode)
But, i am getting blank screen.
try changing your geometry's materials. You might have a white object on a white background.
edit:
it also turns out that the vertices of your triangle are colinear. You thus end up with a degenerate triangle.
Your path has no surface area since you start at (0,0), draw a line to (10, 10) and then a second line to (5,5) which is already on the line between (0,0) and (10, 10). Then you close the path by drawing another line to (0,0).
Changing either of the three points creates a path with a surface area.
Also, the z-axis points out of the screen. This means that depending on where your camera is positioned, you may be placing the carbonNode behind the camera. If instead you meant to position it further into the scene you should likely use a negative z value.
Here's some code I wrote to address the problem.
// Material colors
let cyanMaterial = SCNMaterial()
cyanMaterial.diffuse.contents = UIColor.cyanColor()
let anOrangeMaterial = SCNMaterial()
anOrangeMaterial.diffuse.contents = UIColor.orangeColor()
let aPurpleMaterial = SCNMaterial()
aPurpleMaterial.diffuse.contents = UIColor.purpleColor()
// A bezier path
let bezierPath = UIBezierPath()
bezierPath.moveToPoint(CGPointMake(-0.25, 0))
bezierPath.addLineToPoint(CGPointMake(0, -0.25))
bezierPath.addLineToPoint(CGPointMake(0.25, 0))
bezierPath.addLineToPoint(CGPointMake(0, 0.25))
bezierPath.closePath()
// Add shape
let shape = SCNShape(path: bezierPath, extrusionDepth: 0.75)
shape.materials = [cyanMaterial, anOrangeMaterial, aPurpleMaterial]
let shapeNode = SCNNode(geometry: shape)
shapeNode.position = SCNVector3(x: 0.2, y: 0.75, z: 0.1);
self.rootNode.addChildNode(shapeNode)
shapeNode.rotation = SCNVector4(x: -1.0, y: -1.0, z: 0.0, w: 0.0)
Keep in mind that the numbers, the Scene Kit scale, are in meters. So set the numbers similarly to those that work for the other shapes you create.

Resources