How can I access multiple animations in Swift? - ios

Image of the file hierarchy in xcode of my animation file
Issue:
xcode is recognizing multiple animations for a single Collada (.dae) file. But I can't find any documentation on how to access these animations directly. I tried using the fox game example, but it only loads one of the animations.
Here's my code:
let modelNode = self.addModel_DAE_return(x: 0, y: 0, z: 0, scaleFactor: 0.0005, fileName: "models.scnassets/export_014/export_014_model.dae")
// add the animation to the model
let modelAnim = self.loadAnim_DAE(fileName: "models.scnassets/export_014/export_014_anim.dae")
modelNode.addAnimationPlayer(modelAnim, forKey: "headMove")
modelAnim.play()
// add the model to the scene
node.addChildNode(modelNode)
How can I access and load in the other animations?
Context:
I'm making an AR app. I'm using the Apple Image Recognition example as a base.
Here's a link to it:
https://developer.apple.com/documentation/arkit/recognizing_images_in_an_ar_experience
I animated a skeleton in Maya and exported the animation to a COLLADA (.DAE) file using the OpenCollada extension for Maya.
I exported separate files for the model and the animation, because if I export them as a single file, none of my animations export and xcode crashes every time I try to access the file to check if it registers any animations.
I want to access the "Entities" of my animation file so I can just loop over, load and attach the animations, but I can't
also I have looked into GameKit kind of but, is there not a way to do this with SceneKit?

Each joint in the rig has animation information
Originally I was only getting the information the first joint that had animations on it and not its children that also had animations attached
i.e. lets say your rig hierarchy is
hips > rightShoulder > arm > elbow > wrist
and your animations were on the shoulder, arm, elbow, and wrist joints
you do not have any animations on your hip joint
using enumerateChildNodes will only grab the information from the shoulder joint
using enumerateHierarchy will grab all the animation information from the shoulder, the elbow, AND the wrist joint
see below for my function that I used:
note that I had separately loaded and saved my 3D model onto an SCNNode and that was passed in so I could attach the animations to it
func loadAttachAnim_DAE(fileName: String, modelNode: SCNNode){
let scene = SCNScene(named: fileName)!
//find top level animation
var animationPlayer: SCNAnimationPlayer! = nil
scene.rootNode.enumerateHierarchy{ (child, stop) in
if !child.animationKeys.isEmpty {
print ("child = \(child) -----------------")
animationPlayer = child.animationPlayer(forKey: child.animationKeys[0])
modelNode.addAnimationPlayer(animationPlayer, forKey: "\(child)")
animationPlayer.play()
}
}
}

Related

SceneKit - positioning SCNCamera and permitting object rotation

