How to move circle with lefteyetransform ARKit2 - swift

I'm trying to develop a game that moves the circle according the left eye
let screenSize = UIScreen.main.bounds
let screenWidth = screenSize.width
let screenHeight = screenSize.height
let camera = self.sceneView.session.currentFrame?.camera
let pippo = simd_mul((anchor.transform),faceAnchor.leftEyeTransform)
var j: SCNVector3 = SCNVector3(pippo.columns.3.x ,pippo.columns.3.y , -pippo.columns.3.z);
self.circlePath = UIBezierPath(arcCenter: CGPoint(x: CGFloat( j.x * Float(screenHeight/2) ),y:CGFloat( j.y * Float(screenWidth/2) )), radius: CGFloat(20), startAngle: CGFloat(0), endAngle:CGFloat(Double.pi * 2), clockwise: true)
self.shapeLayer = CAShapeLayer()
self.shapeLayer.path = self.circlePath.cgPath
self.shapeLayer.fillColor = UIColor.clear.cgColor
self.shapeLayer.strokeColor = UIColor.red.cgColor
self.shapeLayer.lineWidth = 3.0
self.view.layer.addSublayer(self.shapeLayer)
I move a bit the circle, but it is centered on the leftmost part of the screen (instead of being in the middle) and the movement doesn't cover the whole screen, but just a part of it, how can I solve it?
thanks in advance.

Related

How can I draw a circle in SceneKit using its radius and origin?

What I am trying to do that draw a circle node using its radius and origin points.
Is there any way to draw a circle node in SceneKit?
How can I draw that?
First approach
Use SceneKit SCNCylinder's procedural mesh to create a circle.
func circle(origin: SCNVector3, radius: CGFloat) -> SCNNode {
let cylinderNode = SCNNode()
cylinderNode.geometry = SCNCylinder(radius: radius, height: 0.001)
cylinderNode.geometry?.firstMaterial?.diffuse.contents = UIColor.red
cylinderNode.geometry?.firstMaterial?.isDoubleSided = true
cylinderNode.position = origin
cylinderNode.eulerAngles.x = .pi/2
return cylinderNode
}
sceneView.scene?.rootNode.addChildNode(circle(
origin: SCNVector3(), radius: 0.5)
)
Second approach
This approach is impractical for your particular case, because you must specify the radius in UIKit units, not in meters like in SceneKit. However, I decided to publish it as option B.
// Path
let circlePath = UIBezierPath(arcCenter: CGPoint(x: 0, y: 0),
radius: 20,
startAngle: 0.0,
endAngle: .pi * 2,
clockwise: true)
circlePath.flatness = 0.1
// Shape
let material = SCNMaterial()
material.diffuse.contents = UIColor.systemRed
material.isDoubleSided = true
let circleShape = SCNShape(path: circlePath, extrusionDepth: 0.001)
circleShape.materials = [material]
// Node
let circleNode = SCNNode()
circleNode.geometry = circleShape
sceneView.scene?.rootNode.addChildNode(circleNode)
You could probably do something like this:
let circle = SCNPlane(width: 1.0, height: 1.0)
circle.cornerRadius = circle.width/2
hope, this is, what you are looking for.
another approch might be the SCNCylinder with a very small height or even SCNTorus with a very small pipe radius.

Set position of outerBelowCircle CAShapeLayer in the center of View

I make a circle by using CAShapeLayer, and I want to set the center of it, in the center of the view. But the circle will be created exactly below the center line (see the screenshot), could anyone help me on this?
let circle = CAShapeLayer()
view.layer.addSublayer(circle)
circle.position = view.center
let circularPath = UIBezierPath(arcCenter: .zero, radius: 100, startAngle: 0, endAngle: 2 * CGFloat.pi, clockwise: true)
circle.path = circularPath.cgPath
I also change the way that for setting it in the center, but It doesn't work
circle.position = CGPoint(x: view.layer.bounds.midX, y: view.layer.bounds.midY)
Consider the following method according to your screenshot this method will resolve your issue.
func showCircle() {
let view = UIView()
let circle = CAShapeLayer()
view.layer.addSublayer(circle)
let positionX = view.center.x
let positionY = view.center.y - 100 // your circle radius
circle.position = CGPoint(x: positionX, y: positionY)
let circularPath = UIBezierPath(arcCenter: .zero, radius: 100, startAngle: 0, endAngle: 2 * CGFloat.pi, clockwise: true)
circle.path = circularPath.cgPath
}
I haven't tested the code. Please verify it by yourself.

How to call UIBezier outline in Swift Playground?

