SceneKit – Making a custom physics body - swift

So I've designed a hallway that I want my player to walk through, but I can
't seem to get a physics body to work for it. Either the player walks through the walls or he can't walk down the hallway because it sees the object as a giant cube. How do I get the physics body to go around the object.
let chessPieces = SCNScene(named: "art.scnassets/hallway.dae")
if let knight2 = chessPieces?.rootNode.childNodeWithName("Room", recursively: true) {
knight2.position = SCNVector3Make(150, 30, 0)
knight2.scale = SCNVector3Make(knight2.scale.x * 200, knight2.scale.y * 200, knight2.scale.z * 200)
var nodeScale = NSValue(SCNVector3:SCNVector3Make(200, 200, 200));
var nodeGeometry = knight2.geometry;
var shape = SCNPhysicsShape(geometry: nodeGeometry!, options: [SCNPhysicsShapeScaleKey:nodeScale])
knight2.physicsBody = SCNPhysicsBody(type:SCNPhysicsBodyType.Static, shape: shape)
knight2.physicsBody?.categoryBitMask = rockCategory
knight2.physicsBody?.angularVelocityFactor = SCNVector3Make(0.0,0.0,0.0)
knight2.physicsBody?.collisionBitMask = 3
knight2.name = "Student"
knight2.physicsBody?.mass = 1000
scene?.rootNode.addChildNode(knight2)
}

SceneKit physics bodies model solid shapes. If you're trying to model an open space enclosed by boundaries — like a room or hallway — a single physics body won't help you. That'd fill in the volume of the room with an impassible area, and other physics bodies (with overlapping collision masks) would be forced out of that area.
If you want to make an open space enclosed by boundaries, you need to create physics bodies for the boundaries. The SceneKitVehicle sample code illustrates doing this, creating separate physics bodies for the floor and walls of a room using SCNFloor and SCNBox geometries for each.

Related

How do I load my own Reality Composer scene into RealityKit?

