How to add SKVideoNode to ARKit-SceneKit in iOS 11? - sprite-kit

Is there a way to add SKVideoNode to ARKit scene(Scenekit)? I tried adding SKVideoNode as SCNPlane geometry diffuse contents but it is not working,
let videoNode = SKVideoNode(fileNamed: "0.mov")
videoNode.size = CGSize(width: 200, height: 100)
videoNode.alpha = 0.8
videoNode.play()
self.videoNode = videoNode
let plane = SCNPlane(width: 0.05, height: 0.05)
let newMaterial = SCNMaterial()
newMaterial.isDoubleSided = true
newMaterial.diffuse.contents = self.videoNode
plane.materials = [newMaterial]
let node = SCNNode(geometry: plane)
parent.addChildNode(node)

SKNode is not one of the supported types for SceneKit material property contents. Neither are any of its subclasses.
If you want to get SpriteKit content mapped onto a SceneKit material, the way to do it is to set an SKScene as the material property contents. That scene can then contain any number or type of SpriteKit nodes.

I had the exact same issue. Fixed by setting a size to my Sprite Kit Scene when I created it:
let spriteKitScene = SKScene(size: CGSize(width: 640, height: 480))
I hope that fixes your issue!

Related

How to increase lighting in scene?

I'm trying to light up a basic model I downloaded from Mixamo.
let scene = SCNScene(named: "art.scnassets/Ch45_nonPBR.dae")!
// create and add a camera to the scene
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode)
// place the camera
cameraNode.position = SCNVector3(x: 0, y: 40, z: 110)
// create and add a light to the scene
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light!.type = .omni
lightNode.position = SCNVector3(x: 0, y: 50, z: 50)
scene.rootNode.addChildNode(lightNode)
// create and add an ambient light to the scene
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = .ambient
ambientLightNode.light!.color = UIColor.white
scene.rootNode.addChildNode(ambientLightNode)
At present the camera is too close to the model. But if I change the z-value from 110, all I see is black. I imagine this has something to do with lighting. What should my lighting be so that I can see my model even if I change the z-value of my camera to a higher value so I can see the model from far away ?
EDIT: For example, right now you can see at this distance, part of the leg is not visible:
If I move further away, the whole model won't be visible!!
EDIT: For example, how do I edit the lighting so it looks just like in the Mixamo preview on their website:
I would like to move the camera far enough away so I can see the whole model on screen.
SCNCamera has a zFar property, the default value is 100, any surface further away from the camera than this is clipped to improve performance. In your screenshot the leg is the furthest part of the model from the camera so gets clipped first, and as you move further away the whole model is clipped.
You can just increase the zFar to a number suitable for your scene.

SceneKit: sceneView projectPoint with multiple cameras

If a scene contains multiple cameras, which camera does the projectPoint method use to project points from 3D to screen-space? If this is defined by the pointOfView property, then how come when I update the position of the pointOfView a given 3D point is still projected to the same 2D point?
Since SCNCamera belongs to SCNView, just set the PoV via the "pointOfView" instance property of a View to a required camera node.
let cameraNode001 = SCNNode()
cameraNode001.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode001)
cameraNode001.position = SCNVector3(x: 0, y: 0, z: 15)
let cameraNode002 = SCNNode()
cameraNode002.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode002)
cameraNode002.position = SCNVector3(x: 10, y: 10, z: 30)
let sceneView = self.view as! SCNView
sceneView.scene = scene
sceneView.pointOfView = cameraNode001
then you can change PoV:
sceneView.pointOfView = cameraNode002

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.

How to draw 3D object with core graphics in Swift?

How can I draw a 3D object (preferably a rectangle) with core graphics in Swift?
Is it possible or do I have to use a different library?
Is it possible with UIKit?
Borrowing from this answer: https://stackoverflow.com/a/24127282/887210
The key part for your question is:
SCNBox(width: 1, height: 4, length: 9, chamferRadius: 0)
This draws a rectangular box with SceneKit and UIKit. It's set up to be used in a custom UIViewController in your project but it can easily be adapted to other uses.
The example code:
override func loadView() {
// create a scene view with an empty scene
let sceneView = SCNView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
let scene = SCNScene()
sceneView.scene = scene
// default lighting
sceneView.autoenablesDefaultLighting = true
// a camera
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
scene.rootNode.addChildNode(cameraNode)
// a geometry object
let box = SCNBox(width: 1, height: 4, length: 9, chamferRadius: 0)
let boxNode = SCNNode(geometry: box)
scene.rootNode.addChildNode(boxNode)
// configure the geometry object
box.firstMaterial?.diffuse.contents = UIColor.red
box.firstMaterial?.specular.contents = UIColor.white
// set a rotation axis (no angle) to be able to
// use a nicer keypath below and avoid needing
// to wrap it in an NSValue
boxNode.rotation = SCNVector4(x: 1, y: 1, z: 0.0, w: 0.0)
// animate the rotation of the torus
let spin = CABasicAnimation(keyPath: "rotation.w") // only animate the angle
spin.toValue = 2.0*Double.pi
spin.duration = 10
spin.repeatCount = HUGE // for infinity
boxNode.addAnimation(spin, forKey: "spin around")
view = sceneView // Set the view property to the sceneView created here.
}
This question is similar to the question whether it is possible to draw a 3D object on a sheet of paper which is, between, 2D. The third dimension effect is achieved drawing additional lines as the projections. The third dimension could also be perceived through the motion, so, Core Animation is a possible companion to Core Graphics but it requires a lot of calculation, as result, it is quite complicated (using Core Animation).
Actually, SceneKit or Metal are the options to draw 3D models using Swift.

How to contain SKEmitterNode particles in parent Node?

I would like to add a SKEmitterNode to SKNode but have its particles stay inside the parent node's frame. Kinda like clipsToBounds property on UIView.
Example: the particles from the emitter should not leave the black square SKSpriteNode:
You could do it with SKCropNode. Like this:
if let particles = SKEmitterNode(fileNamed: "rain.sks") {
let cropNode = SKCropNode()
cropNode.position = CGPoint(x: CGRectGetMidX(frame), y: CGRectGetMidY(frame))
cropNode.zPosition = 3
cropNode.maskNode = SKSpriteNode(color: SKColor.blackColor(), size: CGSize(width: 150, height: 150))
cropNode.addChild(particles)
addChild(cropNode)
}
Unfortunately this works only on iOS8... When you try to add an emitter into crop node in iOS9, you will probably run into some issues, eg. nothing will be rendered and fps drop may occur. And this is already known issue.
Like it's said in that link, instead of particles being rendered, nothing actually happens. Personally, I haven't experienced fps issues , but particles are definitely not rendered.
A workaround would be to add a node which will hold an emitter, and then mask that container node. So, let's add an SKSpriteNode to make that black background like from your example. Let's call it background:
if let particles = SKEmitterNode(fileNamed: "rain.sks") {
let cropNode = SKCropNode()
cropNode.position = CGPoint(x: CGRectGetMidX(frame), y: CGRectGetMidY(frame))
cropNode.zPosition = 3
let blackNode = SKSpriteNode(color: .blackColor(), size: CGSize(width: 200, height: 200))
blackNode.addChild(particles)
cropNode.maskNode = SKSpriteNode(color: SKColor.blackColor(), size: CGSize(width: 200, height: 200))
cropNode.addChild(blackNode)
addChild(cropNode)
}