Adding SceneKit Camera node vanishes my main node - swift

I am just starting off with SceneKit and I have some code that renders a cube onto the screen. It shows up find but as soon as I add a camera node the cube no longer appears on screen. Given that I'd set the position of the cube's Node to 0,0,0 and the same for the camera, I cannot figure out what is wrong. I attempted to set the pointOfView property of the SCNView to the camera node but this did not work.
Here is the code for clarification:
func setup() {
box = SCNBox(width: 30, height: 30, length: 30, chamferRadius: 5)
shapeNode.geometry = box
shapeNode.position = SCNVector3(x:0, y:0, z:0)
mainNode.addChildNode(shapeNode)
scene.rootNode.addChildNode(mainNode)
sceneKitView = SCNView(frame:bounds, options:nil)
sceneKitView.autoenablesDefaultLighting = true
sceneKitView.allowsCameraControl = true
sceneKitView.scene = scene
sceneKitView.backgroundColor = UIColor.blackColor()
addSubview(sceneKitView)
// cameraNode.camera = SCNCamera()
// cameraNode.position = SCNVector3Make(0, 0, 10)
// scene.rootNode.addChildNode(cameraNode)
// sceneKitView.pointOfView = cameraNode
}

if both your camera and cube are at (0, 0, 0) then the camera is inside the cube and can't see it. You can make your cube's material doubleSided so that back-facing triangles are visible, but you probably just want to move your camera at something like (0, 0, 100) (a camera's direction of view is along the negative Z axis, and your box has a size of 30).

Related

How can I rotate 3d object using right and left arrows in Swift? (SceneKit)

How can I enable the user to move the object using the right and left arrows I put below, instead of manually moving it?
"allowscameracontrol" allows the user to rotate the object with their hand. But I just want it to be rotated using arrows.
** -> sceneView.allowsCameraControl = true**
import UIKit
import SceneKit
class ViewController: UIViewController {
#IBOutlet weak var sceneView: SCNView!
override func viewDidLoad() {
super.viewDidLoad()
// 1: Load .obj file
let scene = SCNScene(named: "converse_obj.obj")
//Add camera node
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
//Place camera
cameraNode.position = SCNVector3(x: 0, y: 10, z: 35)
//*Set camera on scene
scene?.rootNode.addChildNode(cameraNode)
//Adding light to scene
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light?.type = .omni
lightNode.position = SCNVector3(x: 0, y: 10, z: 35)
scene?.rootNode.addChildNode(lightNode)
// 6: Creating and adding ambien light to scen
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light?.type = .ambient
ambientLightNode.light?.color = UIColor.darkGray
scene?.rootNode.addChildNode(ambientLightNode)
// Allow user to manipulate camera
sceneView.allowsCameraControl = true
// Set background color
sceneView.backgroundColor = UIColor.white
// Allow user translate image
sceneView.cameraControlConfiguration.allowsTranslation = false
// Set scene settings
sceneView.scene = scene
}
}
I haven't used SceneKit before.
I'm not sure if you would want to rotate your camera or rotate the node that contains the object. From the way it's named, it makes it sound like allowsCameraControl rotates the camera. In any case you would use rotationMatrixAroundZ to create a rotation matrix, and then multiply that by the matrix for whichever thing you want to rotate.
If rotating the camera is what you want to do, your code might look like this:
func rotateCameraZByDegrees(_ zRotation: Double) {
let radians = zRotation / 180.0 * Double.pi
let rotationZ = rotationMatrixAroundZ(radians: radians)
cameraNode.transform = SCNMatrix4(simd_mul(simd_float4x4(cameraNode.transform), rotationZ))
}
If you instead want to rotate your object's node, and your object's node is named sneakersNode, change the last line to:
sneakersNode.transform = SCNMatrix4(simd_mul(simd_float4x4(sneakersNode.transform), rotationZ))
This post [31881911] in stackO shows you some examples for arrow keys.
Turn allowsCameraControl = false
This post [68506605] see my answer - creating a camera control class makes things a lot easier.
For your rotation, just rotate the object directly - several ways to do this.
func resetRotation()
{
for (_, vNode) in displayNodes
{
vNode.node.removeAllActions()
}
vRotate0 = SCNAction.rotateTo(x: 0, y: 0, z: 0, duration: 2)
vRotate1 = SCNAction.rotateBy(x: 0, y: CGFloat(GLKMathDegreesToRadians(450)), z: 0, duration: 3)
vRotate2 = SCNAction.rotateBy(x: 0, y: CGFloat(GLKMathDegreesToRadians(-90)), z: 0, duration: 1)
vRotate3 = SCNAction.rotateBy(x: CGFloat(GLKMathDegreesToRadians(-45)), y: 0, z: 0, duration: 1)
vRotate4 = SCNAction.rotateBy(x: CGFloat(GLKMathDegreesToRadians(45)), y: 0, z: 0, duration: 1)
seq = SCNAction.sequence([vRotate0, vRotate1, vRotate2, vRotate3, vRotate4, vRotate3, vRotate4])
allSeq = SCNAction.repeatForever(seq)
for (_, vNode) in displayNodes
{
vNode.node.runAction(allSeq)
}
}
You could use some sequences - so your object could do a 360 and stop, come back 90 at a slower pace, whatever you want. The above repeats forever, but you can take that out.
Or just on Left key press, rotate Y += n degrees.

Camera position in orthographic projection

I'm trying to understand how to use camera with usesOrthographicProjection = true. I need to set it up so when we first see the scene, the object should be viewable in full.
I used the Xcode's SceneKit template, adjust a little bit about the camera (all the code is in viewDidLoad). Using default
camera (perspective projection), it looks like this:
cameraNode.position = SCNVector3(x: 0, y: 0, z: ship.boundingBox.max.z + 20)
Next, I tried to set the orthographic projection, now it looks like this:
cameraNode.camera?.usesOrthographicProjection = true
cameraNode.position = SCNVector3(x: 0, y: 0, z: ship.boundingBox.max.z + 20)
No matter how I tried to change the camera position, it still looks the same as the image above. I've tried using GLKMatrix4MakeOrtho to no avail. Nothing works. What should I do the change the first view impression?
cameraNode.camera?.usesOrthographicProjection = true
// let width = Float(UIScreen.main.bounds.size.width)
// let width: Float = 9
// let glMat = GLKMatrix4MakeOrtho(-width/2,
// width/2,
// -width/2,
// width/2,
// 1,
// 1000)
// let glMat = GLKMatrix4MakeOrtho(ship.boundingBox.min.x,
// ship.boundingBox.max.x,
// ship.boundingBox.min.y,
// ship.boundingBox.max.y,
// ship.boundingBox.min.z,
// ship.boundingBox.max.z)
// cameraNode.camera?.projectionTransform = SCNMatrix4FromGLKMatrix4(glMat)
cameraNode.position = SCNVector3(x: 0,
y: 0,
z: ship.boundingBox.max.z + 20)
Use orthographicScale instance property to control camera’s magnification factor when using an orthographic projection.
var orthographicScale: Double { get set }
Here's full code version:
import SceneKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let sceneView = self.view as! SCNView
sceneView.scene = SCNScene(named: "art.scnassets/ship.scn")!
sceneView.allowsCameraControl = true
sceneView.backgroundColor = UIColor.black
sceneView.pointOfView?.camera!.usesOrthographicProjection = true
sceneView.pointOfView?.camera!.zNear = 0.1
sceneView.pointOfView?.camera!.zFar = 50.0
sceneView.pointOfView?.camera!.orthographicScale = 5.0
}
}
Orthographic projection is a means of representing three-dimensional objects in two dimensions. So there's no distance in Z since we are in two dimensions. That's why you have to use an orthographicScale property. No matter how you move the camera with parallel projection beams, the distance to objects will remain unchanged.
So, take into consideration: in an orthographic projection, equally sized objects appear equally sized regardless of their distance from the camera.
The only parameter that controls a "distance" in two-dimensional space (there's no Z position in reality in 2D) is an orthographicScale.

How to increase lighting in scene?

I'm trying to light up a basic model I downloaded from Mixamo.
let scene = SCNScene(named: "art.scnassets/Ch45_nonPBR.dae")!
// create and add a camera to the scene
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode)
// place the camera
cameraNode.position = SCNVector3(x: 0, y: 40, z: 110)
// create and add a light to the scene
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light!.type = .omni
lightNode.position = SCNVector3(x: 0, y: 50, z: 50)
scene.rootNode.addChildNode(lightNode)
// create and add an ambient light to the scene
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = .ambient
ambientLightNode.light!.color = UIColor.white
scene.rootNode.addChildNode(ambientLightNode)
At present the camera is too close to the model. But if I change the z-value from 110, all I see is black. I imagine this has something to do with lighting. What should my lighting be so that I can see my model even if I change the z-value of my camera to a higher value so I can see the model from far away ?
EDIT: For example, right now you can see at this distance, part of the leg is not visible:
If I move further away, the whole model won't be visible!!
EDIT: For example, how do I edit the lighting so it looks just like in the Mixamo preview on their website:
I would like to move the camera far enough away so I can see the whole model on screen.
SCNCamera has a zFar property, the default value is 100, any surface further away from the camera than this is clipped to improve performance. In your screenshot the leg is the furthest part of the model from the camera so gets clipped first, and as you move further away the whole model is clipped.
You can just increase the zFar to a number suitable for your scene.

