ARKit dae model position - ios

I create SCNNode with .dae model.
let node = SCNNode()
let scene = SCNScene(named: "scene.scnassets/scene.dae")!
for child in scene.rootNode.childNodes
{
node.addChildNode(child)
}
Then add simbTransform and add node to the ar scene
var translation = matrix_identity_float4x4
translation.columns.3.z = -1
node.simdTransform = matrix_multiply(currentFrame.camera.transform, translation)
self.sceneView.scene.rootNode.addChildNode(node)
When I move camera node is attached to it. So i can't walk around node or walk up closer.
But when I do something like that, it work as I expected.
let node = SCNNode(geometry: SCNBox(width: 0.5, height: 0.5, length: 0.5, chamferRadius: 0))
var translation = matrix_identity_float4x4
translation.columns.3.z = -1
node.simdTransform = matrix_multiply(currentFrame.camera.transform, translation)
self.sceneView.scene.rootNode.addChildNode(node)
So how can I attach node with .dae model to specify position and not the camera.

In which method you are doing this code?
var translation = matrix_identity_float4x4
translation.columns.3.z = -1
node.simdTransform = matrix_multiply(currentFrame.camera.transform, translation)
self.sceneView.scene.rootNode.addChildNode(node)
If you do this in update method of AR session then it is logical because you change transform of the object every time camera moves.
Make sure you call this only once - during the scene setup.

Related

What is causing SCNNodes not to touch each others when using physics with ARKit?

I'm trying to place objects on top of each others, but for some reason the physics are not working as they should. When I drop items on top of other items there's always some space which remains between the items. What is that space and how can I get rid of it?
As you can see in the picture below, there're three objects stacked on top of each others. This is what always happens when I place them on top of each others.
This is how I create the plane and add physics to it:
private func createPlaneWithPhysics(planeCenter pos: vector_float3) -> SCNNode {
let planeMaterial = SCNMaterial()
planeMaterial.diffuse.contents = UIColor.white.withAlphaComponent(0)
let planeGeometry = SCNBox(width: defaultPlaneWidth, height: 0.01, length: defaultPlaneHeight, chamferRadius: 0)
planeGeometry.materials = [planeMaterial]
let planeNode = SCNNode(geometry: planeGeometry)
planeNode.position = SCNVector3(pos.x, pos.y - DimensionCorrection.yAxisError, pos.z)
let shape = SCNPhysicsShape(geometry: planeGeometry, options: nil)
let physicsBody = SCNPhysicsBody(type: .kinematic, shape: shape)
planeNode.physicsBody = physicsBody
planeNode.physicsBody?.categoryBitMask = Int(SCNPhysicsCollisionCategory.static.rawValue)
planeNode.physicsBody?.contactTestBitMask = Int(SCNPhysicsCollisionCategory.default.rawValue)
return planeNode
}
And in the VirtualContainer I add physics for the VirtualItem(.physicsBody) in the following way:
private func physicsForvirtualItem() -> SCNPhysicsBody {
let shape = SCNPhysicsShape(node: virtualItem, options: [SCNPhysicsShape.Option.type : SCNPhysicsShape.ShapeType.boundingBox])
let physics = SCNPhysicsBody(type: .dynamic, shape: shape)
physics.mass = virtualItem.mass
physics.isAffectedByGravity = true
physics.velocityFactor = SCNVector3(0, 1, 0)
physics.angularVelocityFactor = SCNVector3(0, 1, 0)
physics.categoryBitMask = Int(SCNPhysicsCollisionCategory.default.rawValue)
return physics
}
The physicShape wasn’t moving when I used node: instead of geometry: in the physicsShape method... see my approach below - set a primitive geometry to the physicShape node.
let shape = SCNPhysicsShape(node: virtualItem, options: [SCNPhysicsShape.Option.type : SCNPhysicsShape.ShapeType.convexHull])
When you import geometry created in external 3D authoring packages, the default pivots & bounding boxes can be very unpredictable. To troubleshoot you might just create a simple box geometry and apply it to your imported or irregular geometry.
let box = SCNBox(width: 0.05, height: 0.05, length: 0.05, chamferRadius: 0)
let shape = SCNPhysicsShape(geometry: box, options: [SCNPhysicsShape.Option.type : SCNPhysicsShape.ShapeType.convexHull])
let physics = SCNPhysicsBody(type: .dynamic, shape: shape)
I think it might have something to do with using node instead of geometry
if the box is not centered on the geometry the pivot needs to be centred using a centerPivot function like...
func centerPivot(for node: SCNNode) {
var min = SCNVector3Zero
var max = SCNVector3Zero
node.__getBoundingBoxMin(&min, max: &max)
node.pivot = SCNMatrix4MakeTranslation(
min.x + (max.x - min.x)/2,
min.y + (max.y - min.y)/2,
min.z + (max.z - min.z)/2
)
}
to use just
centerPivot(for: node!)
Using nil or the standard shapes for the collision geometry is designed for speed -- not for correctness. Just use the actual geometry of the SCNNode instead of one of the standard shapes. Today's devices are fast so you are probably fine doing this in many cases.
Something like this, assuming you have your SCNNode already set up with its associated geometry, either from code, or by importing a .dae or .scn file:
let node = SCNNode()
// (add geometry via code or init node from scene loaded from file)
let pShape = SCNPhysicsShape(geometry: node.geometry)
let pBody = SCNPhysicsBody(type: .dynamic, shape: pShape)
// (add any other physicsBody setup stuff)
node.physicsBody = pBody

