I have a basic 3d interactive globe. I’m using the function centerCameraOnDot to move the camera to center on different parts of the globe. When I run the code and call centerCameraOnDot everything runs smoothly. I can even run the function multiple times in a row on different locations and it will animate to each one correctly. However if I perform any gesture on the view stop and then call the function after a few seconds the camera never animates to the new location. I did some debugging and the function does execute and newCameraPosition is calculated correctly. However the camera action doesn’t perform. This may be a result of gestures modifying the state of the scene/camera. How do I go about performing these actions regardless of previous gestures.
I tried reinitializing the view and running the function and this obviously works but is not practical.
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) {
let fixedDistance: Float = 6.0
let newCameraPosition = dotPosition.normalized().scaled(to: fixedDistance)
// Position animation
let moveAction = SCNAction.move(to: newCameraPosition, duration: 1.5)
// Set up lookAt constraint for orientation
let constraint = SCNLookAtConstraint(target: earthNode)
constraint.isGimbalLockEnabled = true
// Animate the transition
SCNTransaction.begin()
SCNTransaction.animationDuration = 1.5
cameraNode.constraints = [constraint]
cameraNode.runAction(moveAction)
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)
}
}
}
}
}




