SceneKit – Node with animated skeleton not moving - swift

I have a SceneKit app with 2 .scn files in the art.scnassets folder. The base ship.scn that comes with the project, and a new file orcIdle.scn that I added myself.
When I call this code on this ship, it moves it properly:
self.myScene = SCNScene(named: "art.scnassets/ship.scn")!
// Set the scene to the view
let ship = (myScene.rootNode.childNode(withName: "ship", recursively: false))!
ship.position = SCNVector3(0, 0, -5)
self.sceneView.scene = myScene
When I call the same code on my orcIdle file, it places the orc exactly at (0,0,0)
self.myScene = SCNScene(named: "art.scnassets/orcIdle.scn")!
// Set the scene to the view
let orc = (myScene.rootNode.childNode(withName: "orcIdle", recursively: false))!
orc.position = SCNVector3(0, 0, -5)
self.sceneView.scene = myScene
here are screenshots of the file node:
This is the default ship file included in the project by Xcode.
This is my orcIdle file.
As you can see, it's the exact same code but 1 moves, and the other doesn't. Also, in the top right corner of the images, i can adjust the Z value there. It works for .ship, but not for .orcIdle.
Is it a scale issue? Any thoughts?
Here's a new screenshot with as much of the scene graph as I can capture.

Usually, you must select a skeleton's top hierarchy (like root node) to move geometry, since its mesh was bound to skeleton. If model's skeleton is considerably deeper in the hierarchy use .childNodes[x] subscript syntax to fetch a desired joint.
But a solution based on your .scn file is the following – create a topNode object and put root and LOD_Group inside it.
So your code should look like this:
let scene = SCNScene(named: "art.scnassets/orcIdle.scn")!
orc = scene.rootNode.childNode(withName: "topNode", recursively: true)!
orc.position = SCNVector3(0,0,-20)

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)
}
}

Spritekit endless runner parent SKNode

I'm familiar with swift but I've started dabbling my hand in spritekit. I've been following a tutorial about created an endless runner. The approach taken by the author is to create a single SKNode that contains all the children. This container node is then moved rather than moving all the child Nodes individually. But what's got me stumped is that the container node doesn't have a size associated with it so I'm a bit confused as to how/why this works and the author doesn't really explain it.
So we have
let containerNode = SKNode()
let thePlayer = Player("image":"player") //SKSpriteNode
let inc = 0
override func didMoveToView(){
self.anchorPoint = CGPointMake(0.5,0)
addChild(containerNode )
containerNode.addChild(player)
moveWorld()
}
func moveWorld(){
let moveWorldAction = SKAction.moveByX(-screenWidth, y:0, duration:6)
let block = SKAction.runBlock(movedWorld)
let seq = SKAction.sequence([moveWorldAction,block])
let repeatAction = SKAction.repeatActionForever(seq)
containerNode.runAction(repeatAction)
}
func movedWorld() {
inc = inc + 1
addObjects()
}
func addObjects() {
let obj = Object()
containerNode.addChild(obj)
let ranX = arc4random_uniform(screenWidth)
let ranY = arc4random_uniform(screenHeight)
obj.position = CGPointMake(screenWidth * (inc + 1) + ranX, ranY)
}
There's some conversion that I've omitted in the code above from int to float but it's not necessary for the point I want to understand.
I get why when new objects are created we do the multiple by the increment, but what I don't get is the containerNode doesn't have a size, so why do it's children show? Is this the most efficient way to do this?
I'm assuming that it's just convenience rather than moving all the other objects individually but the fact that it doesn't have a size is confusing me.
Since an SKNode isn't rendered, it doesn't need (or have) a size property. Only SKNode subclasses (SKLabelNode, SKShapeNode, etc.) that are visible require a size (declared explicitly or calculated implicitly). For example, SpriteKit needs to know the size of an SKSpriteNode with a 20 x 20 texture to correctly render it. You can use SKNode's calculateAccumulatedFrame method to determine the total size (a rectangle) of all of the descendants (other than other SKNodes) in its node tree.
If I understand the documentation for SKNode correctly this little section should answer your question:
Every node in a node tree provides a coordinate system to its
children. After a child is added to the node tree, it is positioned
inside its parent’s coordinate system by setting its position
properties. A node’s coordinate system can be scaled and rotated by
changing its xScale, yScale, and zRotation properties. When a node’s
coordinate system is scaled or rotated, this transformation is applied
both to the node’s own content and to that of its descendants.
Source
Again, if I'm reading this correctly, it seems that when a node is added to a node tree it inherits it's parent coordinate system. So by default containerNode is the same size as self in this case.
Also according to the frame property of a SKNode:
The frame is the smallest rectangle that contains the node’s content,
taking into account the node’s xScale, yScale, and zRotation
properties. Source
Which again sounds like if a frame isn't set it takes the smallest rectangle (self in this case or it might be understood that it's the children of containerNode I'm not sure) as it's own frame.
Whatever the case is, it's inheriting it from another SKNode.

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

