ARKit Drawing 3D paper plane with rounded corner - swift

I'm trying to draw 3D paper plane with rounded corner in ARKit, but I can't do that.
I did that with bazier path:
// create bezeir path
let path = UIBezierPath()
// A bezier path
path.move(to: CGPoint(x: 0, y: 0.025))
path.addLine(to: CGPoint(x: 0.02, y: -0.005))
path.addLine(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: -0.02, y: -0.005))
path.close()
// create arrow shape
let arrowShape = SCNShape(path: path, extrusionDepth: 0.001)
arrowShape.chamferRadius = 50
// create new node
arrownode = SCNNode(geometry: arrowShape);
// set arrow color
arrownode!.geometry?.firstMaterial?.diffuse.contents = UIColor.yellow
The result :
I need like this exactly but with rounded corner.

An instance property chamferRadius creates chamfer for extruded zDepth, not for XY path.
let arrowShape = SCNShape(path: path, extrusionDepth: 0.005)
arrowShape.chamferRadius = 20
If you want to create a rounded corners use the following method for path:
path.addQuadCurve(to: CGPoint, controlPoint: CGPoint)
or
path.addCurve(to: CGPoint, controlPoint1: CGPoint, controlPoint2: CGPoint)

Related

How do I cast two shadows from an UIImageView with a SF Symbol as the image?

I want to cast a shadow to the lower left AND the lower right of my Image. But I'm struggling to get the second shadow cast (layer2)...
I've created a subclass of UIImage view and use that in IB:
import UIKit
class ShadowImageView: UIImageView {
var layer2 = CALayer()
override func layoutSubviews() {
super.layoutSubviews()
self.layer2 = CALayer(layer: self.layer)
self.clipsToBounds = false
self.layer.shadowColor = UIColor.gray.cgColor
self.layer.shadowOffset = CGSize(width: -16, height: 16)
self.layer.shadowOpacity = 0.33
self.layer.shadowRadius = 3
self.layer.masksToBounds = false
self.layer2.shadowColor = UIColor.gray.cgColor
self.layer2.shadowOffset = CGSize(width: 16, height: 16)
self.layer2.shadowOpacity = 0.33
self.layer2.shadowRadius = 3
self.layer2.masksToBounds = false
self.layer.insertSublayer(self.layer2, at: 0)
print("\(self.layer)\n")
if let sublayers = self.layer.sublayers {
for layer in sublayers {
print("\(layer)\n")
}
}
}
}
Am I going in the right direction to copy the layer and apply a new shadow to it or should I look in a different direction?
Fixed the shadow problem. I was able to generate double shadows by adding 3 layers on my UIView. The furthest behind (the first sublayer of the view's layer) is the darker shadow, the one on top of that is the lighter shadow and on top of that I have one with the normal color.
I'm 'drawing' the images on the layers by applying a shadow path & color.
The image I'm drawing is coming from a method like listed below, but it's not being generated from a SFSymbols. I would like to generate it during runtime so I end up with a simple method of using symbols to place Neumorphic elements in our apps. The manual generation of these BezierPaths isn't really pleasant.
static var shieldFill: UIBezierPath = {
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 58.94, y: 115.53))
bezierPath.addCurve(to: CGPoint(x: 62.84, y: 114.4), controlPoint1: CGPoint(x: 60.06, y: 115.53), controlPoint2: CGPoint(x: 61.57, y: 115.04))
bezierPath.addCurve(to: CGPoint(x: 101.76, y: 74.46), controlPoint1: CGPoint(x: 91.5, y: 100.49), controlPoint2: CGPoint(x: 101.76, y: 91.89))
bezierPath.addLine(to: CGPoint(x: 101.76, y: 39.94))
bezierPath.addCurve(to: CGPoint(x: 92.04, y: 25.05), controlPoint1: CGPoint(x: 101.76, y: 31.84), controlPoint2: CGPoint(x: 99.41, y: 28.12))
bezierPath.addCurve(to: CGPoint(x: 67.63, y: 16.85), controlPoint1: CGPoint(x: 87.89, y: 23.29), controlPoint2: CGPoint(x: 71.29, y: 17.97))
bezierPath.addCurve(to: CGPoint(x: 58.94, y: 15.53), controlPoint1: CGPoint(x: 64.94, y: 16.11), controlPoint2: CGPoint(x: 61.52, y: 15.53))
bezierPath.addCurve(to: CGPoint(x: 50.24, y: 16.85), controlPoint1: CGPoint(x: 56.35, y: 15.53), controlPoint2: CGPoint(x: 52.93, y: 16.11))
bezierPath.addCurve(to: CGPoint(x: 25.83, y: 25.05), controlPoint1: CGPoint(x: 46.53, y: 17.97), controlPoint2: CGPoint(x: 29.93, y: 23.29))
bezierPath.addCurve(to: CGPoint(x: 16.11, y: 39.94), controlPoint1: CGPoint(x: 18.46, y: 28.12), controlPoint2: CGPoint(x: 16.11, y: 31.84))
bezierPath.addLine(to: CGPoint(x: 16.11, y: 74.46))
bezierPath.addCurve(to: CGPoint(x: 55.03, y: 114.4), controlPoint1: CGPoint(x: 16.11, y: 91.89), controlPoint2: CGPoint(x: 26.46, y: 100.29))
bezierPath.addCurve(to: CGPoint(x: 58.94, y: 115.53), controlPoint1: CGPoint(x: 56.3, y: 115.04), controlPoint2: CGPoint(x: 57.76, y: 115.53))
bezierPath.close()
return bezierPath
}()
I was able to get this very easily:
That seems to be the sort of thing you're after.
The double shadow stems from the fact that in a graphics context, shadow drawing is a state applied to any future drawing. So if you set the context's shadow to be down-and-to-the-left and draw the symbol image, and then set the context's shadow to be down-and-to-the-right and draw the symbol image, you get the double shadow.
It is true that the symbol image itself is drawn twice but that does not necessarily change the way it appears; if I hadn't told you I'd drawn it twice, I don't think you'd have known.