I have created 3 "scenes" inside Experience.rcproject file, that is created when you start a new Augmented Reality project using xcode.
Working for 3D a lot, I would say that these were 3 objects inside a scene, but inside Experience.rcproject I have added 3 "scenes". Inside each one, the same 3D model. The first one is attached to an horizontal plane, the second one to a vertical plane and the third one to an image.
I am woking with Reality Kit for the first time and learning along the way.
My idea of doing so, is to load the right object when I want to have it attached to the horizontal, vertical or image.
This is how I accomplished this.
I have modified Experience.swift file provided by Apple to accept scene names, like this:
public static func loadBox(namedFile:String) throws -> Experience.Box {
guard let realityFileURL = Foundation.Bundle(for: Experience.Box.self).url(forResource: "Experience", withExtension: "reality") else {
throw Experience.LoadRealityFileError.fileNotFound("Experience.reality")
}
let realityFileSceneURL = realityFileURL.appendingPathComponent(namedFile, isDirectory: false)
let anchorEntity = try Experience.Box.loadAnchor(contentsOf: realityFileSceneURL)
return createBox(from: anchorEntity)
}
and I call this line
let entity = try! Experience.loadBox(namedFile:sceneName)
whatever I want, but I have to use this code:
// I have to keep a reference to the entity so I can remove it from its parent and nil
currentEntity?.removeFromParent()
currentEntity = nil
// I have to load the entity again, now with another name
let entity = try! Experience.loadBox(namedFile:sceneName)
// store a reference to it, so I can remove it in the future
currentEntity = entity
// remove the old one from the scene
arView.scene.anchors.removeAll()
// add the new one
arView.scene.anchors.append(entity)
This code is stupid and I am sure there is a better way.
Any thoughts?
Hierarchy in RealityKit / Reality Composer
I think it's rather a "theoretical" question than practical. At first I should say that editing Experience file containing scenes with anchors and entities isn't good idea.
In RealityKit and Reality Composer there's quite definite hierarchy in case you created single object in default scene:
Scene –> AnchorEntity -> ModelEntity
|
Physics
|
Animation
|
Audio
If you placed two 3D models in a scene they share the same anchor:
Scene –> AnchorEntity – – – -> – – – – – – – – ->
| |
ModelEntity01 ModelEntity02
| |
Physics Physics
| |
Animation Animation
| |
Audio Audio
AnchorEntity in RealityKit defines what properties of World Tracking config are running in current ARSession: horizontal/vertical plane detection and/or image detection, and/or body detection, etc.
Let's look at those parameters:
AnchorEntity(.plane(.horizontal, classification: .floor, minimumBounds: [1, 1]))
AnchorEntity(.plane(.vertical, classification: .wall, minimumBounds: [0.5, 0.5]))
AnchorEntity(.image(group: "Group", name: "model"))
Here you can read about Entity-Component-System paradigm.
Combining two scenes coming from Reality Composer
For this post I've prepared two scenes in Reality Composer – first scene (ConeAndBox) with a horizontal plane detection and a second scene (Sphere) with a vertical plane detection. If you combine these scenes in RealityKit into one bigger scene, you'll get two types of plane detection – horizontal and vertical.
Two cone and box are pinned to one anchor in this scene.
In RealityKit I can combine these scenes into one scene.
// Plane Detection with a Horizontal anchor
let coneAndBoxAnchor = try! Experience.loadConeAndBox()
coneAndBoxAnchor.children[0].anchor?.scale = [7, 7, 7]
coneAndBoxAnchor.goldenCone!.position.y = -0.1 //.children[0].children[0].children[0]
arView.scene.anchors.append(coneAndBoxAnchor)
coneAndBoxAnchor.name = "mySCENE"
coneAndBoxAnchor.children[0].name = "myANCHOR"
coneAndBoxAnchor.children[0].children[0].name = "myENTITIES"
print(coneAndBoxAnchor)
// Plane Detection with a Vertical anchor
let sphereAnchor = try! Experience.loadSphere()
sphereAnchor.steelSphere!.scale = [7, 7, 7]
arView.scene.anchors.append(sphereAnchor)
print(sphereAnchor)
In Xcode's console you can see ConeAndBox scene hierarchy with names given in RealityKit:
And you can see Sphere scene hierarchy with no names given:
And it's important to note that our combined scene now contains two scenes in an array. Use the following command to print this array:
print(arView.scene.anchors)
It prints:
[ 'mySCENE' : ConeAndBox, '' : Sphere ]
You can reassign a type of tracking via AnchoringComponent (instead of plane detection you can assign an image detection):
coneAndBoxAnchor.children[0].anchor!.anchoring = AnchoringComponent(.image(group: "AR Resources",
name: "planets"))
Retrieving entities and connecting them to new AnchorEntity
For decomposing/reassembling an hierarchical structure of your scene, you need to retrieve all entities and pin them to a single anchor. Take into consideration – tracking one anchor is less intensive task than tracking several ones. And one anchor is much more stable – in terms of the relative positions of scene models – than, for instance, 20 anchors.
let coneEntity = coneAndBoxAnchor.goldenCone!
coneEntity.position.x = -0.2
let boxEntity = coneAndBoxAnchor.plasticBox!
boxEntity.position.x = 0.01
let sphereEntity = sphereAnchor.steelSphere!
sphereEntity.position.x = 0.2
let anchor = AnchorEntity(.image(group: "AR Resources", name: "planets")
anchor.addChild(coneEntity)
anchor.addChild(boxEntity)
anchor.addChild(sphereEntity)
arView.scene.anchors.append(anchor)
Useful links
Now you have a deeper understanding of how to construct scenes and retrieve entities from those scenes. If you need other examples look at THIS POST and THIS POST.
P.S.
Additional code showing how to upload scenes from ExperienceX.rcproject:
import ARKit
import RealityKit
class ViewController: UIViewController {
#IBOutlet var arView: ARView!
override func viewDidLoad() {
super.viewDidLoad()
// RC generated "loadGround()" method automatically
let groundArrowAnchor = try! ExperienceX.loadGround()
groundArrowAnchor.arrowFloor!.scale = [2,2,2]
arView.scene.anchors.append(groundArrowAnchor)
print(groundArrowAnchor)
}
}

SceneKit – Unable to make 3D objects static shape

I already posted my question here but I not got any satisfactory answer, may be question was not clear therefore I'm again posting my problem.
I make a 3D tunnel in blender and imported it into sceneKit project
In my scene I put a player character which run in this tunnel and its fine.the player runs with camera with user touch as in temple run game.
playerNode.addChildNode(cameraNode)
When player runs it come cross the tunnel because tunnel is a soft body shape geometry and there is no physics applied
I make the physics body type static giving the physics body shape as convex hull but it not work, I also check to make it concave body but problem is same the player come across the walls of tunnel. Here is my code
scene = SCNScene(named: "game.scnassets/UpdatedTunnel/second.dae")!
let Tunnel = scene.rootNode.childNodeWithName("SketchUp", recursively: true)!
TunnelNode.addChildNode(Tunnel)
var shape:SCNPhysicsShape?
if let geometry = TunnelNode.geometry {
shape = SCNPhysicsShape(geometry: geometry, options: [SCNPhysicsShapeTypeKey: SCNPhysicsShapeTypeConvexHull])
var nodePhysicsBody = SCNPhysicsBody(type: SCNPhysicsBodyType.Static, shape: shape)
TunnelNode.physicsBody = nodePhysicsBody
}
I try different shape types to make the body solid but nothing work for me, I thought maybe something wrong with my 3D .dae tunnel so for check I create a plane in my scene and make it static body type but it also giving the same error my player cross this plane too.
let plane = SCNPlane(width:20.0,height:15.0)
var planeNode = SCNNode(geometry: plane)
planeNode.position = SCNVector3(4.0,0.0,-5.0)
planeNode.physicsBody = SCNPhysicsBody.staticBody()
planeNode.physicsBody = SCNPhysicsBody(type: SCNPhysicsBodyType.Static,
shape: SCNPhysicsShape(geometry: plane, options: nil))
I am badly stuck at this point from many days please someone help me where is my mistake in code and how I can make my tunnel walls solid so the player when hit the walls it should bounce back.
Here is the image in this link you can see This is my tunnel

Detect collision between SKNode and Particles?

I have this function that creates some lava:
func setupLava() {
let emitter = SKEmitterNode(fileNamed: "Lava.sks")!
emitter.particlePositionRange = CGVector(dx: 200, dy: 0.0)
emitter.advanceSimulationTime(3.0)
emitter.zPosition = 4
emitter.position = CGPoint(x: self.frame.width / 2, y: 300)
lava.addChild(emitter)
}
And I want to detect when the player collides with it. How do I do that?
From the documentation:
Particles are not represented by objects in SpriteKit. This means you
cannot perform node-related tasks on particles, nor can you associate
physics bodies with particles to make them interact with other
content. Although there is no visible class representing particles
added by the emitter node, you can think of a particle as having
properties like any other object.
So you cannot use SpriteKit to detect collisions with the emitted Lava, but you can associate a physics body to the Lava object and collide with it not it individual emitted nodes. Use categoryContactMask, contactBitMask fields of the physicsBody of your nodes to detect contacts.

Accessing properties of multiple SKShapeNodes

In my program I have a method called addObstacle, which creates a rectangular SKShapeNode with an SKPhysicsBody, and a leftward velocity.
func addObstacle(bottom: CGFloat, top: CGFloat, width: CGFloat){
let obstacleRect = CGRectMake(self.size.width + 100, bottom, width, (top - bottom))
let obstacle = SKShapeNode(rect: obstacleRect)
obstacle.name = "obstacleNode"
obstacle.fillColor = UIColor.grayColor()
obstacle.physicsBody = SKPhysicsBody(edgeLoopFromPath: obstacle.path!)
obstacle.physicsBody?.dynamic = false
obstacle.physicsBody?.affectedByGravity = false
obstacle.physicsBody?.contactTestBitMask = PhysicsCatagory.Ball
obstacle.physicsBody?.categoryBitMask = PhysicsCatagory.Obstacle
obstacle.physicsBody?.usesPreciseCollisionDetection = true
self.addChild(obstacle)
obstacle.runAction(SKAction.moveBy(obstacleVector, duration: obstacleSpeed))
}
In a separate method, called endGame, I want to fade out all the obstacles currently in existence on the screen. All the obstacle objects are private, which makes accessing their properties difficult. If there is only one on the screen, I can usually access it by its name. However, when I say childNodeWithName("obstacleNode")?.runAction(SKAction.fadeAlphaBy(-1.0, duration: 1.0)), only one of the "obstacles" fades away; the rest remain completely opaque. Is there a good way of doing this? Thanks in advance (:
You could probably go with:
self.enumerateChildNodesWithName("obstacleNode", usingBlock: {
node, stop in
//do your stuff
})
More about this method can be found here.
In this example I assumed that you've added obstacles to the scene. If not, then instead of scene, run this method on obstacle's parent node.
And one side note...SKShapeNode is not performant solution in many cases because it requires at least one draw pass to be rendered by the scene (it can't be drawn in batches like SKSpriteNode). If using a SKShapeNode is not "a must" in your app, and you can switch them with SKSpriteNode, I would warmly suggest you to do that because of performance.
SpriteKit can render hundreds of nodes in a single draw pass if you are using same atlas and same blending mode for all sprites. This is not the case with SKShapeNodes. More about this here. Search SO about this topic, there are some useful posts about all this.

SpriteKit - How do I check if a certain set of coordinates are inside an SKShapeNode?

In my game, I'm trying to determine what points to dole out depending on where an arrow hits a target. I've got the physics and collisions worked out and I've decided to draw several nested circular SKShapeNodes to represent the different rings of the target.
I'm just having issues working out the logic involved in checking if the contact point coordinates are in one of the circle nodes...
Is it even possible?
The easiest solution specific to Sprite Kit is to use the SKPhysicsWorld method bodyAtPoint:, assuming all of the SKShapeNode also have an appropriate SKPhysicsBody.
For example:
SKPhysicsBody* body = [self.scene.physicsWorld bodyAtPoint:CGPointMake(100, 200)];
if (body != nil)
{
// your cat content here ...
}
If there could be overlapping bodies at the same point you can enumerate them with enumerateBodiesAtPoint:usingBlock:
You can also compare the SKShapeNode's path with your CGPoint.
SKShapeNode node; // let there be your node
CGPoint point; // let there be your point
if (CGPathContainsPoint(node.path, NULL, point, NO)) {
// yepp, that point is inside of that shape
}