SKTextureAtlas cannot be found

I dragged a folder named "movementAnim" into the Image assets folder, which is suppose to create a texture atlas (from what I picked from the WWDC 2015 What's New in SpriteKit Session). See the code below:
SimpleSprite class:
Init() {
let spriteAtlas = SKTextureAtlas(named: "movementAnim")
sprite = SKSpriteNode(texture: spriteAtlas.textureNamed("moveAnim01"))
sprite.name = spriteCategoryName
sprite.position = CGPointMake(CGRectGetMidX(screenSize), sprite.frame.size.height * 3.7)
initialPos = sprite.position
sprite.zPosition = 10
}
func physicsProperties () {
sprite.physicsBody = SKPhysicsBody(rectangleOfSize: sprite.frame.size)
sprite.physicsBody?.friction = 0.4
sprite.physicsBody?.restitution = 0.1
sprite.physicsBody?.dynamic = false
}
In the init of the GameScene class:
simpleSprite = SimpleSprite()
addChild(simpleSprite!.sprite)
simpleSprite!.physicsProperties()
I get the following log message:
Texture Atlas 'movementAnim' cannot be found.
But when I use the following instead of the texture, it works perfectly:
sprite = SKSpriteNode(imageNamed: "newMoveAnim01")
I've crossed checked the names and made sure there are no duplicates. I am not sure what's wrong.
If you add the extension .spriteatlas to your folder name, it should work. XCode does not add this by default.
You are correct that atlases are done in the image assets folder now, William Hu's way was the old way it was done.
Create a folder in your project root path, name it movementAnim.atlas, then copy and paste all your images into this folder. Then go back your project navigator and add this folder into your project, make sure the folder is blue.

Adding and transitioning animations in SceneKit

I looked at Banana game from WWDC which is written in Objective-C was trying to convert the code to Swift for importing animation and transitioning between them but I am having problems running the animations in swift from DAE files.
I have exporter DAE files in both AutoDesk format from 3dsMax and in openCollada format. The Autodesk format the animation is for each bone so I am unable to call an animation by name so I just import the scene and do the following for the animation to start as soon as the file loads.
scene = SCNScene(named: "monster.scnassets/monsterScene.DAE")
scene2 = SCNScene(named:"monster.scnassets/monster.DAE")
var heroNode = SCNNode()
heroNode = scene.rootNode.childNodeWithName("heroNode", recursively: false)!
var nodeArray = scene2.rootNode.childNodes
for childNode in nodeArray {
heroNode.addChildNode(childNode as SCNNode)
}
Although the animation plays as soon as the scene starts I done know how to store the animation.
If I export the collada file using openCollada. I can use the following to run the get and run the animation as there is just one animation for the whole object instead of each bone in case of AutoDesk collada format. By this way I can store the animation also using CAAnimation.
var anim = scene2.rootNode.animationForKey("monster-1")
childNode.addAnimation(anim, forKey: "monster-1")
But then the character runs at an angle and also runs back and forth instead of running at the same spot.
Also the lighting is better using openCollada. I just would like to use openCollada instead of autodesk collada export. Right now I am using openCollada format for exporting the scene and autodesk for exporting character.
How do store animations in SceneKit/Swift and transition between them? Thanks.
If you don't change the default options when loading a scene from a file, all animations in the scene immediately and automatically attach to their target nodes and play. (See the Animation Import Options in the SceneKit API reference.)
If you want to load the animations from a scene file and hold on to them for attaching to nodes (that is, playing) later, you're better off loading them with the SCNSceneSource class. In addition, you can (but don't have to) store your base model in one file and animations in other files.
Just look at this Bananas animation loading code. Just look at it.*
// In AAPLGameLevel.m:
SCNNode *monkeyNode = [AAPLGameSimulation loadNodeWithName:nil fromSceneNamed:#"art.scnassets/characters/monkey/monkey_skinned.dae"];
AAPLMonkeyCharacter *monkey = [[AAPLMonkeyCharacter alloc] initWithNode:monkeyNode];
[monkey createAnimations];
// In AAPLSkinnedCharacter.m (parent class of AAPLMonkeyCharacter):
+ (CAAnimation *)loadAnimationNamed:(NSString *)animationName fromSceneNamed:(NSString *)sceneName
{
NSURL *url = [[NSBundle mainBundle] URLForResource:sceneName withExtension:#"dae"];
SCNSceneSource *sceneSource = [SCNSceneSource sceneSourceWithURL:url options:nil ];
CAAnimation *animation = [sceneSource entryWithIdentifier:animationName withClass:[CAAnimation class]];
//...
}
// In AAPLMonkeyCharacter.m:
- (void)update:(NSTimeInterval)deltaTime
{
// bunch of stuff to decide whether/when to play animation, then...
[self.mainSkeleton addAnimation:[self cachedAnimationForKey:#"monkey_get_coconut-1"] forKey:nil];
//...
}
What's going on here:
There's a custom class managing the animated character. It owns a SCNNode containing the character model, as well as a bunch of CAAnimations for all of the things the model can do (idle/jump/throw/etc).
That class is initialized by passing the character node loaded from one DAE file. (AAPLGameSimulation loadNodeWithName:fromSceneNamed: is a convenience wrapper around loading a SCNScene from a file and grabbing a named node out of it.) That DAE file contains only the character model, with no animations.
Then, AAPLMonkeyCharacter loads (and stores references to) the animations it needs from the separate DAE files containing each animation. This is where SCNSceneSource comes in — it lets you grab animations out of the file without playing them.
When it's time to play, the monkey class calls addAnimation:forKey: to run the animation on its main node.
Translating to Swift and applying to your problem — where you seem to have all animations in the same file — I'd do something like this (vague outline of a hypothetical class):
class Monster {
let node: SCNNode
let attackAnimation: CAAnimation
init() {
let url = NSBundle.mainBundle().URLForResource(/* dae file */)
let sceneSource = SCNSceneSource(URL: url, options: [
SCNSceneSourceAnimationImportPolicyKey : SCNSceneSourceAnimationImportPolicyDoNotPlay
])
node = sceneSource.entryWithIdentifier("monster", withClass: SCNNode.self)
attackAnimation = sceneSource.entryWithIdentifier("monsterIdle", withClass: CAAnimation.self)
}
func playAttackAnimation() {
node.addAnimation(attackAnimation, forKey: "attack")
}
}
The key bits:
SCNSceneSourceAnimationImportPolicyDoNotPlay makes sure that nodes loaded from the scene source don't start with animations attached/playing.
You have to load the animations separately with entryWithIdentifier:withClass:. Be sure to configure them how you like (repeating, fade duration etc) before attaching to the nodes.
2022 code fragment.
In terms of the swift sample in #rickster 's answer.
It seems the code is now more like:
let p = Bundle.main.url(forResource: "File Name", withExtension: "dae")!
source = SCNSceneSource(url: p, options: nil)!
let geom = source.entryWithIdentifier("geometry316",
withClass: SCNGeometry.self)!
as SCNGeometry
yourDragonNode = SCNNode(geometry: geom)
yourAnime = source.entryWithIdentifier("unnamed_animation__0",
withClass: CAAnimation.self)!
In terms of how to get the "mystery strings":
"geometry316" and "unnamed_animation__0" in the example.
notice: https://stackoverflow.com/a/75088130/294884
and: https://stackoverflow.com/a/56787980/294884