apply force on scnnode without loosing it on collision

I'm trying to create some simple logic on scenekit but without luck so far.
I have a sphere and between two planes. The sphere as dynamic physic body and the two planes contains static physic body.
I'm applying force on the sphere towards one of the planes. on collision the sphere bounces from the plane to the opposite direction but it losses lots of the force. how can I make it to keep the force on collision. This is the viewDidLoadCode that is generated in xCode on Game template with my changes:
override func viewDidLoad() {
super.viewDidLoad()
// create a new scene
let scene = SCNScene(named: "art.scnassets/ship.scn")!
// 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)
// create and add a light to the scene
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light!.type = .omni
lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
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.darkGray
scene.rootNode.addChildNode(ambientLightNode)
// retrieve the ship node
let ship = scene.rootNode.childNode(withName: "sphere", recursively: true)!
// retrieve the SCNView
let scnView = self.view as! SCNView
// set the scene to the view
scnView.scene = scene
ship.physicsBody?.applyForce(SCNVector3(x: 100,y: 0, z: 0), asImpulse: false)
// allows the user to manipulate the camera
scnView.allowsCameraControl = true
// show statistics such as fps and timing information
scnView.showsStatistics = true
// configure the view
scnView.backgroundColor = UIColor.black
// add a tap gesture recognizer
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
scnView.addGestureRecognizer(tapGesture)
}
the scn file
simulator
Set the restitution property of the physicsbody.
https://developer.apple.com/documentation/scenekit/scnphysicsbody/1514740-restitution
“This property simulates the “bounciness” of a body. A restitution of 1.0 means that the body loses no energy in a collision—for example, a ball dropped onto a flat surface will bounce back to the height it fell from. A restitution of 0.0 means the body does not bounce after a collision. A restitution of greater than 1.0 causes the body to gain energy in collisions. The default restitution is 0.5.”
Additionally you may want to reduce the .friction and .rollingFriction properties.

SceneKit - adding SCNNode on another SCNNode

