SceneKit – Concave collision box - ios

Im importing an object in DAE format and I'm wanting to use it as a collision object however it is concave, but I'm having trouble implementing this in code:
func addCollisionBox() {
let collisionBoxNode = SCNNode()
let collisionBox = importedCollisionBox.rootNode.childNodeWithName("pCube2", recursively: false)
collisionBoxNode.addChildNode(collisionBox!)
collisionBoxNode.position = SCNVector3Make(0, 0, 0)
collisionBoxNode.scale = SCNVector3Make(30, 20, 50)
//collisionBoxNode.physicsBody = SCNPhysicsBody.staticBody()
collisionBoxNode.physicsBody = SCNPhysicsBody.bodyWithType(SCNPhysicsShapeTypeConcavePolyhedron, shape: collisionBox) // This line errors
collisionBoxNode.physicsBody?.restitution = 0.8
collisionBoxNode.name = "collisionBox"
theScene.rootNode.addChildNode(collisionBoxNode)
}
Cant get the line of code to work which has this in it SCNPhysicsShapeTypeConcavePolyhedron as commented in the code.

A "concave" physics body, per SCNPhysicsShapeTypeConcavePolyhedron, must still be "solid" in some sense, and the implied behavior for that collision type is to keep other bodies outside the "solid" form of the body. As such, your cube still has a "solid" interior even if you set its shape type to concave.
(What the concave shape type does do for you is let you make a solid shape that is non-convex — for example, a cube where one of the faces bows inward to produce a bowl shape.)
If you want to confine other bodies to a box-shaped volume, you'll need to create walls: that is, put a few physics bodies around the volume you want to enclose. Here's a quick utility function for something like that:
func wallsForBox(box: SCNBox, thickness: CGFloat) -> SCNNode {
func physicsWall(width: CGFloat, height: CGFloat, length: CGFloat) -> SCNNode {
let node = SCNNode(geometry: SCNBox(width: width, height: height, length: length, chamferRadius: 0))
node.physicsBody = .staticBody()
return node
}
let parent = SCNNode()
let leftWall = physicsWall(thickness, height: box.height, length: box.length)
leftWall.position.x = -box.width / 2
parent.addChildNode(leftWall)
let rightWall = physicsWall(thickness, height: box.height, length: box.length)
rightWall.position.x = box.width / 2
parent.addChildNode(rightWall)
let frontWall = physicsWall(box.width, height: box.height, length: thickness)
frontWall.position.z = box.length / 2
parent.addChildNode(frontWall)
let backWall = physicsWall(box.width, height: box.height, length: thickness)
backWall.position.z = -box.length / 2
parent.addChildNode(backWall)
let topWall = physicsWall(box.width, height: thickness, length: box.length)
topWall.position.y = box.height / 2
parent.addChildNode(topWall)
let bottomWall = physicsWall(box.width, height: thickness, length: box.length)
bottomWall.position.y = -box.height / 2
parent.addChildNode(bottomWall)
return parent
}
This creates a node hierarchy containing visible walls as separate nodes, but you could easily modify it to create invisible walls and/or a single node with a compound physics body. (See SCNPhysicsShape.)

Related

How to position walls at the edges of the screen using a fixed-position camera (SceneKit)

