Animate specific dae objects based on condition - ios

For 2 dae objects in the scene, how can one initiate animations for each one of them at different conditions?
Since all objects are part of the SCNScene, I am unable to refer to individual ones based on a condition. They all render properly, but they animate all the same time. Can we put a condition to make specific objects in the scene animate at a time?
Thanks in advance!
let idleScene = SCNScene(named: "art.scnassets/Avatar_1.dae")!
// This node will be parent of all the animation models
let node = SCNNode()
// Add all the child nodes to the parent node
for child in idleScene.rootNode.childNodes {
node.addChildNode(child)
}
// Set up some properties
node.position = SCNVector3(hitTestResult.worldTransform.columns.3.x+0.5,hitTestResult.worldTransform.columns.3.y, hitTestResult.worldTransform.columns.3.z)
node.scale = SCNVector3(0.2, 0.2, 0.2)
// Add the node to the scene
sceneView.scene.rootNode.addChildNode(node)
For another avatar (Avatar_2.dae), how do we add it in the scene but give another reference name.
Also how can we individually play/pause the animations for each avatars?
This one if for scene, but is there one for individual avatars?
sceneView.scene.isPaused = play

By explicitly naming the elements in your SCNScene and then designating them as distinct nodes by using rootNode.childNode(withName:):
let scannerScene = SCNScene(named: "Scanner.scn")
let sectorField: SCNNode = (scannerScene?.rootNode.childNode(withName: "sectorField", recursively: true))!
let scanBeam: SCNNode = (scannerScene?.rootNode.childNode(withName: "scanBeam", recursively: true))!
Once you've done this, the individual nodes can be animated independently:
// start scanBeam
let rotateAction = SCNAction.rotateBy(x: 0, y: CGFloat(2*Float.pi), z: 0, duration: 1.5)
let perpetualRotation = SCNAction.repeatForever(rotateAction)
scanBeam.runAction(perpetualRotation)
To stop a specific animation (as opposed to all animations in the entire scene) simply remove the action
scanBeam.removeAction(forKey: String)

Related

Can't detect collision between rootNode and pointOfView child nodes in SceneKit / ARKit

