SceneKit Imported COLLADA Box not "Lit" - swift

I have a SceneKit project with two object in the scene view. The first object is a plane created via SCNPlane. The second object is a simple box created in Blender. In code, I setup ambient and omnidirectional lighting. It lighting effects work for the plane:
But, when I add the box on top of the plane, the lighting effects work on the plane but not the box imported from COLLADA file:
I suspect the problem has to do with normals, but I am not sure. Has anyone importing DAE via SceneKit experienced this? The setup code for the lighting and objects is this:
private func setupAmbientLight() {
// setup ambient light source
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = SCNLight.LightType.ambient
ambientLightNode.light!.color = NSColor(white: 0.35, alpha: 1.0).cgColor
// add to scene
guard let scene = sceneView.scene else {
return
}
scene.rootNode.addChildNode(ambientLightNode)
}
private func setupOmniDirectionalLight() {
// initialize noe
let omniLightNode = SCNNode()
// assign light
omniLightNode.light = SCNLight()
// set type
omniLightNode.light!.type = SCNLight.LightType.omni
// color and position
omniLightNode.light!.color = NSColor(white: 0.56, alpha: 1.0).cgColor
omniLightNode.position = SCNVector3Make(0.0, 2000.0, 0.0)
// add to scene
guard let scene = sceneView.scene else {
return
}
scene.rootNode.addChildNode(omniLightNode)
}
private func setupPlane() {
// create plane geometry with size and material properties
let myPlane = SCNPlane(width: planeSideLength, height: planeSideLength)
myPlane.firstMaterial!.diffuse.contents = NSColor.orange.cgColor
myPlane.firstMaterial!.specular.contents = NSColor.white.cgColor
// intialize node
let planeNode = SCNNode()
// assign plane geometry to the node
planeNode.geometry = myPlane
// rotate -90.0 about the x-axis
let rotMat = SCNMatrix4MakeRotation(-CGFloat(M_PI/2.0), 1.0, 0.0, 0.0)
planeNode.transform = rotMat
planeNode.position = SCNVector3Make(0.0, 0.0, 0.0)
// setup the node's physics body property
planeNode.physicsBody = SCNPhysicsBody(type: .static, shape: SCNPhysicsShape(geometry: myPlane, options: nil))
planeNode.physicsBody!.categoryBitMask = PhysicsMask3DOF.plane.rawValue
// add to scene
guard let scene = sceneView.scene else {
return
}
scene.rootNode.addChildNode(planeNode)
}
private func setupRobot() {
guard let mainScene = sceneView.scene else {
return
}
let bundle = Bundle.main
guard let url = bundle.url(forResource: "robot.scnassets/test_cube", withExtension: "dae") else {
return
}
var cubeScene: SCNScene?
do {
try cubeScene = SCNScene.init(url: url, options: nil)
}
catch {
return
}
guard let cubeNode = cubeScene!.rootNode.childNode(withName: "Cube", recursively: true) else {
return
}
cubeNode.removeFromParentNode()
cubeNode.scale = SCNVector3Make(2000.0, 2000.0, 2000.0)
cubeNode.geometry!.firstMaterial!.diffuse.contents = NSColor.blue.cgColor
cubeNode.geometry!.firstMaterial!.specular.contents = NSColor.white.cgColor
mainScene.rootNode.addChildNode(cubeNode)
}
Update:
So I commented the code for importing the box from DAE and instead added code to create the box via SCNBox and the lighting effects appear to work:

Duh, the box is [2000 x 2000 x 2000] and its node is position at (0, 0, 0). The position of the omni-light source node is (0, 2000, 0). Just needed to move the light source up. Which then begs the question of why was the box properly lit when I created the box with the same dimensions via SCNBox function instead of importing from the DAE file

Related

Unexpected rendering with SceneKit and CIFilter

