Reputation: 38
I have a 3D interactive globe built with SceneKit where countries are represented with dots. The function below takes a position and animates the camera to it.
If the user does not interact with the globe, then I am able to continuously call the function and animate the camera to the new position.
However, if the user performs any gesture on the scene, then the camera animation doesn't work.
A solution found in a different SO thread (linked below) used the line sceneView.pointOfView = cameraNode
at the beginning of the function.
This did solve the issue of the camera not animating after a gesture.
However, this line causes the globe to reset to its original position before animating. I have been trying to figure out a way to bypass this scene reset, but have had no luck.
I assume performing a gesture on the globe creates a new point of view for the scene and overrides the camera's point of view. Therefore, setting the scene's point of view back to the camera before the animation resolves the issue.
import Foundation
import SceneKit
import CoreImage
import SwiftUI
import MapKit
public typealias GenericController = UIViewController
public class GlobeViewController: GenericController {
var nodePos: CGPoint? = nil
public var earthNode: SCNNode!
private var sceneView : SCNView!
private var cameraNode: SCNNode!
private var dotCount = 50000
public init(earthRadius: Double) {
self.earthRadius = earthRadius
super.init(nibName: nil, bundle: nil)
}
public init(earthRadius: Double, dotCount: Int) {
self.earthRadius = earthRadius
self.dotCount = dotCount
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func centerCameraOnDot(dotPosition: SCNVector3) {
sceneView.pointOfView = cameraNode //HERE RESETS
let fixedDistance: Float = 5.0
let newCameraPosition = dotPosition.normalized().scaled(to: fixedDistance)
let moveAction = SCNAction.move(to: newCameraPosition, duration: 1.5)
let constraint = SCNLookAtConstraint(target: earthNode)
constraint.isGimbalLockEnabled = true
sceneView.gestureRecognizers?.forEach { $0.isEnabled = false }
SCNTransaction.begin()
SCNTransaction.animationDuration = 1.5
self.cameraNode.constraints = [constraint]
self.cameraNode.runAction(moveAction) {
DispatchQueue.main.async {
self.sceneView.gestureRecognizers?.forEach { $0.isEnabled = true }
}
}
SCNTransaction.commit()
}
public override func viewDidLoad() {
super.viewDidLoad()
setupScene()
setupParticles()
setupCamera()
setupGlobe()
setupDotGeometry()
}
private func setupScene() {
let scene = SCNScene()
sceneView = SCNView(frame: view.frame)
sceneView.scene = scene
sceneView.showsStatistics = true
sceneView.backgroundColor = .clear
sceneView.allowsCameraControl = true
sceneView.isUserInteractionEnabled = true
self.view.addSubview(sceneView)
}
private func setupParticles() {
guard let stars = SCNParticleSystem(named: "StarsParticles.scnp", inDirectory: nil) else { return }
stars.isLightingEnabled = false
if sceneView != nil {
sceneView.scene?.rootNode.addParticleSystem(stars)
}
}
private func setupCamera() {
self.cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: 0, y: 0, z: 5)
sceneView.scene?.rootNode.addChildNode(cameraNode)
}
private func setupGlobe() {
self.earthNode = EarthNode(radius: earthRadius, earthColor: earthColor, earthGlow: glowColor, earthReflection: reflectionColor)
sceneView.scene?.rootNode.addChildNode(earthNode)
}
private func setupDotGeometry() {
let textureMap = generateTextureMap(dots: dotCount, sphereRadius: CGFloat(earthRadius))
let newYork = CLLocationCoordinate2D(latitude: 44.0682, longitude: -121.3153)
let newYorkDot = closestDotPosition(to: newYork, in: textureMap)
let dotColor = GenericColor(white: 1, alpha: 1)
let oceanColor = GenericColor(cgColor: UIColor.systemRed.cgColor)
let highlightColor = GenericColor(cgColor: UIColor.systemRed.cgColor)
// threshold to determine if the pixel in the earth-dark.jpg represents terrain (0.03 represents rgb(7.65,7.65,7.65), which is almost black)
let threshold: CGFloat = 0.03
let dotGeometry = SCNSphere(radius: dotRadius)
dotGeometry.firstMaterial?.diffuse.contents = dotColor
dotGeometry.firstMaterial?.lightingModel = SCNMaterial.LightingModel.constant
let highlightGeometry = SCNSphere(radius: dotRadius)
highlightGeometry.firstMaterial?.diffuse.contents = highlightColor
highlightGeometry.firstMaterial?.lightingModel = SCNMaterial.LightingModel.constant
let oceanGeometry = SCNSphere(radius: dotRadius)
oceanGeometry.firstMaterial?.diffuse.contents = oceanColor
oceanGeometry.firstMaterial?.lightingModel = SCNMaterial.LightingModel.constant
var positions = [SCNVector3]()
var dotNodes = [SCNNode]()
var highlightedNode: SCNNode? = nil
for i in 0...textureMap.count - 1 {
let u = textureMap[i].x
let v = textureMap[i].y
let pixelColor = self.getPixelColor(x: Int(u), y: Int(v))
let isHighlight = u == newYorkDot.x && v == newYorkDot.y
if (isHighlight) {
let dotNode = SCNNode(geometry: highlightGeometry)
dotNode.name = "NewYorkDot"
dotNode.position = textureMap[i].position
positions.append(dotNode.position)
dotNodes.append(dotNode)
print("myloc \(textureMap[i].position)")
highlightedNode = dotNode
} else if (pixelColor.red < threshold && pixelColor.green < threshold && pixelColor.blue < threshold) {
let dotNode = SCNNode(geometry: dotGeometry)
dotNode.name = "Other"
dotNode.position = textureMap[i].position
positions.append(dotNode.position)
dotNodes.append(dotNode)
}
}
DispatchQueue.main.async {
let dotPositions = positions as NSArray
let dotIndices = NSArray()
let source = SCNGeometrySource(vertices: dotPositions as! [SCNVector3])
let element = SCNGeometryElement(indices: dotIndices as! [Int32], primitiveType: .point)
let pointCloud = SCNGeometry(sources: [source], elements: [element])
let pointCloudNode = SCNNode(geometry: pointCloud)
for dotNode in dotNodes {
pointCloudNode.addChildNode(dotNode)
}
self.sceneView.scene?.rootNode.addChildNode(pointCloudNode)
//performing gestures before this causes the bug
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
if let highlightedNode = highlightedNode {
self.centerCameraOnDot(dotPosition: highlightedNode.position)
}
}
}
}
}
Upvotes: 1
Views: 147