In an AR app, I want to detect collisions between the user walking around and the walls of an AR node that I construct. In order to do that, I create an invisible cylinder right in front of the user and set it all up to detect collisions.
The walls are all part of a node which is a child of sceneView.scene.rootNode.
The cylinder, I want it to be a child of sceneView.pointOfView so that it would always follow the camera.
However, when I do so, no collisions are detected.
I know that I set it all up correctly, because if instead I set the cylinder node as a child of sceneView.scene.rootNode as well, I do get collisions correctly. In that case, I continuously move that cylinder node to always be in front of the camera in a renderer(updateAtTime ...) function. So I do have a workaround, but I'd prefer it to be a child of pointOfView.
Is it impossible to detect collisions if nodes are children of different root nodes?
Or maybe I'm missing something in my code?
The contactDelegate is set like that:
sceneView.scene.physicsWorld.contactDelegate = self so maybe this only includes sceneView.scene, but will exclude sceneView.pointOfView???
Is that the issue?
Here's what I do:
I have a separate file to create and configure my cylinder node which I call pov:
import Foundation
import SceneKit
func createPOV() -> SCNNode {
let pov = SCNNode()
pov.geometry = SCNCylinder(radius: 0.1, height: 4)
pov.geometry?.firstMaterial?.diffuse.contents = UIColor.blue
pov.opacity = 0.3 // will be set to 0 when it'll work correctly
pov.physicsBody = SCNPhysicsBody(type: .kinematic, shape: nil)
pov.physicsBody?.isAffectedByGravity = false
pov.physicsBody?.mass = 1
pov.physicsBody?.categoryBitMask = BodyType.cameraCategory.rawValue
pov.physicsBody?.collisionBitMask = BodyType.wallsCategory.rawValue
pov.physicsBody?.contactTestBitMask = BodyType.wallsCategory.rawValue
pov.simdPosition = simd_float3(0, -1.5, -0.3) // this position only makes sense when setting as a child of pointOfView, otherwise the position will always be changed by renderer
return pov
}
Now in my viewController.swift file I call this function and set is as a child of either root nodes:
pov = createPOV()
sceneView.pointOfView?.addChildNode(pov!)
(Don't worry right now about not checking and unwrapping).
The above does not detect collisions.
But if instead I add it like so:
sceneView.scene.rootNode.addChildNode(pov!)
then collisions are detected just fine.
But then I need to always move this cylinder to be in front of the camera and I do it like that:
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
guard let pointOfView = sceneView.pointOfView else {return}
let currentPosition = pointOfView.simdPosition
let currentTransform = pointOfView.simdTransform
let orientation = SCNVector3(-currentTransform.columns.2.x, -currentTransform.columns.2.y, -currentTransform.columns.2.z)
let currentPositionOfCamera = orientation + SCNVector3(currentPosition)
DispatchQueue.main.async {
self.pov?.position = currentPositionOfCamera
}
}
For completeness, here's the code I use to configure the node of walls in ViewController (they're built elsewhere in another function):
node?.physicsBody = SCNPhysicsBody(type: .dynamic, shape: SCNPhysicsShape(node: node!, options: nil))
node?.physicsBody?.isAffectedByGravity = false
node?.physicsBody?.mass = 1
node?.physicsBody?.damping = 1.0 // remove linear velocity, needed to stop moving after collision
node?.physicsBody?.angularDamping = 1.0 // remove angular velocity, needed to stop rotating after collision
node?.physicsBody?.velocityFactor = SCNVector3(1.0, 0.0, 1.0) // will allow movement only in X and Z coordinates
node?.physicsBody?.angularVelocityFactor = SCNVector3(0.0, 1.0, 0.0) // will allow rotation only around Y axis
node?.physicsBody?.categoryBitMask = BodyType.wallsCategory.rawValue
node?.physicsBody?.collisionBitMask = BodyType.cameraCategory.rawValue
node?.physicsBody?.contactTestBitMask = BodyType.cameraCategory.rawValue
And here's my physycsWorld(didBegin contact) code:
func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) {
if contact.nodeA.physicsBody?.categoryBitMask == BodyType.wallsCategory.rawValue || contact.nodeB.physicsBody?.categoryBitMask == BodyType.wallsCategory.rawValue {
print("Begin COLLISION")
contactBeginLabel.isHidden = false
}
}
So I print something to the console and I also turn on a label on the view so I'll see that collision was detected (and the walls indeed move as a whole when it works).
So Again, it all works fine when the pov node is a child of sceneView.scene.rootNode, but not if it's a child of sceneView.pointOfView.
Am I doing something wrong or is this a limitation of collision detection?
Is there something else I can do to make this work, besides the workaround I already implemented?
Thanks!
Regarding the positioning of your cyliner:
instead to use the render update at time, you better use a position constraint for your cylinder node to move with the point of view. the result will be the same, as if it were a child of the point of view, but collisions will be detected, because you add it to the main rootnode scenegraph.
let constraint = SCNReplicatorConstraint(target: pointOfView) // must be a node
constraint.positionOffset = positionOffset // some SCNVector3
constraint.replicatesOrientation = false
constraint.replicatesScale = false
constraint.replicatesPosition = true
cylinder.constraints = [constraint]
There is also an influence factor you can configure. By default the influence is 100%, the position will immediatly follow.

I can rotate my child nodes individually but not a group with 1 pivot point

I have a root node sent in from .dae file. It creates the children with camera and lights. I can rotate the individual children nodes if I want.
I am trying to avoid the math because it seems so logical that I can rotate 1 parent node and affect change on the child nodes.
I want to rotate the children node by rotating the root node. Can I just rotate the parent node?
or
Do I have to write math for each point transformed around a arbitrary pivot point (0, 0, 0) for example?
I have read a fair amount. I have tried to rotate the root node, with SCNnode.rootNode.
I have tried creating a pivot point with a new scnnode and adding the scene to that new pivot point.
I have tried Euler angles, rotation....
func set_up_scn() {
// self.scn = self.view as SCNView!
scene.scene = SCNScene(named: "sectional.dae");
group = SCNNode()
group.name = "FurnitureGroup"
for node in self.scene.scene!.rootNode.childNodes {
group.addChildNode(node);
}
scene.scene?.rootNode.addChildNode(group)
scene.autoenablesDefaultLighting = true
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(panGestureAction))
scene.addGestureRecognizer(panGesture)
}
#objc func panGestureAction(sender : UIPanGestureRecognizer) {
var pos : CGPoint = sender.translation(in: self.scene)
var pivotPoint : SCNNode = (self.scene.scene?.rootNode.childNode(withName: "FurnitureGroup", recursively: true))!
pivotPoint.rotation.x = Float(pos.x)
pivotPoint.rotation.y = Float(pos.y)
}
I'm not getting errors. I am not getting rotation either; I am getting rotation if I print out the pivotpoints rotation it shows x is changing. but no visible rotation.

