I am having a little more mathematical problem with 3D programming and I am hoping you can help me!
I am trying to create a 3D game using Scenekit with a isometric angle.
This code creates my orthographic camera:
var cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.name = "Camera"
cameraNode.position = SCNVector3Make(-5.0, -5.0, 10.0)
cameraNode.eulerAngles = SCNVector3Make(PI / 3.0, 0.0, -PI / 4.0)
cameraNode.camera?.usesOrthographicProjection = true
cameraNode.camera?.orthographicScale = 7.0
scene.rootNode.addChildNode(cameraNode)
Now i want to move the camera using a pan gesture, producing a scroll feeling. To make this possible the camera shouldn't move vertically, only horizontally. The touch location on screen and the unprojected position in the 3D world should stay the same while moving.
I thought about calculating the 2D translation into 3D difference and ignoring the vertical component. This code actually works and almost produces the desired result, but the speed is not correct. If I pan, the camera seems to accelerate and not react correctly:
var previousTranslation = CGPointMake(0.0, 0.0)
func pan(gesture: UIPanGestureRecognizer)
{
let view = self.view as SCNView
let translation = gesture.translationInView(view)
let location = gesture.locationInView(view)
let diffTrans = translation - previousTranslation
previousTranslation = translation
let cameraNode = scene.rootNode.childNodeWithName("Camera", recursively: false)
let worldPointTrans = view.unprojectPoint(SCNVector3Make(-Float(diffTrans.x), -Float(diffTrans.y), 0.0))
let worldPoint0 = view.unprojectPoint(SCNVector3Make(0.0, 0.0, 0.0))
var diff = worldPointTrans - worldPoint0
diff.x = diff.x / Float(cameraNode!.camera!.orthographicScale)
diff.y = diff.y / Float(cameraNode!.camera!.orthographicScale)
diff.z = 0
cameraNode?.position += diff
}
Does anybody know a sophisticated way of calculating a screen translation into a horizontal 3D translation, ignoring the vertical axis?
Thank you in advance :)
EDIT: The pan works for horizontal translation now. But not for vertical, because I set the difference on the z axis to zero.
I found my own solution.!
I am calculating a ray at the start location of the gesture (P1-P2) and a ray at the translated location (Q1-Q2). Now I have two rays and I let both intersect with the XY Plane to receive the points P0 and Q0
The difference of P0 and Q0 is the unprojected translation.
This technique should also work with a non-orthogonal camera, but i didn't test this yet.
It seems to me that it works, but if anybody could mathematically confirm this assumption, I would be glad to read that :)
Here is the code:
var previousLocation = SCNVector3(x: 0, y: 0, z: 0)
func pan(gesture: UIPanGestureRecognizer)
{
let view = self.view as SCNView
let translation = gesture.translationInView(view)
let location = gesture.locationInView(view)
let secLocation = location + translation
let P1 = view.unprojectPoint(SCNVector3(x: Float(location.x), y: Float(location.y), z: 0.0))
let P2 = view.unprojectPoint(SCNVector3(x: Float(location.x), y: Float(location.y), z: 1.0))
let Q1 = view.unprojectPoint(SCNVector3(x: Float(secLocation.x), y: Float(secLocation.y), z: 0.0))
let Q2 = view.unprojectPoint(SCNVector3(x: Float(secLocation.x), y: Float(secLocation.y), z: 1.0))
let t1 = -P1.z / (P2.z - P1.z)
let t2 = -Q1.z / (Q2.z - Q1.z)
let x1 = P1.x + t1 * (P2.x - P1.x)
let y1 = P1.y + t1 * (P2.y - P1.y)
let P0 = SCNVector3Make(x1, y1,0)
let x2 = Q1.x + t1 * (Q2.x - Q1.x)
let y2 = Q1.y + t1 * (Q2.y - Q1.y)
let Q0 = SCNVector3Make(x2, y2, 0)
var diffR = Q0 - P0
diffR *= -1
let cameraNode = view.scene!.rootNode.childNodeWithName("Camera", recursively: false)
switch gesture.state {
case .Began:
previousLocation = cameraNode!.position
break;
case .Changed:
cameraNode?.position = previousLocation + diffR
break;
default:
break;
}
}
I've calculated the equations for the isometric panning, the code is below.
//camera pan ISOMETRIC logic
func pan(gesture: UIPanGestureRecognizer) {
let view = self.sceneView as SCNView
let cameraNode = view.scene!.rootNode.childNode(withName: "Camera", recursively: false)
let translation = gesture.translation(in: view)
let constant: Float = 30.0
var translateX = Float(translation.y)*sin(.pi/4.0)/cos(.pi/3.0)-Float(translation.x)*cos(.pi/4.0)
var translateY = Float(translation.y)*cos(.pi/4.0)/cos(.pi/3.0)+Float(translation.x)*sin(.pi/4.0)
translateX = translateX / constant
translateY = translateY / constant
switch gesture.state {
case .began:
previousLocation = cameraNode!.position
break;
case .changed:
cameraNode?.position = SCNVector3Make((previousLocation.x + translateX), (previousLocation.y + translateY), (previousLocation.z))
break;
default:
break;
}
}
And to get scaling right, you need to use screenheight as a variable for orthographicScale. The scaling i used here is 30x magnification, note the 30 is also used for the constant in the code above.
let screenSize: CGRect = UIScreen.main.bounds
let screenHeight = screenSize.height
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.name = "Camera"
let cameraDist = Float(20.0)
let cameraPosX = cameraDist*(-1.0)*cos(.pi/4.0)*cos(.pi/6.0)
let cameraPosY = cameraDist*(-1.0)*sin(.pi/4.0)*cos(.pi/6.0)
let cameraPosZ = cameraDist*sin(.pi/6)
cameraNode.position = SCNVector3Make(cameraPosX, cameraPosY, cameraPosZ)
cameraNode.eulerAngles = SCNVector3Make(.pi / 3.0, 0.0, -.pi / 4.0)
cameraNode.camera?.usesOrthographicProjection = true
cameraNode.camera?.orthographicScale = Double(screenHeight)/(2.0*30.0) //30x magnification constant. larger number = larger object
scene.rootNode.addChildNode(cameraNode)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With