ARKit: Occlude everything in a plane except reference image - swift

I have an image reference that is triggering an experience on ARKit2, and I want to create an occlusion plane that covers everything around my trigger, except the trigger itself. Representatively, it is something like this (placed the image below the plane to show better, but it is in reality on the same level):
As of now, I have no issue with making an occlusion on a regular shape like so:
func setOcclusionMaterial() {
let occlusionPlane = SCNPlane(width: imageReference!.physicalSize.width, height: imageReference!.physicalSize.height)
let holdout = SCNMaterial()
holdout.isDoubleSided = true
holdout.diffuse.contents = CIColor.black
holdout.colorBufferWriteMask = SCNColorMask(rawValue: 0)
occlusionPlane.firstMaterial? = holdout
let occlusionNode = SCNNode()
occlusionNode.eulerAngles.x = -.pi / 2
occlusionNode.geometry = occlusionPlane
node!.addChildNode(occlusionNode)
}
However, I'm not sure how I could create a plane that excludes the image area (a circle).
What I'm trying to do is to place a 3D element that appears to be below my trigger but only visible if seen top-down or with a slight angle.
Hope this is clear enough and thanks for your help if you have any ideas.

Your code does not render an occlusion for me, at least not in the Playground I quickly threw together. Maybe you can create one if my answer does not actually solve your problem.
You have to options here. Either create a custom geometry, in a 3D modelling tool like blender or manual via SCNGeometrySource but that is expansive because you will need a lot of triangles to make it work.
You can try to apply a CALayer as the plane's geometry:
Create a CGPath that is the rectangle with the circular cutout (or any form you'd like) and create a CAShapeLayer from the path.
var path = CGMutablePath()
path.addRect(CGRect(x: 0, y: 0, width: 100, height: 100))
path.addEllipse(in: CGRect(x: 25, y: 25, width: 50, height: 50))
let layer = CAShapeLayer()
layer.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
layer.path = path
layer.fillColor = UIColor.white.cgColor
layer.fillRule = .evenOdd
I could not confirm this actually worked because your occlusion is not quite working for me.

Related

Cropping An Image (CIImage) To A UIBezierPath

I have seen this question asked in varying ways on Stack Overflow, but I have not seen an implementation that leverages anything but UIKit to develop an approach. For my use case, I have a CIImage and UIBezierPath (rather, a set of points from a machine learning feature detection model) that I would like to use a "mask" for the CIImage.
For example, I may have an image of a person's face. Subsequently, I may have a UIBezierPath that creates a closed shape around one of their eyes. Ideally, I would like to "crop" my CIImage to encompass the UIBezierPath. I recognize that since the final image will need to be square/rectangle, I would like to have any area outside of the UIBezierPath either black or transparent so it can be used as a mask for a future composition.
At present, I have code set up as such;
func doCrop(image: CIImage) -> CIImage {
// The drawing context (here to show I need to use CIContext for another use later on in this function.
let ciContext = CIContext()
// The original source image
let source = image
// Setup the path
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: 400, y: 0))
path.addLine(to: CGPoint(x: 600, y: 200))
path.addLine(to: CGPoint(x: 400, y: 400))
path.addLine(to: CGPoint(x: 200, y: 400))
path.addLine(to: CGPoint(x: 100, y: 200))
path.close()
// Test
let cropped = source.cropped(to: path.bounds)
}
To be honest, I'm really unsure where to go from here. From what I have ascertained online, I see solutions that either implement UIKit to create an image context and draw a path, or solutions that only mask a UIImageView (which is not relevant for my needs, as I will need to draw this into a CIContext and pass through a Metal shader at a later point).

Is it possible to create an instance of a particular location on the screen (using auto layout)?

I have 4 playing cards on the screen. At the press of a button, I want one of the cards at random to move to the middle of the top half of the screen. Is it possible to create an instance of a constraint (eg: centerXAnchor with constant 0, and centerYAnchor with constant -200) so that I can use CGAffineTransform and move the random image to this point?
Ive tried creating an instance of a CGRect Frame:
let destination = CGPoint(x: 10, y: 10)
but this does not move evenly across devices.
An affine transformation matrix is used to rotate, scale, translate, or skew the objects you draw in a graphics context.
I don't think CGAffineTransform is the ideal thing to use for this task. You aren't doing any the above things (rotate, scale, translate, or skew).
I think you would likely be best using UIView.animateWithDuration
let cardSize = CGSize(width: 100, height: 100)
let card = UIView(frame: CGRect(origin: CGPoint(x: 0, y: 0), size: cardSize))
UIView.animate(withDuration: 1.0) {
card.frame = CGRect(origin: CGPoint(x: 100, y: 100), size: cardSize)
}