ARkit - Camera Position and 3D model Positions

I am trying to put several models in the scene.
for candidate in selectedCandidate {
sceneView.scene.rootNode.addChildNode(selectedObjects[candidate])
}
The candidate and selectedCandidate stands for the index of the model I want to use. Each model contains a rootNode and nodes attached to it. I use the API worldPosition and position of SCNNode to get and modify 3D model's position.
The thing I want to do is put those models right in front users' eyes. It means I need to get the camera's position and orientation vector to put the models in the right position I want. I also use these codes to get the camera's position according to this solution https://stackoverflow.com/a/47241952/7772038:
guard let pointOfView = sceneView.pointOfView else { return }
let transform = pointOfView.transform
let orientation = SCNVector3(-transform.m31, -transform.m32, transform.m33)
let location = SCNVector3(transform.m41, transform.m42, transform.m43)
The PROBLEM is that the camera's position and the model's position I printed out directly are severely different in order of magnitude. Camera's position is 10^-2 level like {0.038..., 0.047..., 0.024...} BUT the model's position is 10^2 level like {197.28, 100.29, -79.25}. From my point of view when I run the program, I am in the middle of those models and models are very near, but the positions are so different. So can you tell me how to modify the model's position to whatever I want? I really need to put the model right in front of user's eyes. If I simply do addChildNode() the models are behind me or somewhere else, while I need the model just be in front of users' eyes. Thank you in advance!
If you want to place an SCNNode infront of the camera you can do so like this:
/// Adds An SCNNode 3m Away From The Current Frame Of The Camera
func addNodeInFrontOfCamera(){
guard let currentTransform = augmentedRealitySession.currentFrame?.camera.transform else { return }
let nodeToAdd = SCNNode()
let boxGeometry = SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0)
boxGeometry.firstMaterial?.diffuse.contents = UIColor.red
nodeToAdd.geometry = boxGeometry
var translation = matrix_identity_float4x4
//Change The X Value
translation.columns.3.x = 0
//Change The Y Value
translation.columns.3.y = 0
//Change The Z Value
translation.columns.3.z = -3
nodeToAdd.simdTransform = matrix_multiply(currentTransform, translation)
augmentedRealityView?.scene.rootNode.addChildNode(nodeToAdd)
}
And you can change any of the X,Y,Z values as you need.
Hope it points you in the right direction...
Update:
If you have multiple nodes e.g. in a scene, in order to use this function, it's probably best to create a 'holder' node, and then add all your content as a child.
Which means then you can simply call this function on the holder node.

ARKit: Are renderer(didAdd: ) and renderer(nodeFor: ) exclusive

