How to add a 3D object as SCNNode in ARKit2? - ios

I am new in ARKit2 and followed a couple of tutorials and official documentation.
problem
How to add 3d object as SCNNode()?
Code
let artFrame = SCNBox(width: CGFloat(w), height: CGFloat(h), length: 0.002, chamferRadius: 0.02)
artFrame.firstMaterial?.diffuse.contents = imageToDisplay
let artFrameNode = SCNNode(geometry: artFrame)
artFrameNode.physicsBody = SCNPhysicsBody(type: .static, shape: SCNPhysicsShape(geometry: artFrame, options: nil))
artFrameNode.position = SCNVector3Make(Float(x), Float(y), Float(z*3))
sceneView.scene.rootNode.addChildNode(artFrameNode)
As per the above code, I am using predefined SCNBox in SCNNode. Is there any way that I can use 3d object such as .dae, .obj instead of SCNBox and wrap with Image?
I check out the documentation and it says that you can add Mesh objects to SCNNode :
https://developer.apple.com/documentation/scenekit/scnnode/1419841-init
Edit
Whenever I am adding a new .dae and converting to .scn. Xcode is throwing follwing error:
/Users/paly/Library/Developer/Xcode/DerivedData/sample-bohiilnhthuwfkechtmhscucgccc/Build/Products/Debug-iphoneos/sample.app: resource fork, Finder information, or similar detritus not allowed
Command CodeSign failed with a nonzero exit code

To add .dae or .obj object to the scene, you need to get childNode of the .dae object and simply add it to your scene. I recommend to convert your .dae file to .scn simply using Xcode's Editor menu.
You also need the node's name, which can be accessed in Scene Graph View. Just click on your .obj file and click the Scene Graph View button on bottom left corner of the Xcode scene editor. Here, my node's name is "objectNode":
Now you can add the 3D object as a SCNNode to your scene:
override func viewDidLoad() {
super.viewDidLoad()
...
let objectScene = SCNScene(named: "object.scn") // Here, add your .obj or .scn file
let objectNode: SCNNode = objectScene.rootNode.childNode(withName: "YourObjectName", recursively: true) // Get the object name from Scene Graph View, which is "objectNode" for me.
objectNode.position = SCNVector3(0,0,-4)
let scene = SCNScene() // Main scene of the app
scene.rootNode.addChildNode(objectNode)
sceneView.scene = scene
...
}

Related

SCNAnimationPlayer() Seems to be playing only small part of animation while using SceneKit

I made an animation in Blender and moved it to Xcode and it is converted from .dae file
to .scn by Xcode.
I can play the animation in scene graph of Xcode as it is designed in the Blender.
I am loading geometry and animation and create node for animated object.
I use SCNAnimationPlayer() to load animation. The code is below.
let scene = SCNScene(named: "scene.scn")!
let geometry = scene.rootNode.childNode(withName: "animatedGeo",
recursively:true).geometry!
let animationNode = SCNNode(geometry: geometry)
let armature = scene.rootNode.childNode(withName: "Armature", recursively: true)!
let animationPlayer = armature.animationPlayer(forKey: "action_container-Armature")!
animationNode.addAnimationPlayer(animationPlayer, forKey: "action_container-Armature")
rootNode.addChildNode(animationNode)
I did not set anything for animationPlayer programmatically because all the settings looks
Ok in scene graph window of Xcode.
However when scene loaded I see a small movement on animated object on iPhone screen.
Looks like only first (parent) bone animation is partly playing.
I could not find the why all of animation was not playing as it is played in scene graph.
Yes, I found how to do it when I was trying to manipulate my object designed and animated in Blender programmatically instead of animation I made in Blender.
What you have to do is to add "Armature" node to your scene node.
This is all. You don't have to use SCNAnimationPlayer().
let scene = SCNScene(named: "scene.scn")!
let myObjectArmaturNode = scene.rootNode.childNode(withName: "Armature", recursively: true)!
rootNode.addChildNode(myObjectArmaturNode)
This is it.
The animation runs like it is designed in Blender.
The animation consists of 4 bones by the way.