I'm trying to put sphere on top of the box, but the position is strange: Ball is half hidden inside box. I tried to change box and sphere pivot, but it didn't help. Here's the code:
let cubeGeometry = SCNBox(width: 10, height: 10, length: 10,
chamferRadius: 0)
let cubeNode = SCNNode(geometry: cubeGeometry)
//cubeNode.pivot = SCNMatrix4MakeTranslation(0, 1, 0)
scene.rootNode.addChildNode(cubeNode)
let ballGeometry = SCNSphere(radius: 1)
let ballNode = SCNNode(geometry: ballGeometry)
ballNode.pivot = SCNMatrix4MakeTranslation(0.5, 0, 0.5)
ballNode.position = SCNVector3Make(0, 5, 0)
cubeNode.addChildNode(ballNode)`
and the result:
what I'm doing wrong? How to put the ball right on the top side of the box?
UPDATE: If I add Cube instead of ball, it looks good as I want
You need to translate on the Y-axis by cube-height/2 + sphere-radius. Thus you should have:
ballNode.position = SCNVector3Make(0, 6, 0)
Here is the screenshot:
The relevant complete code:
override func viewDidLoad() {
super.viewDidLoad()
// create a new scene
let scene = SCNScene()
let cubeGeometry = SCNBox(width: 10, height: 10, length: 10,
chamferRadius: 0)
cubeGeometry.firstMaterial?.diffuse.contents = UIColor.yellow
let cubeNode = SCNNode(geometry: cubeGeometry)
scene.rootNode.addChildNode(cubeNode)
let ballGeometry = SCNSphere(radius: 1)
ballGeometry.firstMaterial?.diffuse.contents = UIColor.green
let ballNode = SCNNode(geometry: ballGeometry)
ballNode.position = SCNVector3Make(0, 6, 0)
cubeNode.addChildNode(ballNode)
// 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
// show statistics such as fps and timing information
scnView.showsStatistics = true
// configure the view
scnView.backgroundColor = UIColor.gray
}
Update : Why radius and not radius / 2
See this screenshot from the Scene Editor in Xcode. The original position of cube is (0, 0, 0) and so is that of the ball; hence the ball needs to be moved by r and not r / 2; with r / 2 the lower section of the ball with height r/2 will be still inside the cube. You can just add a cube and sphere as in the scene below in the editor and that should help clarify.
Everything is running normally.
You added the ball node to the cube node. So the origin of the ball node is on the center of the cube.
Then you change the position of the ball to half the size of the cube on the y axis. So basically it pops off and you see just the half of the ball (its radius is 1).
So you have to add again half the size of the ball to put it just on the top of the cube :
ballNode.position = SCNVector3Make(0, 5.5, 0)

SceneKit Basics: move node

I have a simple scene with a centered object and the camera positioned on a virtual sphere around the center (inspired from Rotate SCNCamera node looking at an object around an imaginary sphere).
Now I want to be able to move the object with a two finger pan gesture. My code works well as long as I don't rotate the camera first. If I first rotate the camera by 180° (so that the camera looks at the object from behind), then the movement is in the wrong direction (which makes sense to me). It gets worse with other camera positions. However, I cannot find how I must convert or project the x/y translations from the recognizer to the plane parallel to the camera. The object should move under the fingers..
This is the setup of my scene:
let scene = SCNScene()
//add camera
let camera = SCNCamera()
camera.usesOrthographicProjection = true
camera.orthographicScale = 30
camera.zNear = 1.0
camera.zFar = 1000
cameraNode = SCNNode()
cameraNode.camera = camera
cameraNode.position = SCNVector3(x: 0.0, y: 0.0, z: 50.0)
cameraOrbit = SCNNode()
cameraOrbit.addChildNode(cameraNode)
scene.rootNode.addChildNode(cameraOrbit)
boxNode.geometry = SCNBox(width: 10, height: 40, length: 10, chamferRadius: 0.2)
scene.rootNode.addChildNode(boxNode)
let panMoveGestureRecognizer = UIPanGestureRecognizer(target: self, action:#selector(panMoveGesture))
panMoveGestureRecognizer.minimumNumberOfTouches = 2
sceneView.addGestureRecognizer(panMoveGestureRecognizer)
And the gesture recognizers code:
func panMoveGesture(recognizer: UIPanGestureRecognizer) {
//move
let translation = recognizer.translation(in: self.view.superview)
var newXMovement = -Float(translation.x) * 0.1
var newYMovement = Float(translation.y) * 0.1
switch recognizer.state {
case .began:
currentXMovement = cameraOrbit.position.x
currentYMovement = cameraOrbit.position.y
case .changed:
newXMovement += currentXMovement
newYMovement += currentYMovement
cameraOrbit.position.x = newXMovement
cameraOrbit.position.y = newYMovement
case .ended:
currentXMovement = newXMovement
currentYMovement = newYMovement
default: break
}
}
Please help ;-)

Can you change the properties of scnView.autoenablesDefaultLighting?

I need lights to stay "stationary" in my scene. The best lighting method I've found so far is to actually to use scnView.autoenablesDefaultLighting = true however, I can't figure out if there's any way to control some of the attributes. The intensity of the light is a BIT too bright, the location of the light is a BIT different than where I'd like it to be, those kinds of properties.
I've tried using all sorts of other lights, coding them individually BUT since they add to the scene as nodes, the lights (in those cases) themselves will move when I set scnView.allowsCameraControl = true. The default lighting is the only one that will remain "stationary" once the user begins to move the camera around. Can you access/control the properties of the default lighting?
Forget allowsCameraControl and default cameras and lights if you want control of your scene.
let sceneView = SCNView()
let cameraNode = SCNNode() // the camera
var baseNode = SCNNode() // the basic model-root
let keyLight = SCNLight() ; let keyLightNode = SCNNode()
let ambientLight = SCNLight() ; let ambientLightNode = SCNNode()
func sceneSetup() {
let scene = SCNScene()
// add to an SCNView
sceneView.scene = scene
// add the container node containing all model elements
sceneView.scene!.rootNode.addChildNode(baseNode)
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3Make(0, 0, 50)
scene.rootNode.addChildNode(cameraNode)
keyLight.type = SCNLightTypeOmni
keyLightNode.light = keyLight
keyLightNode.position = SCNVector3(x: 10, y: 10, z: 5)
cameraNode.addChildNode(keyLightNode)
ambientLight.type = SCNLightTypeAmbient
let shade: CGFloat = 0.40
ambientLight.color = UIColor(red: shade, green: shade, blue: shade, alpha: 1.0)
ambientLightNode.light = ambientLight
cameraNode.addChildNode(ambientLightNode)
// view the scene through your camera
sceneView.pointOfView = cameraNode
// add gesture recognizers here
}
Move or rotate cameraNode to effect motion in view. Or, move or rotate baseNode. Either way, your light stay fixed relative to the camera.
If you want your lights fixed relative to the model, make them children of baseNode instead of the camera.
If someone who wondering how to setup entire scene with new Scenekit integration with swift ui go though this. This work fine.
struct TestControllerNew: View {
let sceneView = SCNView()
let cameraNode = SCNNode()
var baseNode = SCNNode()
let id = "D69A09F8-EA80-4231-AD35-4A9908B4343C"
var scene = SCNScene()
var body: some View {
SceneView(
scene: sceneSetup(),
pointOfView: cameraNode,
options: [
.autoenablesDefaultLighting
]
)
}
func getTermsOfUseURL() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return paths[0].appendingPathComponent("\(id).usdz")
}
func sceneSetup() -> SCNScene {
let scene = try! SCNScene(url: getTermsOfUseURL())
// add to an SCNView
sceneView.scene = scene
// add the container node containing all model elements
sceneView.scene!.rootNode.addChildNode(baseNode)
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3Make(0, 1, 10)
scene.rootNode.addChildNode(cameraNode)
// view the scene through your camera
sceneView.pointOfView = cameraNode
// add gesture recognizers here
return scene
}
}
Here the USDZ file get from document directory as a URL, you can use name instead of that.

Resources