Filling animation + permanent mask in swift?

Something like this:
Or follow this link if you can:
https://www.lottiefiles.com/450-play-fill-loader
My case is simpler - I have a view with mask which consists of multiple rects and a linear fill. Mask is created in runtime (so I can't use lottie) but remains permanent, fill is animated (filling from right to left). But how to draw it?
Note: I tried to find a similar animation implementation but in most cases they just try to change the mask params while in my case mask is constant.
If i understand you correct, you can add subview under your mask and change its width, something like that:
let fillingStartFrame = CGRect.init(x: 0, y: 0, width: 0, height: view.frame.height)
let fillingEndFrame = CGRect.init(x: 0, y: 0, width: view.frame.width, height: view.frame.width)
let fillingView = UIView(frame: fillingStartFrame)
view.insertSubview(fillingView, belowSubview: YOUR_MASK)
UIView.animate(withDuration: 0.3) {
fillingView.frame = fillingEndFrame
}

Draw hemisphere in SceneKit using Swift

How to draw a hemisphere in scene kit programatically using swift, I trying the build the hemisphere robot head using scene kit but did not how to do, I came to know we can use SCNShape with bezier path
let path = UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: 0.4, height: 0.4))
let bottomNode = SCNNode()
let hemisphere = SCNShape(path: path, extrusionDepth: 0.1)
but did not generated the hemisphere. Can any one help me on this?

SpriteKit: unwanted stretching of sprite when resizing/scaling

I've seen similar questions before but seems that no clear solution is provided. Currently I have a SKScene with child, contained by a SKView. Note: I'm not using a Game project; instead, I'm using the SKView in a normal UIView, and just add SKView as the subview of the UIView. The sprite itself is a small image, and it only occupies a certain portion in the middle-bottom part of the whole frame. The code looks something like this:
let imageView = SKView()
imageView.frame = CGRect(x: ..., y: ..., width: ..., height: ...) //all are predefined constants
let sprite = SKSpriteNode(texture: SKTexture(imageNamed: "image"))
sprite.position = CGPoint(x: imageView.frame.width * 0.5, y: imageView.frame.height * 0.5) // so it's positioned in the center
let scene = SKScene(size: imageView.frame.size) // also tried with sprite.size, but not working
scene.addChild(sprite)
scene.scaleMode = .aspectFit
imageView.presentScene(scene)
self.frame.addSubview(imageView)
Now the problem is the sprite is not in its original shape; it's stretched along the vertical axis. I tried changing the scaleMode with all 5 choices: .fill / .aspectFill / .aspectFit / .resizeFill / none, but none of them give the sprite its original shape/ratio. I've also tried changing the constants for the imageView's frame, but that only cuts the sprite (if imageView.frame.height is decreased). So may I know how to address this issue?
Your code works fine as written, so the problem is likely elsewhere. I modified it a little bit so you can test it out in a Playground. Just make a new Playground and copy and paste. You'll also have to drag in an image for your sprite.
import SpriteKit
import PlaygroundSupport
// create the outer UIView and show it on the playground
let outerView = UIView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
PlaygroundPage.current.liveView = outerView
// create the SKView and add it to the outer view
let imageView = SKView(frame: CGRect(x: 25, y: 25, width: 250, height: 250))
outerView.addSubview(imageView)
// create the scene and present it
var scene = SKScene(size: imageView.frame.size)
imageView.presentScene(scene)
// create the sprite, position it, add it to the scene
let sprite = SKSpriteNode(texture: SKTexture(imageNamed: "worf.jpg"))
sprite.position = CGPoint(x: imageView.frame.width * 0.5, y: imageView.frame.height * 0.5)
scene.scaleMode = .aspectFit
scene.addChild(sprite)
Here's the result:
No stretching! So your issue is probably related to the UIView you're placing it in. For example if you add:
outerView.transform = CGAffineTransform(scaleX: 1, y: 2)
Now the image is stretched as you describe. Take a look at your containing view and everything above it.