Here is a simple SceneKit project with two boxes. I’ve added a CIBloom filter to one of them. When I rotate the scene, the ”glow” effect is rendered behind the other box?
I saw someone else had this issue and solved it by setting writesToDepthBuffer to false but in my case it’s important to keep all 3D data (I want to rotate around the scene).
I’ve provided a couple of images and the code.
I would really appreciate any help!
Here is the code I used:
import UIKit
import QuartzCore
import SceneKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// create a new scene
let scene = SCNScene()
// 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: 0, z: 15)
let box = SCNNode(geometry: SCNBox(width: 8.0, height: 8.0, length: 2.0, chamferRadius: 0.0))
box.geometry?.firstMaterial?.diffuse.contents = UIColor.green
box.position.z = -4
scene.rootNode.addChildNode(box)
let box2 = SCNNode(geometry: SCNBox(width: 1.0, height: 1.0, length: 1.0, chamferRadius: 0.0))
box2.geometry?.firstMaterial?.diffuse.contents = UIColor.red
scene.rootNode.addChildNode(box2)
let bloomFilter = CIFilter(name:"CIBloom")!
bloomFilter.setValue(10.0, forKey: "inputIntensity")
bloomFilter.setValue(100.0, forKey: "inputRadius")
box2.filters = [bloomFilter]
// retrieve the SCNView
let scnView = self.view as! SCNView
// set the scene to the view
scnView.scene = scene
// allows the user to manipulate the camera
scnView.allowsCameraControl = true
scnView.autoenablesDefaultLighting = true
// show statistics such as fps and timing information
scnView.showsStatistics = true
// configure the view
scnView.backgroundColor = UIColor.black
}
override var prefersStatusBarHidden: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return .allButUpsideDown
} else {
return .all
}
}
}

Same color rendered differently in SpriteView vs. SceneView

For performance reasons I have to switch from SceneView to SpriteView in my macOS project (showing more than 63 scenes did not work with SceneView, but it does with SpriteView).
But now im facing an issue that SpriteView is rendering colors differently than SceneView. Below is a simple reproduction of the issue I am facing.
I have tried a multitude of material and lighting options, but I seem to miss something more fundamental. Help is very much appreciated.
var body: some View {
HStack {
// SpriteView
SpriteView(scene: { () -> SKScene in
let scene = SKScene()
scene.backgroundColor = .white
scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
let node = SK3DNode()
node.scnScene = self.sphereScene
scene.addChild(node)
return scene
}())
// SceneView
SceneView(scene: sphereScene,
options: [.autoenablesDefaultLighting])
}
}
var sphereScene: SCNScene {
let scnScene = SCNScene()
let ballGeometry = SCNSphere(radius: 5)
let ballNode = SCNNode(geometry: ballGeometry)
let material = SCNMaterial()
material.diffuse.contents = NSColor.purple
material.lightingModel = .physicallyBased
ballGeometry.materials = [material]
scnScene.rootNode.addChildNode(ballNode)
return scnScene
}
You're absolutely right: SpriteKit processes SceneKit's scenes differently than SceneKit. It's visually noticeable that the lighting intensity, blurring of highlights and decolorization of edges with 90 degree reflection are different. The main tool that can be advised in this case is the use of Ambient Light to additionally illuminate the SpriteKit scene based on the SceneKit content. You should turn a default lighting off (in order to get rid of colorization artifacts) and use regular lights. Here I used directional light.
SpriteView:
import SwiftUI
import SceneKit
import SpriteKit
struct SpriteView: NSViewRepresentable {
var scene = SKScene()
func makeNSView(context: Context) -> SKView {
let skView = SKView(frame: .zero)
skView.presentScene(scene)
scene.backgroundColor = .black
return skView
}
func updateNSView(_ uiView: SKView, context: Context) { }
}
ContentView:
struct ContentView: View {
var body: some View {
ZStack {
HStack {
SpriteView(scene: { () -> SKScene in
let scene = SKScene()
scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
let ambient = SCNNode()
ambient.light = SCNLight()
ambient.light?.type = .ambient
ambient.light?.intensity = 1200
let node = SK3DNode()
node.autoenablesDefaultLighting = false
node.scnScene = self.sphereScene
node.scnScene?.rootNode.addChildNode(ambient)
scene.addChild(node)
return scene
}() )
SceneView(scene: sphereScene, options: [])
}
}
}
var sphereScene: SCNScene {
let scnScene = SCNScene()
scnScene.background.contents = NSColor.black
let ballNode = SCNNode(geometry: SCNSphere(radius: 5.0))
let directional = SCNNode()
directional.light = SCNLight()
directional.light?.type = .directional
directional.light?.intensity = 500
scnScene.rootNode.addChildNode(directional)
let material = SCNMaterial()
material.lightingModel = .physicallyBased
material.diffuse.contents = NSColor.purple
ballNode.geometry?.materials = [material]
scnScene.rootNode.addChildNode(ballNode)
return scnScene
}
}
The following worked for me, correcting saturation and brightness brought me near to the SceneKit defaultLighting appearance:
// get object and manipulate
let object = scene.rootNode.childNode(withName: "object", recursively: false)
let color = NSColor(named: "\(colorNr)")?
.usingColorSpace(.displayP3) // specify color space, important!
object?.geometry?.firstMaterial?.lightingModel = .physicallyBased
// correct color for SpriteView
let color2 = NSColor(hue: color?.hueComponent ?? 0,
saturation: (color?.saturationComponent ?? 0) * 0.55,
brightness: (color?.brightnessComponent ?? 0) * 0.55 + 0.45,
alpha: 1.0)
object?.geometry?.firstMaterial?.diffuse.contents = color2
object?.geometry?.firstMaterial?.diffuse.intensity = 0.9
object?.geometry?.firstMaterial?.roughness.contents = 0.9

