This function is supposed to add a 3D model to screen in a confined space:
func addCharacter(gridPosition: SCNVector3, levelNode: SCNNode){
let carScene = SCNScene(named: "assets.scnassets/Models/cube.dae")
// Create a material using the model_texture.tga image
let carMaterial = SCNMaterial()
carMaterial.diffuse.contents = UIImage(named: "assets.scnassets/Textures/model_texture.tga")
carMaterial.locksAmbientWithDiffuse = false
// Create a clone of the Car node of the carScene - you need a clone because you need to add many cars
let carNode = carScene!.rootNode.childNodeWithName("Cube", recursively: false)!.clone() as SCNNode
carNode.name = "Cube"
carNode.position = gridPosition
// Set the material
carNode.geometry!.firstMaterial = carMaterial
// Create a physicsbody for collision detection
let carPhysicsBodyShape = SCNPhysicsShape(geometry: SCNBox(width: 0.30, height: 0.20, length: 0.16, chamferRadius: 0.0), options: nil)
carNode.physicsBody = SCNPhysicsBody(type: SCNPhysicsBodyType.Kinematic, shape: carPhysicsBodyShape)
carNode.physicsBody!.categoryBitMask = PhysicsCategory.Car
carNode.physicsBody!.collisionBitMask = PhysicsCategory.Player
levelNode.addChildNode(carNode)
}
The code works if I change the dimensions of the dae file before adding it to Xcode, and by dimensions I mean the Bounding Box Size property of Xcode. I wanted to know if It was possible to change this bounding box size directly from Xcode. It does not seem possible from the UI. Is it possible with code? Or, even better, scale down the object maintaining proportions to a size for the XYZ between the range 0.3 -> 0.7? At the moment my objects show a box size of over 45 for XYZ. Furthermore, If I was to use a .scn file instead of a .dae in the code above, would that still work?
EDIT:
If I change the size via code, would it have an impact on efficiency? I notice that for larger .dae models the fps drops from 60 to 30 and the game slows down.
Changing carNode's scale property will reduce the apparent size of the car.
However, I think you'll be ahead to load your .DAE into the Xcode scene editor. That will allow you to scale it down ahead of time (in the Node Inspector, option-command-3). You can also, if you want, add your texture. Then save it as a .SCN file, which is compressed and should load faster.
Changing the scaling, either in code or in the Xcode scene editor, won't affect efficiency. Reducing the complexity of the car (the number of polygons/vertices) will speed things up, though.
Consider creating SCNLevelOfDetail instances for your car node. That will cause the renderer to use substitute, lower resolution geometry when the node is far from the camera. The WWDC 2014 slide deck demonstrates this, slide 38, AAPLSlideLOD.m.
Related
I am working on the game with spaceship flying around earth and destroying objects. I wanted to add some reaction when object hit the spaceship but the contact is detected before it "hit" visual model of the spaceship. I found out that the physicsbody of the node is bigger than visual model. What should I do to set it on the same size?
Here's some more info:
I created model built of many basic shapes/nodes in SceneKit editor in Xcode
I set physics body in the editor with options: dynamic type, default shape, bounding box, scale: 1.
There's part of my code that do something with the ship node:
shipNode = scene.rootNode.childNode(withName: "ship", recursively: true)!
shipNode.physicsBody!.physicsShape = SCNPhysicsShape(node: shipNode, options: nil)
shipNode.physicsBody!.categoryBitMask = 4
screen of UI of my game, with physicsshapes visible
Just to close this out, we should probably mark it as solved;
Before, the spaceship was too big so I changed scale of "ship reference" in main scene. In this situation textures got smaller but physicsbody didn't. After your tip, I changed the size of the ship in original .scn file and it solved all problems
I'm trying to create an application in which the user stacks different geometric shapes. In a .scn file, which is loaded inside the ARSCNView, I insert a static plane and then at each tap of the user, the app inserts a dynamic SCNNode.
The first node is being inserted a few inches above the plane, to replicate a falling object. And then, each other node is being dropped on top of another.
This is the main idea of the application; the problem appears after adding 3 or 4 nodes, they appear to slide of each other, almost jiggle, and the whole structure collapses.
This is my node I'm inserting:
let dimension: CGFloat = 0.075
let cube = SCNBox(width: dimension, height: dimension, length: dimension, chamferRadius: 0.0)
let node = SCNNode(geometry: cube)
node.physicsBody = SCNPhysicsBody(type: SCNPhysicsBodyType.dynamic, shape: nil)
node.physicsBody?.mass = 2.0
node.physicsBody?.friction = 1.0
node.physicsBody?.restitution = 0.01
node.physicsBody?.damping = 0.0
node.physicsBody?.angularDamping = 0.0
node.physicsBody?.rollingFriction = 1.0
node.physicsBody?.allowsResting = true
let insertionYOffset = 0.3
node.position = SCNVector3(hitResult.worldCoordinates.x, hitResult.worldCoordinates.y + Float(insertionYOffset), hitResult.worldCoordinates.z)
I've tried to play with the values and these are the best ones, but they aren't enough to create a stable structure of blocks.
As a requirement, I need to keep the blocks dynamic, they need to be affected by gravity, wind, etc.
The problem most likely has something to do with a factor called Dynamic which will allow it to continuously move or may have to do with the objects hitting each other to fix that problem all you have to do is change the collision mask to two different numbers.
I see two points that may be the flaws of your simulation:
Your objects are stacked. When two objects collides there's a small amount of error added in order to keep the physics in a stable phase and prevent weird behaviors. So when you stack objects you add error amounts and the simulation becomes inaccurate. I suggest making objects static over time or depending of the position, or reduce gravity by a bit.
Most of the physics engines works better with objects sized with the unit size (most of the cases, it's 1 like in Box2D or PhysX). Objects that are very small or very big tend to act less natural. 0.075 sounds a bit small for a simulation. I don't see a obvious solution, maybe look for a way to change the reference size.
I am trying to work with a dae model, but somehow can't modify the position or scale it at all. Other models work fine though. Can these dae models be locked somehow?
let objScene = SCNScene(named: "art.scnassets/test.dae")!
self.objNode = objScene.rootNode.childNode(withName: "test", recursively: true)
let action = SCNAction.scale(by: 0.25, duration: 1.0)
self.objNode.runAction(action)
self.objNode?.position = SCNVector3(0.0,0.0,-2.0)
self.arSceneView.scene.rootNode.addChildNode(self.objNode!)
This can happen if your Collada file contains animations that target the "test" node.
If you programmatically set the node's position or scale, and then an animation targeting the node's transform is evaluated it will override everything giving the impression that the node can't be modified.
You can always nest a node targeted by an animation under a dummy parent node, that you can freely translate, rotate or scale.
I'm currently trying to combine the following sources:
Apples SceneKit Vehicle Demo, Resp. its Swift version,
ARKit by example, and resp. its Swift version.
Each project on its own works like a charm (although I changed the vehicle demo so that the car can be controlled by on-screen buttons).
Now, when I try to combine both projects to create an augmented reality racing game, I run into problems regarding the size of the .dae model of the car: it's too big.
I can scale the model using the (chassis) nodes .scale property, but as soon as I add the SCNPhysicsVehicle properties and behaviour, the car gets reset(?) to its original size. I tried to scale the model in Xcode (open dae file, change scale), but its bounding box remains the same - that tells me that the rescaling didn't work properly.
Any hints?
1)you can scale the dae models by art.scnassets directly.
art.scnassets -> car.dae -> node inspector -> transforms -> scale the object
2) can scale 3dmodel by SCNAction
let scene = SCNScene(named: "art.scnassets/cup.dae")!
let node = scene.rootNode.childNode(withName: "cup", recursively: true)!
let action = SCNAction.scale(by: sender.scale, duration: 1.0)
node.runAction(action)
What I like to do is use Blender or some other 3d modeling program to resize your dae model to work in meters. Everything in ARKit is based on meters, so by sticking to the same metric you can get all your models to play well together without having to guess what the scale factor needs to be.
I'm not sure how to fix the model directly in Xcode. However, you can fix it in blender. Start by importing the object into blender. Select the object and observe it's dimensions. Scale the object to the desired dimensions and apply them by hittin Ctrl + A, and selecting scale. Alternatively, from the object menu, you can select Apply -> Scale. Now you can export your model with the corrected size.
I am currently trying to set up a rotating ball in scene kit. I have created the ball and applied a texture to it.
ballMaterial.diffuse.contents = UIImage(named: ballTexture)
ballMaterial.doubleSided = true
ballGeometry.materials = [ballMaterial]
The current ballTexture is a semi-transparent texture as I am hoping to see the back face roll around.
However I get some strange culling where only half of the back facing polygons are shown even though the doubleSided property is set to true.
Any help would be appreciated, thanks.
This happens because the effects of transparency are draw-order dependent. SceneKit doesn't know to draw the back-facing polygons of the sphere before the front-facing ones. (In fact, it can't really do that without reorganizing the vertex buffers on the GPU for every frame, which would be a huge drag on render performance.)
The vertex layout for an SCNSphere has it set up like the lat/long grid on a globe: the triangles render in order along the meridians from 0° to 360°, so depending on how the sphere is oriented with respect to the camera, some of the faces on the far side of the sphere will render before the nearer ones.
To fix this, you need to force the rendering order — either directly, or through the depth buffer. Here's one way to do that, using a separate material for the inside surface to illustrate the difference.
// add two balls, one a child of the other
let node = SCNNode(geometry: SCNSphere(radius: 1))
let node2 = SCNNode(geometry: SCNSphere(radius: 1))
scene.rootNode.addChildNode(node)
node.addChildNode(node2)
// cull back-facing polygons on the first ball
// so we only see the outside
let mat1 = node.geometry!.firstMaterial!
mat1.cullMode = .Back
mat1.transparent.contents = bwCheckers
// my "bwCheckers" uses black for transparent, white for opaque
mat1.transparencyMode = .RGBZero
// cull front-facing polygons on the second ball
// so we only see the inside
let mat2 = node2.geometry!.firstMaterial!
mat2.cullMode = .Front
mat2.diffuse.contents = rgCheckers
// sphere normals face outward, so to make the inside respond
// to lighting, we need to invert them
let shader = "_geometry.normal *= -1.0;"
mat2.shaderModifiers = [SCNShaderModifierEntryPointGeometry: shader]
(The shader modifier bit at the end isn't required — it just makes the inside material get diffuse shading. You could just as well use a material property that doesn't involve normals or lighting, like emission, depending on the look you want.)
You can also do this using a single node with a double-sided material by disabling writesToDepthBuffer, but that could also lead to undesirable interactions with the rest of your scene content — you might also need to mess with renderingOrder in that case.
macOS 10.13 and iOS 11 added SCNTransparencyMode.dualLayer which as far as I can tell doesn't even require setting isDoubleSided to true (the documentation doesn't provide any information at all). So a simple solution that's working for me would be:
ballMaterial.diffuse.contents = UIImage(named: ballTexture)
ballMaterial.transparencyMode = .dualLayer
ballGeometry.materials = [ballMaterial]