Reputation: 1491
I am really bad at explaining these situations so bear with me.
What I want is when the user taps the white annotation, that point will scroll to the center (along with the globe)
I would also like to be able to do this programmatically, scrolling to a point when i provide x/y coords for the globe
I am using the following function to calculate the SCNVector3 based on x/y coordinates
func positionForCoordinates(coordinates: CGPoint, radius: CGFloat) -> SCNVector3 {
let s = coordinates.x
let t = coordinates.y
let r = radius
let x = r * cos(s) * sin(t)
let y = r * sin(s) * sin(t)
let z = r * cos(t)
return SCNVector3(x: Float(x), y: Float(y), z: Float(z))
}
its the math that really is eluding me.
Upvotes: 2
Views: 715
Reputation: 2601
Let's assume according to your problem, we knew the following
We can take advantage of animatable properties of SCNNode
and SCNCamera
so this will make it easier for us to do animation and not to manually lerp values in render loop.
func setupCamera(scene: SCNScene) {
cameraOrbit = SCNNode()
cameraNode = SCNNode()
camera = SCNCamera()
// camera stuff
camera.usesOrthographicProjection = true
camera.orthographicScale = 10
camera.zNear = 1
camera.zFar = 100
// initially position is far away as we will animate moving into the globe
cameraNode.position = SCNVector3(x: 0, y: 0, z: 70)
cameraNode.camera = camera
cameraOrbit = SCNNode()
cameraOrbit.addChildNode(cameraNode)
scene.rootNode.addChildNode(cameraOrbit)
}
Camera node is set with SCNCamera
instance via its camera
property, and is wrapped inside another node as we will use to manipulate its rotation.
I left code for defining those variables in the class for brevity.
We need a method that convert map coordinate (latitude/longitude) to rotation angles for us to plug it into SCNNode.eulerAngles
in order to rotate our camera around the globe model.
/**
Get rotation angle of sphere along x and y direction from input map coordinate to show such location at the center of view.
- Parameter from: Map coordinate to get rotation angle for sphere
- Returns: Tuple of rotation angle in form (x:, y:)
*/
func rotationXY(from coordinate: CLLocationCoordinate2D) -> (x: Double, y: Double) {
// convert map coordiante to texture coordinate
let v = 0.5 - (coordinate.latitude / 180.0)
let u = (coordinate.longitude / 360.0) + 0.5
// convert texture coordinate to rotation angles
let angleX = (u-0.5) * 2 * Double.pi
let angleY = (0.5-v) * -Double.pi
return (x: angleX, y: angleY)
}
From the code, we need to convert from map coordinate to texture coordinate in which when you place a texture ie. diffuse texture onto a sphere to render normally without any modification of rotation angle of globe node, and camera's position is placed along z-axis (ie. x=0, y=0, z=N), the center of such texture will be shown at the camera. So in the equation, we take into account 0.5
to accommodate on this.
After that we convert results into angles (radians). Along x direction, we could rotate sphere for 360 degrees to wrap around it fully. For y direction, it takes 180 degrees to wrap around it.
Please note I didn't get rid of
0.5
for both case as to make it as-is in each conversion, and for clearer to see along with explanation. You can simplify it by removing from the code.
As a plus, I will include zoom level as well.
Let's assume that you allow zooming in level of 0.0 to 10.0. We use orthographicScale
property of SCNNode
to simulate zooming in for orthographic type of camera we've set up above. In contrast, orthographicScale
is inverse of zoom level in normal understanding. So at zoom level of 10.0, orthographicScale
will be 0.0 in order to achieve zoom-in effect.
/**
Rotate camera around globe to specified coordinate.
- Parameter to: Location in coordinate (latitude/longitude)
- Parameter zoomLevel: Zoom level in range 0.0 - 10.0.
- Parameter duration: Duration for rotation
- Parameter completion: Delegate when rotation completes to notify back to user
*/
func flyGlobeTo(to location: CLLocationCoordinate2D, zoomLevel: Double, duration: Double, completion: (()->Void)?=nil) {
// make a call to our conversion method
let rotation = self.rotationXY(from: location)
SCNTransaction.begin()
SCNTransaction.animationDuration = duration
SCNTransaction.completionBlock = {
completion?()
}
self.cameraOrbit.eulerAngles.x = Float(rotation.y)
self.cameraOrbit.eulerAngles.y = Float(rotation.x)
self.camera.orthographicScale = 10.0 - zoomLevel // calculate value from inverse from orthographicScale
SCNTransaction.commit()
}
And that's it. Whenever you need to fly to a target coordinate on the map, just use flyGlobeTo()
method. Make sure you call it in main thread. Animation is done via SCNTransaction
and not through UIView.animate
, obviously it's different technology but I note it here too as first time I did use the latter myself.
Upvotes: 2