SceneKit: sceneView projectPoint with multiple cameras

If a scene contains multiple cameras, which camera does the projectPoint method use to project points from 3D to screen-space? If this is defined by the pointOfView property, then how come when I update the position of the pointOfView a given 3D point is still projected to the same 2D point?
Since SCNCamera belongs to SCNView, just set the PoV via the "pointOfView" instance property of a View to a required camera node.
let cameraNode001 = SCNNode()
cameraNode001.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode001)
cameraNode001.position = SCNVector3(x: 0, y: 0, z: 15)
let cameraNode002 = SCNNode()
cameraNode002.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode002)
cameraNode002.position = SCNVector3(x: 10, y: 10, z: 30)
let sceneView = self.view as! SCNView
sceneView.scene = scene
sceneView.pointOfView = cameraNode001
then you can change PoV:
sceneView.pointOfView = cameraNode002

How to draw 3D object with core graphics in Swift?

How can I draw a 3D object (preferably a rectangle) with core graphics in Swift?
Is it possible or do I have to use a different library?
Is it possible with UIKit?
Borrowing from this answer: https://stackoverflow.com/a/24127282/887210
The key part for your question is:
SCNBox(width: 1, height: 4, length: 9, chamferRadius: 0)
This draws a rectangular box with SceneKit and UIKit. It's set up to be used in a custom UIViewController in your project but it can easily be adapted to other uses.
The example code:
override func loadView() {
// create a scene view with an empty scene
let sceneView = SCNView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
let scene = SCNScene()
sceneView.scene = scene
// default lighting
sceneView.autoenablesDefaultLighting = true
// a camera
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
scene.rootNode.addChildNode(cameraNode)
// a geometry object
let box = SCNBox(width: 1, height: 4, length: 9, chamferRadius: 0)
let boxNode = SCNNode(geometry: box)
scene.rootNode.addChildNode(boxNode)
// configure the geometry object
box.firstMaterial?.diffuse.contents = UIColor.red
box.firstMaterial?.specular.contents = UIColor.white
// set a rotation axis (no angle) to be able to
// use a nicer keypath below and avoid needing
// to wrap it in an NSValue
boxNode.rotation = SCNVector4(x: 1, y: 1, z: 0.0, w: 0.0)
// animate the rotation of the torus
let spin = CABasicAnimation(keyPath: "rotation.w") // only animate the angle
spin.toValue = 2.0*Double.pi
spin.duration = 10
spin.repeatCount = HUGE // for infinity
boxNode.addAnimation(spin, forKey: "spin around")
view = sceneView // Set the view property to the sceneView created here.
}
This question is similar to the question whether it is possible to draw a 3D object on a sheet of paper which is, between, 2D. The third dimension effect is achieved drawing additional lines as the projections. The third dimension could also be perceived through the motion, so, Core Animation is a possible companion to Core Graphics but it requires a lot of calculation, as result, it is quite complicated (using Core Animation).
Actually, SceneKit or Metal are the options to draw 3D models using Swift.