How to change SCNView pivot point

Question
How does the SceneView/camera work out around which point to rotate, and how do I force the scene to rotate around the red sphere (-2, 0, 0)?
Demo
In this example SceneView I have placed
a camera at (-2, 0, 5)
a red sphere at (-2, 0, 0)
a blue sphere at (-1, 0, 0)
a white sphere at (0, 0, 0)
So the camera is in the same position along the x-axis as the red sphere
In the image you see the scene rotates around the blue sphere.
Playground example
Here is a working playground to produce the above example
import UIKit
import SceneKit
import PlaygroundSupport
var scene = SCNScene()
var sceneView: SCNView = {
let s = SCNView(
frame: CGRect(x: 0, y: 0, width: 600, height: 600)
)
s.scene = scene
s.backgroundColor = UIColor.black
s.allowsCameraControl = true
return s
}()
PlaygroundPage.current.liveView = sceneView
let redXPosition: simd_float1 = -2.0
let blueXPosition: simd_float1 = -1.0
let originXPosition: simd_float1 = 0.0
// MARK: Scene Nodes
let cameraNode: SCNNode = {
let n = SCNNode()
n.camera = SCNCamera()
n.camera?.contrast = 0.0
n.camera?.wantsHDR = false
return n
}()
cameraNode.simdPosition = simd_float3(redXPosition, 0, 5)
scene.rootNode.addChildNode(cameraNode)
let ambientLightNode: SCNNode = {
let n = SCNNode()
n.light = SCNLight()
n.light!.type = SCNLight.LightType.ambient
n.light!.color = UIColor(white: 0.75, alpha: 1.0)
return n
}()
ambientLightNode.simdPosition = simd_float3(0,5,0)
scene.rootNode.addChildNode(ambientLightNode)
// MARK: Spheres
// MARK: Origin - White
let originNode: SCNNode = {
let sphere = SCNSphere(radius: 0.5)
let node = SCNNode(geometry: sphere)
let mat = SCNMaterial()
mat.diffuse.contents = UIColor.white
sphere.materials = [mat]
return node
}()
// MARK: Red
let redNode: SCNNode = {
let sphere = SCNSphere(radius: 0.3)
let node = SCNNode(geometry: sphere)
let mat = SCNMaterial()
mat.diffuse.contents = UIColor.red
sphere.materials = [mat]
return node
}()
// MARK: Blue
let blueNode: SCNNode = {
let sphere = SCNSphere(radius: 0.3)
let node = SCNNode(geometry: sphere)
let mat = SCNMaterial()
mat.diffuse.contents = UIColor.blue
sphere.materials = [mat]
return node
}()
// MARK: Place nodes in scene
originNode.simdPosition = simd_float3(originXPosition,0,0)
redNode.simdPosition = simd_float3(redXPosition,0,0)
blueNode.simdPosition = simd_float3(blueXPosition,0,0)
scene.rootNode.addChildNode(originNode)
scene.rootNode.addChildNode(redNode)
scene.rootNode.addChildNode(blueNode)

Adding SCNLight inside SK3DNode

