Camera position in orthographic projection - swift

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.

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.

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.

Adding SceneKit Camera node vanishes my main node

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

Using SceneKit for hitTesting not returning a hit with SCNNode

The documentation in XCode clearly states that hitTesting a geometry in SceneKit can be done with SCNRender, SCNView or the SCNNode themselves when one plans to test a 3D line segment. I have a use for SCNScene with its nodes without a renderer or a view, therefore I am planning to use SCNNode hitTesting. I create a SCNScene, put a SCNNode in it and test a simple ray that goes through, but I always get an empty hitList and I don't understand why:
import Swift
import SceneKit
let boxGeometry = SCNBox(width: 1.0, height: 1.0, length: 1.0, chamferRadius: 0)
let boxNode = SCNNode(geometry: boxGeometry)
var scene = SCNScene()
scene.rootNode.addChildNode(boxNode)
let from = SCNVector3(x: 0, y: -2, z: 0)
let to = SCNVector3(x: 0, y: 2 , z: 0)
var hits = scene.rootNode.hitTestWithSegmentFromPoint(from, toPoint: to, options:nil) // this is always empty
if hits != nil {
if hits!.count > 0 {
var hit = (hits!.first as! SCNHitTestResult).node as SCNNode
}
}
I have tried passing various forms of options but nothing changes.
SCNHitTestFirstFoundOnlyKey: yes or no does not change anything
SCNHitTestSortResultsKey: yes or no does not change anything
SCNHitTestClipToZRangeKey: invalid for SCNNode
SCNHitTestBackFaceCullingKey: yes or no does not change anything
SCNHitTestBoundingBoxOnlyKey: yes or no does not change anything
SCNHitTestRootNodeKey: rootNOde of scene or boxNode does not change
anything
SCNHitTestIgnoreHiddenNodesKey: yes or no does not change anything
What am I doing wrong?
I have found the answer, which is either a bug or a feature: using SCNScene and its nodes SCNNode for 3D hitTesting, in particular the method: "hitTestWithSegmentFromPoint(toPoint:options:)" does not return a hit unless the scene is included in an SCNView. It appears it cannot be used offscreen. My guess is yours for why this is the case, although I can imagine it has something to do with performing some of these quite expensive calculations on the graphics card.
I have tested this using the GameView SCNScene starter project. The critical line is self.gameView!.scene = scene
override func awakeFromNib(){
let scene = SCNScene()
let boxGeometry = SCNBox(width: 1.0, height: 1.0, length: 1.0, chamferRadius: 0.0)
let boxNode = SCNNode(geometry: boxGeometry)
boxNode.position=SCNVector3(x: 0, y: 0, z: 0)
scene.rootNode.addChildNode(boxNode)
// 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: 0, z: 15)
// create and add a light to the scene
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light!.type = SCNLightTypeOmni
lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
scene.rootNode.addChildNode(lightNode)
// create and add an ambient light to the scene
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = SCNLightTypeAmbient
ambientLightNode.light!.color = NSColor.darkGrayColor()
scene.rootNode.addChildNode(ambientLightNode)
// set the scene to the view
// uncomment this to fail
self.gameView!.scene = scene
// allows the user to manipulate the camera
self.gameView!.allowsCameraControl = true
// show statistics such as fps and timing information
self.gameView!.showsStatistics = true
// configure the view
self.gameView!.backgroundColor = NSColor.blackColor()
let hitList = scene.rootNode.hitTestWithSegmentFromPoint(SCNVector3(x:-10,y:0,z:0), toPoint: SCNVector3(x:10,y:0,z:0), options:[SCNHitTestBackFaceCullingKey:false, SCNHitTestSortResultsKey:true, SCNHitTestIgnoreHiddenNodesKey:false])
if hitList?.count > 0 {
println("Hit found: \n\n\( hitList![0] )") // assign self.gameView!.scene = scene to reach this point.
} else {
println("No hit") // uncomment self.gameView!.scene = scene to reach this point.
}
}
I've also had trouble with hitTestWithSegmentFromPoint.
I was calling it in viewDidLoad() and it returned a 0 elements array, though I was sure there was a hit.
Calling it in viewDidAppear() (or later) solved my problem.