I am close to completing my first project in SceneKit but I'm struggling with the last few steps. It is probably easiest to explain my progress by sharing a short screen capture video of the Xcode Simulator displaying my current scene.
As you can see by the screen capture my project is composed of three elements (this is all done in code, I do not import any external assets):
outside box (defined via six SCNBox objects per corner)
inside sun (defined via a SCNTube object for the circle and UIBezierPath objects per "ray")
position of camera
Based on feedback I have committed the code to GitHub.
Right now the camera is allowed to rotate as seen in the screen capture but the centre of rotation of the camera and of the objects doesn't align so it appears to spin off-axis.
Here's where I want to get to:
correct camera position so that the combined box & sun is positioned directly in front of the camera, filling the screen
maintain the sun's position as being fixed (already done I guess)
allow the box to rotate freely in x, y & z around the sun based on touch input - so the user can "flick" the box and watch it flip and spin around the sun
The code structure is straight forward:
class GameViewController: UIViewController {
var gameView: SCNView!
var gameScene: SCNScene!
var cameraNode: SCNNode!
var targetCreationTime: TimeInterval = 0
override func viewDidLoad() {
super.viewDidLoad()
initView()
initScene() // createSun() and createCube() called here
initCamera()
}
And with respect to the camera position:
func initCamera() {
let camera = SCNCamera()
cameraNode = SCNNode()
cameraNode.camera = camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 0)
cameraNode.rotation = SCNVector4Make(1, 0, 0, .pi/2)
}
But what I've found is that despite playing around with the random cameraNode.position and cameraNode.rotation values the camera view doesn't seem to change.
My questions - any help will be greatly appreciated:
advice on repositioning the camera (what am I doing wrong?!) - once it's in the right place I can easily set "gameView.allowsCameraControl = false"
advice on how to enable the box to spin about its axis around the sun (while the sun remains fixed)
stretch goal! Any kind of general "check out this tutorial" type info on materials and lighting, and embedding this view into a SwiftUI view
Thanks!
I decided to stop fighting the point of rotation and instead reposition the elements around this.
One interesting thing, which I’ve mentioned at the start of the createBox() func.
// originally debugCube & debugNode were used for debugging the pivot point of the box
// but I found have this large node helped to balance out the centre of mass
// set to fully transparent and added to boxNode as final step after all other transformations
If you comment out the lines 19-26 plus 117 you will completely remove debugNode. And funnily enough when you do that the box stops spinning correctly. But you add it back in and everything is fixed. I’m guessing it’s adding “mass” to the overall node and helping lock the point of rotation to the correct position. So in the end I just made it transparent!
The final (version 1.0) code is posted on GitHub at github.com/LedenMcLeden/logo
Use this answer in post for your camera: 57586437, remove camera rotation and take camera control off. Rotate your box with a simple (I'd do an x,y,z independent spin just to verify it) spin so that you'll know if your pivot point is correct. It should be ok by default and spin in place right in front of the camera, but depends on how you built your cube.
If you added the sun and stuff as a subnode of your box, then you're probably in decent shape and the pieces will rotate together.
If you want to do camera rotations similar to cameraControl, then you'll need to add a gesture recognizer and then you can start experimenting with it.
Hope that helps!

How can I load multiple nodes of the same 3d model all at once?

I'm working on an ARKit application where you would walk through a portal and you'd be somewhere else. I purchased a 3d model of an archway to serve as the portal. I took the model in to Blender to flatten it down to one piece, but the texture disappeared and whenever I apply a new texture it only shows up on one portion of the model.
Here is the original model, only modified by changing it to .dae format. however, I can't load arch1, arch2, arch3, etc. without writing the same line over and over
With this model I'd be writing the same code over and over
private func addPortalArch(hitResult: ARHitTestResult) {
let portalScene = SCNScene(named: "art.scnassets/Medieval_portal.dae")
let portalNode = portalScene?.rootNode.childNode(withName: "arch1", recursively: true)
// I'd be writing this portalNode = portalScene line over and over for each arch.
//I'd also have to get the position for each piece as far as I understand, which I do not know how I'd begin to do that based on where the user taps to place the portal.
portalNode?.position = SCNVector3(hitResult.worldTransform.columns.3.x, hitResult.worldTransform.columns.3.y, hitResult.worldTransform.columns.3.z)
self.sceneView.scene.rootNode.addChildNode(portalNode!)
}
I need the model to be all once piece but still have the same stonework material over the whole of it.
Looking at the file you sent, your best bet is to use the model which contains each aspect of the Arch (your 1st picture).
The 1st step (not always necessary but useful) is to convert the dae file to an .scn file:
Having done this you then want to create an Empty Node to be the Portal Door RootNode and make each element of the Portal a child of this e.g:
You will also need to rotate each ChildNode's (not the PortalRoot) Euler Angle X by -90 (as when you load the model the Portal Door is on it's side):
Now since you have a single RootNode, there is no need to loop through all the nodes so that is displayed on the screen. You can simple use a function like this:
/// Places The Portal Door 5m Aeay From The Camera
func setupPortalDoor(){
//1. Get The Portal Scene
guard let portalScene = SCNScene(named: "portalDoor.scn"),
//2. Get The Portal Node
let portalModel = portalScene.rootNode.childNode(withName: "PortalRoot", recursively: false) else { return }
//3. Add To The ARSCNView Root
augmentedRealityView?.scene.rootNode.addChildNode(portalModel)
//4. Position It 5m Away From The Camera
portalModel.position = SCNVector3(0, 0, -5)
/*
The Portal Is Large So I Suggest Scaling It ^__________*
*/
}
If you later wanted to change the portal arch to say marble you can easily do it like so:
for node in portalModel.childNodes{
node.geometry?.firstMaterial?.diffuse.contents = UIColor.cyan
}
Hope this helps...