I have a SceneKit scene in which the camera is stationary, and is positioned like this: cameraNode.position = SCNVector3(0.0, 0.0, 100.0). Other than that, the camera has the default configuration.
In the scene is a single, spherical SCNNode with a physics body.
Below the sphere is a flat plane, with a physics body, on which the sphere rolls around. The plane is positioned in the center of the scene, at SCNVector3(0.0, 0.0, 0.0).
What I need is for the scene to be surrounded by invisible "walls" that are positioned exactly at the edges of the screen. The sphere should bounce off these static physics bodies so it never leaves the screen.
I've tried placing one of these "walls" (an SCNNode with an SCNBox geometry) using the actual screen dimensions, but the positioning is incorrect; the node is apparently off screen. This is presumably because SceneKit coordinates are in meters, not pixels or whatever.
Question: How can I figure out the positioning of the "walls" so that they are fixed to the edges of the screen?
Thanks for your help!
Use the SCNSceneRenderer's unprojectPoint(_:) method to convert left and right edges of the screen to 3D space coordinates and add two planes in those coordinates.
let leftPoint = SCNVector3(scnView.bounds.minX, scnView.bounds.midY, 1.0)
let righPoint = SCNVector3(scnView.bounds.maxX, scnView.bounds.midY, 1.0)
let leftPointCoords = scnView.unprojectPoint(leftPoint)
let rightPointCoords = scnView.unprojectPoint(righPoint)
let rightPlane = SCNPlane(width: 100.0, height: 100.0)
let leftPlane = SCNPlane(width: 100.0, height: 100.0)
let rightPlaneNode = SCNNode(geometry: rightPlane)
rightPlaneNode.eulerAngles = .init([0.0, .pi / 2, 0.0])
rightPlaneNode.physicsBody = .init(type: .static, shape: nil)
let leftPlaneNode = SCNNode(geometry: leftPlane)
leftPlaneNode.physicsBody = .init(type: .static, shape: nil)
leftPlaneNode.eulerAngles = .init([0.0, .pi / 2, 0.0])
rightPlaneNode.position = rightPointCoords
leftPlaneNode.position = leftPointCoords
scene.rootNode.addChildNode(rightPlaneNode)
So, obviously there's a mathematical solution to this problem, and that's the best way to do this. Unfortunately, I'm not very good at math, so I had to come up with another solution.
First, I create the wall to be located at the top edge of the screen. It will begin at the center of the scene:
let topEdge = SCNNode()
let topEdgeGeo = SCNBox(width: MainData.screenWidth, height: 5.0, length: 5.0, chamferRadius: 0.0)
topEdge.geometry = topEdgeGeo
topEdge.physicsBody = SCNPhysicsBody(type: SCNPhysicsBodyType.kinematic, shape: SCNPhysicsShape.init(node: topEdge))
topEdge.physicsBody?.categoryBitMask = CollisionTypes.kinematicObjects.rawValue
topEdge.physicsBody?.collisionBitMask = CollisionTypes.dynamicObjects.rawValue
topEdge.physicsBody?.isAffectedByGravity = false
topEdge.physicsBody?.allowsResting = true
topEdge.physicsBody?.friction = 0.0
topEdge.position = SCNVector3(0.0, 0.0, 2.5)
scnView.scene?.rootNode.addChildNode(topEdge)
I then repeatedly reposition the wall a little bit farther up the y axis until it's no longer within the camera's viewport:
var topWallIsOnScreen: Bool = scnView.isNode(topEdge, insideFrustumOf: scnView.pointOfView!)
while topWallIsOnScreen {
topEdge.position.y += 0.001
topWallIsOnScreen = scnView.isNode(topEdge, insideFrustumOf: scnView.pointOfView!)
}
The end result is that the wall is positioned at the top edge of the screen. I was concerned about performance, but it seems to work just fine.

Need starting and ending position of MeshResource/Box in RealityKit

let material = SimpleMaterial.init(color: .red,roughness: 1,isMetallic: false)
let doorBox = MeshResource.generateBox(width: 0.02,height: 1, depth: 0.5)
let doorEntity = ModelEntity(mesh: doorBox, materials: [material])
let anchor = ARAnchorEntity()
anchor.addChild(doorEntity)
In RealityKit, I am having box which is MeshResource, Box looks like a line. I have added this box in ARView, and have set realtime camera position. In one scenario I want to know Box/Line’s starting and ending position.
Lets say box with entity has middle/current position (0.1,0.23,-1.3) then what will be box’s left and right position ? Anchor with box is keep changing it's position with camera movement.
Thanks in advance.
Check explanation with the image
You can use this extension.
extension Entity {
func getDistancedPosition(x: Float, y: Float, z: Float) -> SIMD3<Float> {
let referenceNodeTransform = transform.matrix
var translationMatrix = matrix_identity_float4x4
translationMatrix.columns.3.x = x
translationMatrix.columns.3.y = y
translationMatrix.columns.3.z = z
let updatedTransform = matrix_multiply(referenceNodeTransform,
translationMatrix)
return .init(updatedTransform.columns.3.x,
updatedTransform.columns.3.y,
updatedTransform.columns.3.z)
}
}
To get left and right for your box, Use below code:
let side1Position = door.getDistancedPosition(x: 0, y: 0, z: self.viewModel.doorDepth/2)
let side2Position = door.getDistancedPosition(x: 0, y: 0, z: -(self.viewModel.doorDepth/2))
To make the box look like the line you must have used depth. If not then you can change the parameter accordingly. e.g. door.getDistancedPosition(x: -0.1, y: 0, z: 0)
You can also refer to this question and its accepted answer:
Position a SceneKit object in front of SCNCamera's current orientation