iOS ARKit - move objects along path

I am trying to move implement bouncing balls using ARKit. I want the balls coming from one end of screen bouncing and moving out of screen.
Can anyone please recommend best approach or point to sample code to implement this?
Can I use UIBezierPath to create a path and move SCNNode along the path. If yes, how can I move the node along path.
Create a scene and a ball Node. Use force direction for ball Node and animate the ball Node with Physics. This is just an example.
// Create a new scene
guard let scene = SCNScene(named: "BouncingBalls.scn", inDirectory: "art.scnassets") else { return }
sceneView.scene = scene
// Add physics bodies
guard let ballsNode = scene.rootNode.childNode(withName: "balls", recursively: true) else { return }
let forceDirection = SCNVector3Make(0, 3, 0)
for ballNode in ballsNode.childNodes {
let physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil)
physicsBody.applyForce(forceDirection, asImpulse: true)
ballNode.physicsBody = physicsBody
}
Try this in View didLoad. Hope this works.
The best way to get what you want is to use a predefined animation or dynamics that was made in such 3D apps as Autodesk Maya, Autodesk 3dsMax, or Maxon Cinema4D.
After producing a ball animation (or dynamics, it's up to you) you need to bake this animation (baking is a process of keyframe generation for every frame instead of using interpolated curve) and export this scene as a .dae file format. ARKit/SceneKit supports not only .dae animations but also brand-new format .usdz.
SceneKit API (as well as other Apple frameworks' APIs) is not designed for 3D animation.

SCNNode Disobeying Position after Placement

I'm using Apples SceneKit and have a custom .dae asset. I've converted the asset to a .scn file. I am grabbing the SCNNode by name from the .scn file. After placing the SCNNode in my SCNView scene as a child node and setting it's position to be SCNVector3(0,0,-1), it ignores this position and instead follows my phone location. The asset renders right on top of me and when I walk away, it follows me. It's getting to be very annoying and I can't find a solution.
However, if I replace the SCNNode taken from the .scn file, and use a SCNBox, instead, everything works just fine. The cube stays in it's set position.
Here is the relevant code:
func addCoins(at position: SCNVector3) {
let donorScene = SCNScene(named: "art.scnassets/nodes.scn")
if let coin = donorScene?.rootNode.childNode(withName: "coin", recursively: true) {
coin.position = position // 0, 0, -1 (right in front of me)
scene.scene.rootNode.addChildNode(coin)
}
}
Attached is a screenshot showing what I see whenever I move.
So the problem was that my 3d asset was too large. The asset was created at 200cm^2 which is 2meter^2 in the 3d modeling program. After the asset was imported into Xcode, it's interpreted by SceneKit as 200meters, which is way too large for SceneKit, apparently.

How can I combine SceneKit with existing OpenGL using SCNRenderer?

I would like to use SceneKit’s SCNRenderer to draw a few models into my existing GLKit OpenGL ES 3.0 application and match my existing modelViewProjection transform. When I load a SceneKit scene and render it using SCNRenderer the SceneKit camera transformations seem to be ignored and I just get a default bounding box view. More generally of course I would prefer to supply my own matrix and I am not sure how to do that.
//
// Called from my glkView(view: GLKView, drawInRect rect: CGRect)
//
// Load the scene
let scene = SCNScene(named: "art.scnassets/model.scn")!
// Create and add regular SceneKit camera to the scene
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: 0, y: 0, z: 10)
scene.rootNode.addChildNode(cameraNode)
// Render into the current OpenGL context
let renderer = SCNRenderer( context: EAGLContext.currentContext(), options: nil )
renderer.scene = scene
renderer.renderAtTime(0)
I see that there is a projectionTransform exposed on the SCN Camera and I have tried manipulating that as well, but with no results. I’m also guessing that that is just literally a projection transform and not expecting the full modelViewProjection transform.
// Probably WRONG
cameraNode.camera!.projectionTransform = SCNMatrix4FromGLKMatrix4( modelViewProjectionTransform )
Does anyone have an example of mixing SceneKit into OpenGL drawing in this way? Thanks.
EDIT:
Bobelyuk's reference to the example is on point and has helped me solve half of my problem so far. It turns out that although I was adding the camera node I had failed to set the camera node as the 'pointOfView':
scene.pointOfView = cameraNode
The example appears to show how to take an opengl matrix and invert it to set it on the camera, however as of yet I have not been able to get this to work with my code. I will update again with a code example shortly.
http://qiita.com/akira108/items/a743138fca532ee193fe here you can find how to combine SCNRenderer and OpenGLContext
Ultimately it turned out to be very simple. In addition to setting the pointOfView to the camera node I just needed to properly set the camera transform and camera projection transform to my own matrices.
Here is the updated example:
//
// Called from my glkView(view: GLKView, drawInRect rect: CGRect)
//
// Load the scene
let scene = SCNScene(named: "art.scnassets/model.scn")!
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode)
// Set my own matrices
cameraNode.transform = SCNMatrix4Invert(SCNMatrix4FromGLKMatrix4(myModelViewMatrix))
cameraNode.camera!.projectionTransform = SCNMatrix4FromGLKMatrix4(myProjectionMatrix)
// Render into the current OpenGL context
let renderer = SCNRenderer( context: EAGLContext.currentContext(), options: nil )
renderer.scene = scene
renderer.pointOfView = cameraNode // set the point of view for the scene
renderer.renderAtTime(0)
For thos new to this: The model view matrix is the matrix that orients your scene, e.g. composing GLKMatrix4Scale and GLKMatrix4Rotate calls. The projection matrix is the one that sets your view frustrum and point of view, e.g. by composing calls to GLKMatrix4MakePerspective and GLKMatrix4MakeLookAt calls.