Relying completely on ARKit automatic plane detection is something I don't want to do since it takes time to detect surfaces and then real life surfaces should be textured enough, hence I need to think of something to give an option where if I want I should be able to add anchors at my will with a tap of a button.
Here is where renderer(nodeFor: ) comes in handy. Just add an anchor at the tap of a button, using hitTest to ascertain the position of the anchor and then add nodes using nodeFor: method.
However, in other cases when I don't want to manually tap buttons, renderer(didAdd: ) should work. I have made a sharedObject through which I can ascertain whether plane detection needs to be "automated" or "manual". In case it's automated planeDetection would be set as .horizontal whereas in case it is manual, planeDetection would be set as [].
The issue is on testing it appears that either one of the two methods under delegate would work. Is there a way that I can achieve what I desire? Having a switch using which I can toggle whether I want automated plane detection or I want to add anchors and then planes. I would love to have both option.
Is it possible to use two different delegates to achieve it, just a thought...in that case how would it work. Pointers would be very much appreciated.
Yes, renderer(didAdd: ) and renderer(nodeFor: ) are exclusive. As per the docs, if we want to implement our own method for adding node in the scene, we can go ahead and use renderer(nodeFor: ), or we can instead choose ARKit to do the same for us using renderer(didAdd: ).
The way to manage both cases, viz. adding nodes manually while planeDetection = []; automatically adding nodes when planeDetection = .horizontal can be achieved by using renderer(nodeFor: ) method itself. There is no need of renderer(didAdd: ).
Within renderer(nodeFor: ) in case of planeDetection = .horizontal, anchor can be casted as ARPlaneAnchor whose center and extent can be used to update the added node.
Such as:
func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
if let planeAnchor = anchor as? ARPlaneAnchor {
let node = SCNNode()
let plane = SCNPlane(width: CGFloat(planeAnchor.extent.x),
height: CGFloat(planeAnchor.extent.z))
let planeNode = SCNNode(geometry: plane)
planeNode.name = "anchorPlane"
planeNode.simdPosition = float3(planeAnchor.center.x, 0, planeAnchor.center.z)
node.addChildNode(planeNode)
return node
At the same time, another condition can be imposed for planeDetection = [], when anchor can't be casted as ARPlaneAnchor, and geometry underlying the node can be given size as desired.
} else {
let node = SCNNode()
let plane = SCNPlane(width: 0.5, height: 0.5)
plane.firstMaterial?.diffuse.contents = UIColor.white
let planeNode = SCNNode(geometry: plane)
node.addChildNode(node)
return node
}
}

SCNNode parent-child relationship in SceneKit?

From my former question, I believe cloning the SCNNode is the way to solve the problem of loading multiple objects. However, I realized there is something wrong.
I did something like
arrow = scnScene.rootNode.childNode(withName: "arrow", recursively:true)
arrow.rotation = SCNVector4(0, 0, 1, M_PI_2)
arrow.position = SCNVector3(x: 0, y: 0, z: -3);
let sign = SCNNode()
for i in 0..<10{
let node = arrow.clone()
node.position = SCNVector3(i+1,0,0)
sign.addChildNode(node)
}
arrow.addChildNode(sign)
I expect there is a row of arrows displaying but actually there is a column of arrows (Translating in x axis but in fact affecting y axis, seems cloned node's coordinate system has been changed) displaying and all of child objects rotate 180 degree. Why?
Before Karl Sigiscar told me to use SCNReferenceNode, I am not sure how should I use it and will it achieve my goal?
Update:
I tried SCNReferenceNode like this:
let scnScene = SCNScene()
if let filePath = Bundle.main.path(forResource: "arrowsign", ofType: "dae", inDirectory: "art.scnassets") {
// ReferenceNode path -> ReferenceNode URL
let referenceURL = NSURL(fileURLWithPath: filePath)
if #available(iOS 9.0, *) {
arrow = SCNReferenceNode(url: referenceURL as URL!)!
arrow.load()
arrow.rotation = SCNVector4(0, 0, 1, -M_PI_2)
arrow.position = SCNVector3(0,0,-5)
// Create reference node
for i in 0..<10{
let referenceNode : SCNReferenceNode = SCNReferenceNode(url: referenceURL as URL)!
referenceNode.load()
referenceNode.position = SCNVector3(i+1, 0, 0);
arrow.addChildNode(referenceNode)
}
}
}
scnScene.rootNode.addChildNode(arrow)
I didn't rotate child nodes but they rotated automatically (-M_PI_2 degree) when I run it. And I did translate in x axis but in fact there is a column of arrows displaying, rather than a row of arrows. Is that a feature of child-parent node?
After using SCNReferenceNode, I figured out if you add a node to another node as the child, the child node will be set to the parent node's location. For instance, there is a parent node in location (3,4,5), then all of child nodes of it will be located in (3,4,5) at first (you can modify later). So if you want a row of arrows, just modify child nodes' x and keep y & z value be equal to zero.
If the parent node applies rotation, the rotation will be applied to child nodes as well.

Resources