How to use SCNTechnique to create a "masked" portal effect in SceneKit / ARKit?

I'm trying to wrap my head around how to use SCNTechnique with Scenekit.
The effect I'm trying to create can easily be achieved in Unity by adding a custom shader to two materials, making objects with the "filtered" shader to only be visible when it is seen through the other shader (the portal). I've followed this tutorial to create this in Unity: https://www.youtube.com/watch?v=b9xUO0zHNXw
I'm trying to achieve the same effect in SceneKit, but I'm not sure how to apply the passes. I'm looking at SCNTechnique because it has options for stencilStates, but I'm not sure if this will work.
There are very little resources and tutorials on SCNTechnique
As mentioned in the comments above, I am not sure how to achieve an occlusion effect using Stencil Tests which as we know are fairly (and I use that term very loosely) easy to do in Unity.
Having said this, we can achieve a similar effect in Swift using transparency, and rendering order.
Rendering Order simply refers to:
The order the node’s content is drawn in relative to that of other
nodes.
Whereby SCNNodes with a larger rendering order are rendered last and vice versa.
In order to make an object virtually invisible to the naked eye we would need to set the transparency value of an SCNMaterial to a value such as 0.0000001.
So how would we go about this?
In this very basic example which is simply a portal door, and two walls we can do something like this (which is easy enough to adapt into something more substantial and aesthetically pleasing):
The example is fully commented so it should be easy to understand what we are doing.
/// Generates A Portal Door And Walls Which Can Only Be Seen From Behind e.g. When Walking Through The Portsal
///
/// - Returns: SCNNode
func portalNode() -> SCNNode{
//1. Create An SCNNode To Hold Our Portal
let portal = SCNNode()
//2. Create The Portal Door
let doorFrame = SCNNode()
//a. Create The Top Of The Door Frame
let doorFrameTop = SCNNode()
let doorFrameTopGeometry = SCNPlane(width: 0.55, height: 0.1)
doorFrameTopGeometry.firstMaterial?.diffuse.contents = UIColor.black
doorFrameTopGeometry.firstMaterial?.isDoubleSided = true
doorFrameTop.geometry = doorFrameTopGeometry
doorFrame.addChildNode(doorFrameTop)
doorFrameTop.position = SCNVector3(0, 0.45, 0)
//b. Create The Left Side Of The Door Frame
let doorFrameLeft = SCNNode()
let doorFrameLeftGeometry = SCNPlane(width: 0.1, height: 1)
doorFrameLeftGeometry.firstMaterial?.diffuse.contents = UIColor.black
doorFrameLeftGeometry.firstMaterial?.isDoubleSided = true
doorFrameLeft.geometry = doorFrameLeftGeometry
doorFrame.addChildNode(doorFrameLeft)
doorFrameLeft.position = SCNVector3(-0.25, 0, 0)
//c. Create The Right Side Of The Door Frame
let doorFrameRight = SCNNode()
let doorFrameRightGeometry = SCNPlane(width: 0.1, height: 1)
doorFrameRightGeometry.firstMaterial?.diffuse.contents = UIColor.black
doorFrameRightGeometry.firstMaterial?.isDoubleSided = true
doorFrameRight.geometry = doorFrameRightGeometry
doorFrame.addChildNode(doorFrameRight)
doorFrameRight.position = SCNVector3(0.25, 0, 0)
//d. Add The Portal Door To The Portal And Set Its Rendering Order To 200 Meaning It Will Be Rendered After Our Masks
portal.addChildNode(doorFrame)
doorFrame.renderingOrder = 200
doorFrame.position = SCNVector3(0, 0, -1)
//3. Create The Left Wall Enclosure To Hold Our Wall And The Occlusion Node
let leftWallEnclosure = SCNNode()
//a. Create The Left Wall And Set Its Rendering Order To 200 Meaning It Will Be Rendered After Our Masks
let leftWallNode = SCNNode()
let leftWallMainGeometry = SCNPlane(width: 0.5, height: 1)
leftWallNode.geometry = leftWallMainGeometry
leftWallMainGeometry.firstMaterial?.diffuse.contents = UIColor.red
leftWallMainGeometry.firstMaterial?.isDoubleSided = true
leftWallNode.renderingOrder = 200
//b. Create The Left Wall Mask And Set Its Rendering Order To 10 Meaning It Will Be Rendered Before Our Walls
let leftWallMaskNode = SCNNode()
let leftWallMaskGeometry = SCNPlane(width: 0.5, height: 1)
leftWallMaskNode.geometry = leftWallMaskGeometry
leftWallMaskGeometry.firstMaterial?.diffuse.contents = UIColor.blue
leftWallMaskGeometry.firstMaterial?.isDoubleSided = true
leftWallMaskGeometry.firstMaterial?.transparency = 0.0000001
leftWallMaskNode.renderingOrder = 10
leftWallMaskNode.position = SCNVector3(0, 0, 0.001)
//c. Add Our Wall And Mask To The Wall Enclosure
leftWallEnclosure.addChildNode(leftWallMaskNode)
leftWallEnclosure.addChildNode(leftWallNode)
//4. Add The Left Wall Enclosure To Our Portal
portal.addChildNode(leftWallEnclosure)
leftWallEnclosure.position = SCNVector3(-0.55, 0, -1)
//5. Create The Left Wall Enclosure
let rightWallEnclosure = SCNNode()
//a. Create The Right Wall And Set Its Rendering Order To 200 Meaning It Will Be Rendered After Our Masks
let rightWallNode = SCNNode()
let rightWallMainGeometry = SCNPlane(width: 0.5, height: 1)
rightWallNode.geometry = rightWallMainGeometry
rightWallMainGeometry.firstMaterial?.diffuse.contents = UIColor.red
rightWallMainGeometry.firstMaterial?.isDoubleSided = true
rightWallNode.renderingOrder = 200
//b. Create The Right Wall Mask And Set Its Rendering Order To 10 Meaning It Will Be Rendered Before Our Walls
let rightWallMaskNode = SCNNode()
let rightWallMaskGeometry = SCNPlane(width: 0.5, height: 1)
rightWallMaskNode.geometry = rightWallMaskGeometry
rightWallMaskGeometry.firstMaterial?.diffuse.contents = UIColor.blue
rightWallMaskGeometry.firstMaterial?.isDoubleSided = true
rightWallMaskGeometry.firstMaterial?.transparency = 0.0000001
rightWallMaskNode.renderingOrder = 10
rightWallMaskNode.position = SCNVector3(0, 0, 0.001)
//c. Add Our Wall And Mask To The Wall Enclosure
rightWallEnclosure.addChildNode(rightWallMaskNode)
rightWallEnclosure.addChildNode(rightWallNode)
//6. Add The Left Wall Enclosure To Our Portal
portal.addChildNode(rightWallEnclosure)
rightWallEnclosure.position = SCNVector3(0.55, 0, -1)
return portal
}
Which can be tested like so:
let portal = portalNode()
portal.position = SCNVector3(0, 0, -1.5)
self.sceneView.scene.rootNode.addChildNode(portal)
Hope it points you in the right direction...
FYI there is a good tutorial here from Ray Wenderlich which will also be of use to you...