EDIT: Sorry, I wasn't clear originally. I want to get the "outline" path of a line or shape. I'm specifically trying to understand how to use:
context.replacePathWithStrokedPath()
and / or:
CGPathRef CGPathCreateCopyByStrokingPath(CGPathRef path, const CGAffineTransform *transform, CGFloat lineWidth, CGLineCap lineCap, CGLineJoin lineJoin, CGFloat miterLimit);
https://developer.apple.com/documentation/coregraphics/1411128-cgpathcreatecopybystrokingpath?language=objc
I'm not looking for workarounds, thanks.
=====
I'm really trying to wrap my head around drawing a line with an outline around it. i'm using UIBezier, but running into brick walls. So far, I've got this:
import UIKit
import PlaygroundSupport
let screenWidth = 375.0 // points
let screenHeight = 467.0 // points
let centerX = screenWidth / 2.0
let centerY = screenHeight / 2.0
let screenCenterCoordinate = CGPoint(x: centerX, y: centerY)
class LineDrawingView: UIView {
override func draw(_ rect: CGRect) {
let path = UIBezierPath()
path.lineWidth = 5
path.lineCapStyle = .round
//Move to Drawing Point
path.move(to: CGPoint(x:20, y:120))
path.addLine(to: CGPoint(x:200, y:120))
path.stroke()
let dot = UIBezierPath()
dot.lineWidth = 1
dot.lineCapStyle = .round
dot.move(to: CGPoint(x:200, y:120))
dot.addArc(withCenter: CGPoint(x:200, y:120), radius: 5, startAngle: CGFloat(0.0), endAngle: CGFloat(8.0), clockwise: true)
UIColor.orange.setStroke()
UIColor.orange.setFill()
path.stroke()
dot.fill()
let myStrokedPath = UIBezierPath.copy(path)
myStrokedPath().stroke()
}
}
let tView = LineDrawingView(frame: CGRect(x: 0,y: 0, width: screenWidth, height: screenHeight))
tView.backgroundColor = UIColor.white
PlaygroundPage.current.liveView = tView
So, where am I going wrong in this? I cannot seem to figure out where to use CGPathCreateCopyByStrokingPath...or how...
EDIT 2:
Ok, now I've got this. Closer, but how do I fill the path again?
let c = UIGraphicsGetCurrentContext()!
c.setLineWidth(15.0)
let clipPath = UIBezierPath(arcCenter: CGPoint(x:centerX,y:centerY), radius: 90.0, startAngle: -0.5 * .pi, endAngle: 1.0 * .pi, clockwise: true).cgPath
c.addPath(clipPath)
c.saveGState()
c.replacePathWithStrokedPath()
c.setLineWidth(0.2)
c.setStrokeColor(UIColor.black.cgColor)
c.strokePath()
The class was modified slightly to produce this graphic:
The path was not copied in the modified code. Instead the existing path was used to draw, then modified and reused. The dot did not have a stroke so that was added. Since only closed paths can be filled, I drew a thinner path on top of a thicker path by changing the line width.
This is the modified code:
class LineDrawingView: UIView {
override func draw(_ rect: CGRect) {
let path = UIBezierPath()
path.lineWidth = 7
path.lineCapStyle = .round
//Move to Drawing Point
path.move(to: CGPoint(x:20, y:120))
path.addLine(to: CGPoint(x:200, y:120))
path.stroke()
let dot = UIBezierPath()
dot.lineWidth = 1
dot.lineCapStyle = .round
dot.move(to: CGPoint(x:200, y:120))
dot.addArc(withCenter: CGPoint(x:200, y:120), radius: 5, startAngle: CGFloat(0.0), endAngle: CGFloat(8.0), clockwise: true)
dot.stroke()
UIColor.orange.setStroke()
UIColor.orange.setFill()
path.lineWidth = 5
path.stroke()
dot.fill()
}
}
So, I've found the (an) answer. I used CAShapeLayer:
let c = UIGraphicsGetCurrentContext()!
c.setLineCap(.round)
c.setLineWidth(15.0)
c.addArc(center: CGPoint(x:centerX,y:centerY), radius: 90.0, startAngle: -0.5 * .pi, endAngle: (-0.5 * .pi) + (3 / 2 * .pi ), clockwise: false)
c.replacePathWithStrokedPath()
let shape = CAShapeLayer()
shape.path = c.path
shape.fillColor = UIColor.yellow.cgColor
shape.strokeColor = UIColor.darkGray.cgColor
shape.lineWidth = 1
myView.layer.addSublayer(shape)
It works well enough, but not on overlapping layers. I need to learn how to connect contours or something.

How to remove arc border in shadowed UIView with rounded / arc path?