How to programmatically wrap png texture around cube in SceneKit

I'm new to SceneKit... trying to get some basic stuff working without much success so far. For some reason when I try to apply a png texture to a CNBox I end up with nothing but blackness. Here is the simple code snippet I have in viewDidLoad:
let sceneView = (view as SCNView)
let scene = SCNScene()
let boxGeometry = SCNBox(width: 10.0, height: 10.0, length: 10.0, chamferRadius: 1.0)
let mat = SCNMaterial()
mat.locksAmbientWithDiffuse = true
mat.diffuse.contents = ["sofb.png","sofb.png","sofb.png","sofb.png","sofb.png", "sofb.png"]
mat.specular.contents = UIColor.whiteColor()
boxGeometry.firstMaterial = mat
let boxNode = SCNNode(geometry: boxGeometry)
scene.rootNode.addChildNode(boxNode)
sceneView.scene = scene
sceneView.autoenablesDefaultLighting = true
sceneView.allowsCameraControl = true
What it ends up looking like is a white light source reflecting off of a black cube against a black background. What am I missing? I appreciate all responses
If you had different images, you would build a different SCNMaterial object from each like so:
let material_L = SCNMaterial()
material_L.diffuse.contents = UIImage(named: "CapL")
Here, CapL refers to a .png file that has been stored in the project's Assets.xcassets folder. After building 6 such objects, you hand them to the boxNode as follows:
boxGeometry.materials = [material_L, material_green_r, material_K, material_purple_r, material_g, material_j]
Note that "boxGeometry" would be better named "box" or "cube". Also, it would be a good idea to do that work in a new class in your project, constructed like:
class BoxScene: SCNScene {
Which you would then call with modern Swift in your viewController's viewDidLoad method like this:
let scnView = self.view as! SCNView
scnView.scene = BoxScene()
(For that let statement to work, go to Main.storyboard -> View Controller Scene -> View Controller -> View -> Identity icon Then under Custom Class, change it from UIView to SCNView. Otherwise, you receive an error message, like:
Could not cast value of type 'UIView' to 'SCNView'
Passing an array of images (to create a cube map) is only supported by the reflective material property and the scene's background.
In your case, all the images are the same, so you would only have to assign the image (not an array) to the contents to have it appear on all sides of the box

Resources