SceneKit Physics simulation does not match actual Node location

I am attempting to implement a first-person space shooter in Scenekit, and I am having the (familiar, I know) problem of getting the physics simulation match the actual position and transform of the SCNNodes the physics simulation is supposed to represent.
The enemy drone ship is created using this function, which places the node in a SCNnode called SectorObjectNode which contains all game objects external to the ship(enemies, stars, etc) and its torpedoes (both of which live in the scene's root node:
func spawnDrone(_ sender: UIButton) {
let humonshipScene = SCNScene(named: "Humon.scn")
let humonShip = humonshipScene?.rootNode.childNodes[0]
self.enemyDrone = humonShip
let droneShape = SCNBox(width: 10, height: 5, length: 5, chamferRadius: 0)
let dronePhysicsShape = SCNPhysicsShape(geometry: droneShape, options: nil)
self.enemyDrone?.physicsBody = SCNPhysicsBody(type: .dynamic, shape: dronePhysicsShape)
self.enemyDrone?.physicsBody?.isAffectedByGravity = false
self.enemyDrone?.physicsBody?.friction = 0
self.enemyDrone?.physicsBody?.categoryBitMask = 0b00000010
self.enemyDrone?.physicsBody?.contactTestBitMask = 0b00000010
self.enemyDrone?.name = "drone"
self.enemyDrone?.pivot = SCNMatrix4MakeTranslation(0.5, 0.5, 0.5)
self.enemyDrone?.position = SCNVector3Make(0, 0, -30)
self.enemyDrone?.scale = SCNVector3Make(1, 1, 1)
let actualPosition = self.scene.rootNode.convertPosition((self.enemyDrone?.position)!, from: self.enemyDrone)
self.enemyDrone?.position = self.scene.rootNode.convertPosition(actualPosition, to: self.sectorObjectsNode)
self.sectorObjectsNode.addChildNode(self.enemyDrone!)
}
The sectorObjectsNode is rotated in reaction to onScreen joystick (thereby rotating the "universe" around the ship to simulate motion) using this code:
func turnShip() {
self.rotate(self.sectorObjectsNode, around: SCNVector3Make(1, 0, 0), by: CGFloat(self.yThrust))
self.rotate(self.sectorObjectsNode, around: SCNVector3Make(0, 1, 0), by: CGFloat(self.xThrust))
}
func rotate(_ node: SCNNode, around axis: SCNVector3, by angle: CGFloat) {
let rotation = SCNMatrix4MakeRotation(Float(angle), axis.x, axis.y, axis.z)
let newTransform = SCNMatrix4Mult(node.worldTransform, rotation)
// Set the new transform
if let parent = node.parent {
node.transform = parent.convertTransform(newTransform, from: nil)
} else {
node.transform = newTransform
}
}
But this code causes the physics simulation to reset ( The grey box in the center of the screen is the physics bounding box for the drone as depicted by the engine when sceneView.debugOptions is set to .showPhysicsShapes), with the following results:
I've tried capturing the drone's presentation position before rotation and then applying it after the two rotate functions, but this causes the ship to move down and to the left. I'm stymied as to how to get the physics simulation of the drone (which I'm using pretty exclusively for collision detection) to stick to the actual position of the enemyDrone node.
As per usual, the issue was RTFM. I set the physics body to be the wrong type:
self.enemyDrone?.physicsBody = SCNPhysicsBody(type: .dynamic, shape: dronePhysicsShape)
Needed to be changed to
self.enemyDrone?.physicsBody = SCNPhysicsBody(type: .kinematic, shape: dronePhysicsShape)