I am creating an SK3DNode inside an SKScene:
let ball: SK3DNode = {
let scnScene = SCNScene()
let ballGeometry = SCNSphere(radius: 200)
let ballNode = SCNNode(geometry: ballGeometry)
ballNode.position = SCNVector3(0, 0, 0)
let material = SCNMaterial()
material.diffuse.contents = UIImage(named: "wall")
ballGeometry.materials = [material]
let light = SCNLight()
light.type = .omni
light.color = UIColor.white
let lightNode = SCNNode()
lightNode.light = light
scnScene.rootNode.addChildNode(ballNode)
scnScene.rootNode.addChildNode(lightNode)
let node = SK3DNode(viewportSize: CGSize(width: 1000, height: 1000))
node.scnScene = scnScene
node.autoenablesDefaultLighting = false
return node
}()
However, the sphere renders black. Tried it with or without the material. Is there something I am missing?
The sphere is manually placed at (0, 0, 0) and so is the light (default value). This means that the light is placed inside the sphere. This means that the surface of the sphere is facing away from the light source and thus isn't lit.

Collision between two nodes not detected ARKit

I created two nodes: a sphere and a box:
var sphere = SCNNode(geometry: SCNSphere(radius: 0.005))
//I get the box node from scn file
let boxScene = SCNScene(named: "art.scnassets/world.scn")!
var boxNode: SCNNode?
I want two nodes or physicsBody's to interact, so I created a category for categoryBitMask and contactTestBitMask:
struct CollisionCategory: OptionSet {
let rawValue: Int
static let box = CollisionCategory(rawValue: 1)
static let sphere = CollisionCategory(rawValue: 2)
}
Here I set the box node as a physics body:
self.boxScene.rootNode.enumerateChildNodes { (node, _) in
if node.name == "box" {
boxNode = node
let boxBodyShape = SCNPhysicsShape(geometry: SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0.1), options: nil)
let physicsBody = SCNPhysicsBody(type: .static, shape: boxBodyShape)
boxNode!.physicsBody = physicsBody
boxNode!.physicsBody?.categoryBitMask = CollisionCategory.box.rawValue
boxNode!.physicsBody?.contactTestBitMask = CollisionCategory.sphere.rawValue
boxNode!.physicsBody?.collisionBitMask = boxNode!.physicsBody!.contactTestBitMask
}
}
Here I set the sphere node in the render function, which you can move around the view:
func setUpSphere() {
let sphereBodySphere = SCNPhysicsShape(geometry: SCNSphere(radius: 0.005))
let physicsBody = SCNPhysicsBody(type: .kinematic, shape: sphereBodySphere)
sphere.physicsBody = physicsBody
sphere.physicsBody?.categoryBitMask = CollisionCategory.sphere.rawValue
sphere.physicsBody?.contactTestBitMask = CollisionCategory.box.rawValue
sphere.geometry?.firstMaterial?.diffuse.contents = UIColor.blue
sphere.physicsBody?.collisionBitMask = sphere.physicsBody!.contactTestBitMask
previousPoint = currentPosition
}
///It Adds a sphere and changes his position
func renderer(_ renderer: SCNSceneRenderer, willRenderScene scene: SCNScene, atTime time: TimeInterval) {
guard let pointOfView = sceneView.pointOfView else { return }
let mat = pointOfView.transform
let dir = SCNVector3(-1 * mat.m31, -1 * mat.m32, -1 * mat.m33)
let currentPosition = pointOfView.position + (dir * 0.185)
if buttonPressed {
if let previousPoint = previousPoint {
sphere.position = currentPosition
sceneView.scene.rootNode.addChildNode(sphere)
}
}
}
I added the protocol SCNPhysicsContactDelegate to the ViewController,
and I set in ViewDidLoad():
override func viewDidLoad() {
super.viewDidLoad()
sceneView.delegate = self
sceneView.scene.physicsWorld.contactDelegate = self
///I correctly see the shapes of the sphere and the box physics bodies using
sceneView.debugOptions = .showPhysicsShapes
createBox()
setUpSphere()
sceneView.scene = boxScene
sceneView.scene.physicsWorld.contactDelegate = self
}
Then I added that function:
func physicsWorld(_ world: SCNPhysicsWorld, didEnd contact: SCNPhysicsContact) {
print("Collision!")
}
This is what happens.
When the two nodes collide nothing happens, so I can't know if the two bodies are touching. Could the problem be about .kinematic, .static or about the function render()?
I followed step by step different tutorials about collisions in ARKit: Tutorial 1, Tutorial 2.
I have no idea why it doesn't work like expected.
Is there something wrong in my code?
Download file code link: https://ufile.io/20sla
willRenderScene is called uptown 60 times a second for every time the scene is going to be rendered. Since you're recreating the physics body every time it's probably messing up the physics engine determine collisions.
Try changing your code to only create the physics body once during setup.