I have ticket View like this
.
My method is I have 2 view, 1 is the ticket itself, and other is for shadow. I have to do this because if I mask the view, it got clipped and the shadow will not appear in the ticket view.
here is the code for create the ticket view:
let shapeLayer = CAShapeLayer()
shapeLayer.frame = someView.bounds
shapeLayer.path = UIBezierPath(roundedRect: someView.bounds,
byRoundingCorners: [UIRectCorner.bottomLeft,UIRectCorner.bottomRight] ,
cornerRadii: CGSize(width: 5.0, height: 5.0)).cgPath
let rect = CGRect(x:0, y:0, width:200, height:100)
let cornerRadius:CGFloat = 5
let subPathSideSize:CGFloat = 25
let path = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius)
let leftSubPath = UIBezierPath(arcCenter: CGPoint(x: rect.width / 2, y: 0),
radius: subPathSideSize / 2, startAngle: .pi, endAngle: .pi * 0, clockwise: false)
leftSubPath.close()
let rightSubPath = UIBezierPath(arcCenter: CGPoint(x: rect.width / 2, y: rect.height),
radius: subPathSideSize / 2, startAngle: .pi, endAngle: .pi * 0, clockwise: true)
rightSubPath.close()
path.append(leftSubPath)
path.append(rightSubPath.reversing())
let mask = CAShapeLayer()
mask.frame = shapeLayer.bounds
mask.path = path.cgPath
someView.layer.mask = mask
Notes: SomeView is the TicketView.
And here is the code for adding shadow:
let shadowMask = CAShapeLayer()
shadowMask.frame = shadowView.bounds
shadowMask.path = path.cgPath
shadowMask.shadowOpacity = 0.2
shadowMask.shadowRadius = 4
shadowMask.masksToBounds = false
shadowMask.shadowOffset = CGSize(width: 0, height: 2)
shadowView.backgroundColor = UIColor.clear
shadowView.layer.addSublayer(shadowMask)
The shadow makes arc/rounded corner have border like this one (marked with circle red).
Here is my Playground gist
Do you know how to remove the border in the rounded corner and arc path?
Thank you.
u need to add clipToBounds in this block that you have written.
let mask = CAShapeLayer()
mask.frame = shapeLayer.bounds
mask.path = path.cgPath
someView.clipsToBounds = true
someView.layer.mask = mask
So I think there is different calculation for corner in mask and path. So I used fillColor of the shadowLayer to match the color of the CouponView. And now, the borders are gone.
shadowLayer.fillColor = someView.backgroundColor.cgColor

Drawing a partial circle

I'm writing a program that will take a number between 0 and 1, and then spits out a circle (or arc I guess) that is completed by that much.
So for example, if 0.5 was inputted, the program would output a semicircle
if 0.1, the program would output a tiny little arc that would ultimately be 10% of the whole circle.
I can get this to work by making the angle starting point 0, and the angle ending point 2*M_PI*decimalInput
However, I need to have the starting point at the top of the circle, so the starting point is 3*M_PI_2 and the ending point would be 7*M_PI_2
I'm just having trouble drawing a circle partially complete with these new starting/ending points. And I'll admit, my math is not the best so any advice/input is appreciated
Here is what I have so far
var decimalInput = 0.75 //this number can be any number between 0 and 1
let start = CGFloat(3*M_PI_2)
let end = CGFloat(7*M_PI_2*decimalInput)
let circlePath = UIBezierPath(arcCenter: circleCenter, radius: circleRadius, startAngle: start, endAngle: end, clockwise: true)
circlePath.stroke()
I just cannot seem to get it right despite what I try. I reckon the end angle is culprit, unless I'm going about this the wrong way
The arc length is 2 * M_PI * decimalInput. You need to add the arc length to the starting angle, like this:
let circleCenter = CGPointMake(100, 100)
let circleRadius = CGFloat(80)
var decimalInput = 0.75
let start = CGFloat(3 * M_PI_2)
let end = start + CGFloat(2 * M_PI * decimalInput)
let circlePath = UIBezierPath(arcCenter: circleCenter, radius: circleRadius, startAngle: start, endAngle: end, clockwise: true)
XCPCaptureValue("path", circlePath)
Result:
Note that the path will be flipped vertically when used to draw in a UIView.
You can use this extension to draw a partial circle
extension UIBezierPath {
func addCircle(center: CGPoint, radius: CGFloat, startAngle: Double, circlePercentage: Double) {
let start = deg2rad(startAngle)
let end = start + CGFloat(2 * Double.pi * circlePercentage)
addArc(withCenter: center,
radius: radius,
startAngle: start,
endAngle: end,
clockwise: true)
}
private func deg2rad(_ number: Double) -> CGFloat {
return CGFloat(number * Double.pi / 180)
}
}
Example usage (you can copy paste it in a playground to see the result)
let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
view.backgroundColor = UIColor.green
let layer = CAShapeLayer()
layer.strokeColor = UIColor.red.cgColor
layer.fillColor = UIColor.clear.cgColor
layer.lineWidth = 8
let path = UIBezierPath()
path.addCircle(center: CGPoint(x: 50, y: 50), radius: 50, startAngle: 270, circlePercentage: 0.87)
layer.path = path.cgPath
view.layer.addSublayer(layer)
view.setNeedsLayout()