How to create a ring with 3D effect using Sprite Kit?

I want to create a ring with a 3D effect using Sprite Kit. (SEE IMAGES)
I tried subclassing a SKNode and adding two nodes as children. (SEE CODE)
One node was a complete SKShapeNode ellipse, and the other was half ellipse using SKCropNode with a higher zPosition.
It looks good, but the SKCropNode increases the app CPU usage from 40% to 99%.
Any ideas on how to reduce the SKCropNode performance cost, or any alternative to create the same ring 3D effect?
class RingNode: SKNode {
let size: CGSize
init(size: CGSize, color: SKColor)
{
self.size = size
self.color = color
super.init()
ringPartsSetup()
}
private func ringPartsSetup() {
// LEFT PART (half ellipse)
let ellipseNodeLeft = getEllipseNode()
let leftMask = SKSpriteNode(texture: nil, color: SKColor.blackColor(), size: CGSize(
width: ellipseNodeLeft.frame.size.width/2,
height: ellipseNodeLeft.frame.size.height))
leftMask.anchorPoint = CGPoint(x: 0, y: 0.5)
leftMask.position = CGPoint(x: -ellipseNodeLeft.frame.size.width/2, y: 0)
let leftNode = SKCropNode()
leftNode.addChild(ellipseNodeLeft)
leftNode.maskNode = leftMask
leftNode.zPosition = 10 // Higher zPosition for 3D effect
leftNode.position = CGPoint(x: -leftNode.frame.size.width/4, y: 0)
addChild(leftNode)
// RIGHT PART (complete ellipse)
let rightNode = getEllipseNode()
rightNode.position = CGPoint(x: 0, y: 0)
rightNode.zPosition = 5
addChild(rightNode)
}
private func getEllipseNode() -> SKShapeNode {
let ellipseNode = SKShapeNode(ellipseOfSize: CGSize(
width: size.width,
height: size.height))
ellipseNode.strokeColor = SKColor.blackColor()
ellipseNode.lineWidth = 5
return ellipseNode
}
}
You've got the right idea with your two-layer approach and the half-slips on top. But instead of using a shape node inside a crop node, why not just use a shape node whose path is a half-ellipse? Create one using either CGPath or UIBezierPath API — use a circular arc with a transform to make it elliptical — then create your SKShapeNode from that path.
You may try converting your SKShapeNode to an SKSpriteNode. You can use SKView textureFromNode: (but we aware of issues with scale that require you to use it only after the node has been added to the view and at least one update cycle has run), or from scratch using an image (created programatically with a CGBitmapContext, of course).

Resources