The documentation for SceneKit's collisionBitMask property of SCNPhysicsBody states the following:
When two physics bodies contact each other, a collision may occur.
SceneKit compares the body’s collision mask to the other body’s
category mask by performing a bitwise AND operation. If the result is
a nonzero value, then the body is affected by the collision. Each body
independently chooses whether it wants to be affected by the other
body.
That last line indicates that if I have two objects, I can set it up so that when they collide, only one of them should be affected by the collision.
let CollisionCategoryPlane = 1 << 0
let CollisionCategorySphere1 = 1 << 1
let CollisionCategorySphere2 = 1 << 2
let plane = SCNNode(geometry: SCNPlane(width: 10, height: 10))
plane.position = SCNVector3(x: 0, y: -10, z: 0)
plane.eulerAngles = SCNVector3(x: Float(-M_PI/2), y: 0, z: 0)
plane.physicsBody = SCNPhysicsBody.staticBody()
plane.physicsBody?.categoryBitMask = CollisionCategoryPlane
plane.physicsBody?.collisionBitMask = CollisionCategorySphere1 | CollisionCategorySphere2
// the plane should be affected by collisions with both spheres (but the plane is static so it doesn't matter)
scene.rootNode.addChildNode(plane)
let sphere1 = SCNNode(geometry: SCNSphere(radius: 1))
sphere1.physicsBody = SCNPhysicsBody.dynamicBody()
sphere1.physicsBody?.categoryBitMask = CollisionCategorySphere1
sphere1.physicsBody?.collisionBitMask = CollisionCategoryPlane
// sphere1 should only be affected by collisions with the plane, not with sphere2
scene.rootNode.addChildNode(sphere1)
let sphere2 = SCNNode(geometry: SCNSphere(radius: 1))
sphere2.position = SCNVector3(x: 1, y: 10, z: 0)
sphere2.physicsBody = SCNPhysicsBody.dynamicBody()
sphere2.physicsBody?.categoryBitMask = CollisionCategorySphere2
sphere2.physicsBody?.collisionBitMask = CollisionCategoryPlane | CollisionCategorySphere1
// sphere2 should be affected by collisions with the plane and sphere1
scene.rootNode.addChildNode(sphere2)
Sphere1 should fall onto the plane, then sphere2 should fall onto sphere1 and bounce off, and sphere1 should be unaffected by the collision with sphere2. However, the observed behaviour is both spheres falling onto the plane and coming to rest inside each other - no collision event between the two spheres is registered.
What is going on here?
On related notes, some even stranger behaviour is observed when I make a couple small modifications to the above code.
If remove the line that defines the plane's collsionBitMask, leaving it as the default SCNPhysicsCollisionCategoryAll, sphere1 no longer collides with the plane.
If I move the lines that define the objects' physics bodies, categoryBitMasks, and collisionBiMasks to after the objects have each been added to the the scene, all the objects will collide with every other object. Even if I set every collisionBitMask to zero.
Related
I have an AR app in which I have calculated current sun position from the user. I reduced this position to distance of 10,000 meters, so it kinda "stays" in place when I move inside the scene. Now I would like to cast shadows from this node on other nodes inside my scene. I tried few types of lightning, but none was successful. Some didn't drop shadow at all, others behave strange. What would be the best method to create a light source from such distant node to cast shadows on invisible floor node? Should I also add ambient light to the scene? Can it be added to camera node, or should be somewhere else?
Use directional light for Sun simulation (Sun has parallel rays that for us are primary rays from very distant light source) and use ambient light for simulating fake secondary rays (in case you do not use Global Illumination).
// DIRECTIONAL LIGHT for `primary light rays` simulation
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light!.type = .directional
lightNode.light!.castsShadow = true
lightNode.light!.shadowMode = .deferred
lightNode.light!.categoryBitMask = -1
lightNode.light!.automaticallyAdjustsShadowProjection = true
//lightNode.light!.maximumShadowDistance = 11000
lightNode.position = SCNVector3(x: 0, y: -5000, z: 0)
lightNode.rotation = SCNVector4(x: -1, y: 0, z: 0, w: .pi/2)
scene.rootNode.addChildNode(lightNode)
Tip: The position of directional light could be any (in the following code it is even under the plane y:-5000), the most important thing is direction of directional light!
Also directional light has no falloff parameter or, so called, quadratic light decay.
// AMBIENT LIGHT for `fake secondary light rays` simulation
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = .ambient
ambientLightNode.light!.intensity = 1000
ambientLightNode.light!.color = NSColor.white
scene.rootNode.addChildNode(ambientLightNode)
Do not mistakenly use this Boolean value:
lightNode.castsShadow = true
instead of this one:
lightNode.light!.castsShadow = true
The Boolean value lightNode.castsShadow determines whether SceneKit renders the node’s contents into shadow maps.
And here's a screenshot if you wish to enable shadows in manually created directional light:
Sometimes, you get some benefits if you attach a light to the camera. In that case object's surfaces with normals parallel to light's rays are lit.
I have an ARKit scene with an invisible SCNPlane:
plane.geometry?.firstMaterial?.colorBufferWriteMask = []
This plane is placed on the ground and is used to render deferred shadows from other objects placed in the scene.
I want to render another SCNPlane which should be on the same level as the invisible plane (same Z-coordinate). The problem is, that every time the new object is under the invisible plane, it is not rendered at all.
Is there any way to render the object when it is under the invisible plane?
You can achieve it using the following lines of code:
shadowsPlane.geometry?.materials.first?.writesToDepthBuffer = true
shadowsPlane.geometry?.materials.first?.readsFromDepthBuffer = true
Choose one of two instance properties for .colorBufferWriteMask:
shadowsPlane.geometry?.materials.first?.colorBufferWriteMask = []
Set a rendering order for your objects like:
shadowsPlane.renderingOrder = -1 // the nearest layer
And, of course, use an appropriate .lightingModel instance property:
shadowsPlane.geometry?.materials.first?.lightingModel = .constant
Remember, there will be some tiny air gap between two planes:
shadowsPlane.position = SCNVector3(x: 0, y: 0, z: 0)
floorPlane.position = SCNVector3(x: 0, y: -0.01, z: 0)
Ok, what I am trying to do is create physics body/colliding boundaries for my character, my SCNNode, in my SceneKit game Im building with ARKit. This is so my node cannot move out of the user's vision/go so far away that it isn't visible as it is currently doing. My SCNNode is moved by user input, so I need to make "world boundaries" while ARKit still doesn't have vertical wall detection
I know you can place an object some set distance ahead of you in the real world as stated here Understand coordinate spaces in ARKit
and I have done that with this, just making a box with physics body here -
let box = SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0) //change to be VERY TALL - need to make it a giant room
node.physicsBody = SCNPhysicsBody(type: SCNPhysicsBodyType.static, shape: nil)
box.firstMaterial?.diffuse.contents = UIColor.red
box.firstMaterial?.isDoubleSided = true
node = SCNNode(geometry: box)
node.position = SCNVector3(view.pointOfView.simdWorldFront + float3(0, 0, -0.5)) //random distance ahead
And this works, and I could add it as a child node of camera so it moves as user moves, but I don't think Im doing this correctly.
Essentially I need 4 walls to box the user/SCNNode in (corral the character) that are infinitely high, and at the VERY edge of the horizontal plane that the user can see. Really I don't know what this distance should be in the x plane:
+ float3(0, 0, -0.5)
How would you create AR boundaries like this?
So,
I have the exact position I want to place the node at. If I test things with a sphere geometry I can place spheres in the world by telling the node:
node.simdPosition = position
(I provide the "position" as an input to the function).
That successfully places the object in the world exactly where I want it to go.
What I really want to do is placing a plane:
let plane = SCNPlane(width: 0.2, height: 0.3)
plane.cornerRadius = plane.width / 10
plane.firstMaterial?.diffuse.contents = UIColor.red
plane.firstMaterial?.specular.contents = UIColor.white
let node = SCNNode(geometry: plane)
Then telling it to be placed at the "position":
node.simdPosition = position
All this works with the plane as well. What I have problems with is the angle:
I want to tell the plane's node to be placed with a given "angle" (around Y) offset to the camera. I tried this but it's not working:
node.rotation = SCNVector4Make(0, 1, 0, currentFrame.camera.eulerAngles.z - angle)
So then, the question is, how can a node be placed at a certain position and at the moment it gets placed in the world, also have a certain Y angle offset from the perpendicular to the camera?
I was using the wrong Euler angle... (z)
This made it work:
node.eulerAngles = SCNVector3Make(0, cameraEulerAngles.y - Float(0.7), 0)
In SceneKit, it is possible to make a SCNPhysicsBody be unaffected by a SCNPhysicsField by giving them categoryBitMasks that when compared using bitwise AND, result in a value of zero.
Bodies who's category bit mask produce a non zero value when compared with that of the field are still affected by it.
Is it possible to do the same for the particles in a SCNParticleSystem, so that the particles will be unaffected by a SCNPhysicsField (edit: and still have the particles affected by other physics fields), but certain physics bodies or even other particle systems are affected by it?
To clarify:
What I want: A SCNParticleSystem that is affected by a SCNPhysicsField (specifically a linear gravity field). This can be done by setting the particle system's affectedByPhysicsFields property to true. However, this will make the particle system affected by all physics fields in the scene. I have another field (also a linear gravity field) that I want to only affect a SCNPhysicsBody which I don't want to be affected by the particle systems's field.
As it stands both the particle system and the physics body will be affected by both fields. Unless I give them categoryBitMasks to tell them which fields to interact with. Except particle systems don't have categoryBitMasks, and if a physics field's categoryBitMask is anything other than the default, it will be ignored by any particles.
So I can set it so that one of the fields only affects the body by giving them the same category. But I can't do the same for the particles. Is there any way to accomplish this? (Using the world gravity as one of the fields doesn't work because it doesn't have a categoryBitMask either.)
I've actually figured out a workaround (for now) by just using the particle system's acceleration property instead of a physics field. But I curious to see if it actually is possible to do it using a physics field.
edit: As requested here is some sample code. This code results in a sphere affected by two physics fields, and a particle system affected by one physics field. The comments explain the problem.
let PhysicsCategorySphere = 1 << 0
let PhysicsCategoryParticles = 1 << 1
scene.physicsWorld.gravity = SCNVector3(x: 0, y: 0, z: 0)
let sphere = SCNNode(geometry: SCNSphere(radius: 1))
sphere.physicsBody = SCNPhysicsBody.dynamicBody()
sphere.physicsBody?.categoryBitMask = PhysicsCategorySphere
//only physics fields with categoryBitMasks that match the sphere's can affect it
scene.rootNode.addChildNode(sphere)
let field = SCNPhysicsField.linearGravityField()
field.strength = 9.8
field.direction = SCNVector3(x: 0, y: 1, z: 0)
field.categoryBitMask = PhysicsCategorySphere
//only things with categories that match the field's will be affected by it
let fieldNode = SCNNode()
fieldNode.physicsField = field
scene.rootNode.addChildNode(fieldNode)
let particleSystem = SCNParticleSystem(named: "particles", inDirectory: "")
particleSystem.affectedByPhysicsFields = true
//there is no way that I know of to give the particle system a physics category
let particleNode = SCNNode()
particleNode.addParticleSystem(particleSystem)
scene.rootNode.addChildNode(particleNode)
let particleField = SCNPhysicsField.linearGravityField()
particleField.strength = 20
particleField.direction = SCNVector3(x: 0, y: 0, z: 1)
//particleField.categoryBitMask = PhysicsCategoryParticles
//if the field is given a category, it will no longer affect the particles
//the default is to match all categories, thus the particle field will also affect the sphere
let particleFieldNode = SCNNode()
particleFieldNode.physicsField = particleField
scene.rootNode.addChildNode(particleFieldNode)
Ok, I don't know how I overlooked this, but the answer is right in the documentation for SCNPhysicsField's categoryBitMask property.
To determine whether a field affects the particles spawned by an
SCNParticleSystem object, SceneKit performs the same check using the
categoryBitMask property of the node containing the particle system.