How to draw a UIBezierPath with an ARC to the right similar to the image shown?

How do I draw this UIBezierPath to make it look identical to the green strip on the left of the image below? The rounded arc to the right.
path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: 20, y: 0))
path.addLine(to: CGPoint(x: 20, y: 80))
//Add Half Circle Arc To Right
path.addArc(withCenter: CGPoint(x: 0, y: 160), radius: bounds.width, startAngle: 0, endAngle: 90, clockwise: true)
path.addLine(to: CGPoint(x: 20, y: 215))
path.addLine(to: CGPoint(x: 20, y: bounds.maxY))
path.addLine(to: CGPoint(x: 0, y: bounds.maxY))
path.close()
What you need is a routine that given the height of the desired arc and how far this “bubble” should stick out, and will determine the desired arc angle, radius, and center offset.
To determine the center of the circle giving three points on that circle is to identify the chord between two points and then identify the line that bisects that line segment. That results in a line that goes through the center of the circle. Then repeat that for a different two points on the circle. The intersection of those two lines will be the center of the circle.
So, I used a little basic algebra to calculate the slope (m), the y-intercept (b), and the x-intercept (xIntercept) of the line that bisects the line segment between the start of the arc and its half point. We can take that line, and see where it intercepts the x-axis and determine the center of the circle.
From that, a little trigonometry gives us the angle and the radius of the arc that intersects these three points (the top of the arc, the middle of the arc, and the bottom of the arc).
You get something like:
/// Calculate parameters necessary for arc.
/// - Parameter height: The height of top half of the arc.
/// - Parameter distance: How far out the arc should project.
func angleRadiusAndOffset(height: CGFloat, distance: CGFloat) -> (CGFloat, CGFloat, CGFloat) {
let m = distance / height
let b = height / 2 - distance * distance / (2 * height)
let xIntercept = -b / m
let angle = atan2(height, -xIntercept)
let radius = height / sin(angle)
return (angle, radius, xIntercept)
}
And you can then use that to create your path:
var point: CGPoint = CGPoint(x: bounds.minX, y: bounds.minY)
let path = UIBezierPath()
path.move(to: point)
point.x += edgeWidth
path.addLine(to: point)
point.y += bubbleStartY
path.addLine(to: point)
let (angle, radius, offset) = angleRadiusAndOffset(height: bubbleHeight / 2, distance: bubbleWidth)
let center = CGPoint(x: point.x + offset, y:point.y + bubbleHeight / 2)
path.addArc(withCenter: center, radius: radius, startAngle: -angle, endAngle: angle, clockwise: true)
point.y = bounds.maxY
path.addLine(to: point)
point.x = bounds.minX
path.addLine(to: point)
path.close()
And that yields:
That’s using these values:
var edgeWidth: CGFloat = 10
var bubbleWidth: CGFloat = 30
var bubbleHeight: CGFloat = 100
var bubbleStartY: CGFloat = 80
But you can obviously adjust these values as needed.