Positioning virtual assets with ARKit and SCNKit

I'm trying to understand SCNKit and ARKit a little better and have a barebones Xcode 9 Augmented Reality app deployed and working on my iPhone (which I'm using as a simple test device).
This app's source code is here.
Basically, the app starts, the camera is initialized, and it renders a 3D fighter jet inside the scene (world view) in a similar fashion to how Pokemon Go injects monsters into your camera viewport (wherever your pointing your camera at). Pretty cool!
This code was all auto-generated for me by Xcode. So I'm trying to understand where the logic lives that determines where to position/orient the fighter jet (the SCN file titled art.scnassets/ship.scn). From here we see the jet being loaded:
override func viewDidLoad() {
super.viewDidLoad()
// Set the view's delegate
sceneView.delegate = self
// Show statistics such as fps and timing information
sceneView.showsStatistics = true
// Create a new scene
print("Hello there Mr. Zac")
let scene = SCNScene(named: "art.scnassets/ship.scn")!
// Set the scene to the view
sceneView.scene = scene
}
But I don't understand how the app chooses where to place the jet/ship and to orient it in which direction. I ask because as a first step I'd like to try repositioning the jet and then also swapping it out for my own asset files.
The "logic" for that, such as there is any, lives in two places.
The ship.scn file defines not just a model, but the model's position in the scene. (That is, in a global "world" coordinate space.)
In the scn file that ships in that Xcode project template, the model is positioned at something like 0, 0, -0.5, so if a camera is placed at the origin of the coordinate system, the ship appears directly in front of the camera, half a meter away.
ARKit itself defines scene/world space relative to the initial real-world position/orientation of the device. By default, that coordinate system's z-axis matches the initial orientation of the device, so anything placed "in front of" the coordinate origin will appear in front of the camera when you start the AR session.

SCNNode scale changes when animation plays using a dae file

Trying to use a dae file in scenekit for my model as well as my animations. When I try to scale the model, it scales correctly. After the animation starts playing, it resets to the original scale. Here is what I am trying to do at the moment:
let playerNode = gameScene.rootNode.childNode(withName: "Player", recursively: true)
let animation = CAAnimation.animationWithSceneNamed(name: "GameAssets.scnassets/Objects/WalkAnimation.dae")
playerNode.addAnimation(animation, forKey: "WalkAnimation")
As we don't have access to your Collada file, let's take the example of walk.dae from the SceneKitAnimations sample code.
In that file you will find the following:
<library_animations>
<animation id="WalkID">
...
<source id="node-Bip01_matrix-output">
...
<technique_common>
<accessor source="#node-Bip01_matrix-output-array" count="29" stride="16">
<param name="TRANSFORM" type="float4x4"/>
</accessor>
</technique_common>
You can see that the animation file does not have separate animations for positions and rotations, but instead it has a single animation that targets the whole transforms (cf TRANSFORM and float4x4).
This means that evaluating the animation will override the scale of the node. You'll have to have different animations just for the position and rotation properties instead of an animation for the transform property if you don't want the scale to be overridden.

How to add custom animations in ios scene kit swift

I am making a game with scene kit and part of it rolls some dice to generate a number, i have made the dice in 3DS max 2016. I can make the animations easily on 3DS max, but how would i load it into my scene? I have loaded in a dice with no animations, but I am not sure how I can load the dice animations so that it rolls.
What I intend to do is make xcode generate a number, and with that number is chooses a file out of different files, e.g. animation1, animation2. Say the number generated was 2 it would run animation2.
Thanks
You can export animations from 3DS Max with OpenCOLLADA plugin.
Then you can get animations with SCNSceneSource, like:
let src = SCNSceneSource(URL: yourSceneURL, options: nil)
let node = src.entryWithIdentifier("yourNodeID", withClass: SCNNode.self) as SCNNode
let animation = node.entryWithIdentifier("yourAnimationID", withClass: CAAnimation.self) as CAAnimation
Extract animations to array, and attach random one to your node with SCNNode's addAnimation(forKey:)

Resources