Create UIView with rounded specified borders

I want to create a UIView that has rounded borders which I have specified. For example, I want to create a UIView that has 3 borders: left, top and right, and the topright and topleft borders should be rounded.
This let's me create resizable border views (without rounded corners):
open class ResizableViewBorder: UIView {
open override func layoutSubviews() {
super.layoutSubviews()
setNeedsDisplay()
}
open override func draw(_ rect: CGRect) {
let edges = ... get some UIRectEdges here
let lineWidth = 1
if edges.contains(.top) || edges.contains(.all) {
addBezierPath(paths: [
CGPoint(x: 0, y: 0 + lineWidth / 2),
CGPoint(x: self.bounds.width, y: 0 + lineWidth / 2)
])
}
if edges.contains(.bottom) || edges.contains(.all) {
addBezierPath(paths: [
CGPoint(x: 0, y: self.bounds.height - lineWidth / 2),
CGPoint(x: self.bounds.width, y: self.bounds.height - lineWidth / 2)
])
}
if (edges.contains(.left) || edges.contains(.all) || edges.contains(.right)) && CurrentDevice.isRightToLeftLanguage{
addBezierPath(paths: [
CGPoint(x: 0 + lineWidth / 2, y: 0),
CGPoint(x: 0 + lineWidth / 2, y: self.bounds.height)
])
}
if (edges.contains(.right) || edges.contains(.all) || edges.contains(.left)) && CurrentDevice.isRightToLeftLanguage{
addBezierPath(paths: [
CGPoint(x: self.bounds.width - lineWidth / 2, y: 0),
CGPoint(x: self.bounds.width - lineWidth / 2, y: self.bounds.height)
])
}
}
private func addBezierPath(paths: [CGPoint]) {
let lineWidth = 1
let borderColor = UIColor.black
let path = UIBezierPath()
path.lineWidth = lineWidth
borderColor.setStroke()
UIColor.blue.setFill()
var didAddedFirstLine = false
for singlePath in paths {
if !didAddedFirstLine {
didAddedFirstLine = true
path.move(to: singlePath)
} else {
path.addLine(to: singlePath)
}
}
path.stroke()
}
}
However, I can not find a nice robust way to add a corner radius to a specified corner. I have a hacky way to do it with a curve:
let path = UIBezierPath()
path.lineWidth = 2
path.move(to: CGPoint(x: fakeCornerRadius, y: 0))
path.addLine(to: CGPoint(x: frame.width - fakeCornerRadius, y: 0))
path.addQuadCurve(to: CGPoint(x: frame.width, y: fakeCornerRadius), controlPoint: CGPoint(x: frame.width, y: 0))
path.addLine(to: CGPoint(x: frame.width, y: frame.height - fakeCornerRadius))
path.stroke()
Which gives me this:
Why is that line of the quad curve so fat? I prefer using UIBezierPaths over CALayers because CALayers have a huge performance impact...
What's wrong with your curve-drawing code is not that the curve is fat but that the straight lines are thin. They are thin because they are smack dab on the edge of the view. So your line width is 2 points, but one of those points is outside the view. And points are not pixels, so what pixels are there left to fill in? Only the ones inside the view. So the straight lines have an apparent visible line width of 1, and only the curve has a visible line width 2.
Another problem is that you probably are looking at this app running in the simulator on your computer. But there is a mismatch between the pixels of the simulator and the pixels of your computer monitor. That causes numerous drawing artifacts. The way to examine the drawing accurately down to the pixel level is to use the simulator application's screen shot image facility and look at the resulting image file, full-size, in Preview or similar. Or run on a device and take the screen shot image there.
To demonstrate this, I modified your code to operate in an inset version of the original rect (which, by the way, should be your view's bounds, not its frame):
let fakeCornerRadius : CGFloat = 20
let rect2 = self.bounds.insetBy(dx: 2, dy: 2)
let path = UIBezierPath()
path.lineWidth = 2
path.move(
to: CGPoint(x: rect2.minX + fakeCornerRadius, y: rect2.minY))
path.addLine(
to: CGPoint(x: rect2.maxX - fakeCornerRadius, y: rect2.minY))
path.addQuadCurve(
to: CGPoint(x: rect2.maxX, y: rect2.minY + fakeCornerRadius),
controlPoint: CGPoint(x: rect2.maxX, y: rect2.minY))
path.addLine(
to: CGPoint(x: rect2.maxX, y: rect2.maxY - fakeCornerRadius))
path.stroke()
Taking a screen shot from within the Simulator application, I got this:
As you can see, this lacks the artifacts of your screen shot.

How to move SKSpriteNode in a curve without rotating the sprite in Swift

I'm trying to move a sprite with a curve.
I got this code:
let path = UIBezierPath()
path.move(to: CGPoint.zero)
path.addQuadCurve(to: CGPoint(x: ball.position.x+200, y: ball.position.y+50), controlPoint: CGPoint(x: ball.position.x+100, y: ball.position.y+200))
ball.run(SKAction.follow(path.cgPath, speed: 1.0))
I have few questions:
1 - why my sprite is rotating while moving, and if I can control this rotation?
2 - any idea why the ball is moving only small part of the way and very slow and not a smooth moving (10-20 seconds)?
Does anyone have any idea how this code works?
All the answers I found were related to older Swift version that had different method.
At last I've found the solution :)
func beizerSprite()
{
// create a bezier path that defines our curve
let path = UIBezierPath()
path.move(to: CGPoint(x: 16,y: 239))
path.addCurve(to:CGPoint(x: 301, y: 239),
controlPoint1: CGPoint(x: 136, y: 373),
controlPoint2: CGPoint(x: 178, y: 110))
// use the beizer path in an action
_playButton.run(SKAction.follow(path.cgPath,
asOffset: false,
orientToPath: true,
speed: 50.0))
}
This move the SKSprite in a curve along the screen.

Scaleable UIBezier in Swift 3

I'm trying to design a scaleable UIBezier.
This is my Swift Code:
#IBDesignable
class AnimatedRingView: UIView {
override func draw(_ rect: CGRect) {
//// Color Declarations
let strokeColor = UIColor(red: 1.000, green: 0.706, blue: 0.004, alpha: 1.000)
//// bezier Drawing
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 48.93, y: 266.07))
bezierPath.addCurve(to: CGPoint(x: 48.93, y: 53.93), controlPoint1: CGPoint(x: -9.64, y: 207.49), controlPoint2: CGPoint(x: -9.64, y: 112.51))
bezierPath.addCurve(to: CGPoint(x: 261.07, y: 53.93), controlPoint1: CGPoint(x: 107.51, y: -4.64), controlPoint2: CGPoint(x: 202.49, y: -4.64))
bezierPath.addCurve(to: CGPoint(x: 261.07, y: 266.07), controlPoint1: CGPoint(x: 319.64, y: 112.51), controlPoint2: CGPoint(x: 319.64, y: 207.49))
bezierPath.addLine(to: CGPoint(x: 261.07, y: 266.07))
bezierPath.lineCapStyle = .round;
bezierPath.lineJoinStyle = .round;
strokeColor.setStroke()
bezierPath.lineWidth = 10
bezierPath.stroke()
}
}
This is the result:
My problem:
To save time, I created the UIBezierPath by PaintCode:
And as you can see above, we have a width and height for the UIBezierPath, however, the code is displayed points of UIBezierPath ...
How can I create a size variable to assign a value via code (or inspector) that automatically identify the points of UIBezierPath?
Is this possible? Need help!
Given you creating a Circular Arc, using Bezier Curves for that purpose seems like overkill.
With UIBezierPath, you can use this function:
func addArcWithCenter(_ center: CGPoint,
radius radius: CGFloat,
startAngle startAngle: CGFloat,
endAngle endAngle: CGFloat,
clockwise clockwise: Bool)
and set a radius, along with the requisite start and end angles.
If you really must use cubic Béziers for the purpose, you should use the magic number kappa which equals 0.5522847498, and that is the control points for a circle built out of cubic Béziers.
I have placed a small demo of what I'm describing on my web site at:
http://www.trilithon.com/download/AnimatedRingView.zip
Hope That Helps.
Bezier objects do not support attaching variables to their dimensions, but you can use Oval object with custom start/end angles.
— PaintCode Support