我有一个基本的 3D 交互式地球仪。我使用函数 centerCameraOnDot 将相机移动到地球不同部分的中心。当我运行代码并调用 centerCameraOnDot 时,一切都运行顺利。
我什至可以在不同位置连续多次运行该函数,并且它会正确地为每个位置生成动画。
但是,如果我在视图停止处执行任何手势,然后在几秒钟后调用该函数,则相机永远不会动画到新位置。
我做了一些调试,该函数确实执行并且 newCameraPosition 计算正确。
但是,相机动作不执行。
这可能是手势修改场景/相机状态的结果。
无论之前的手势如何,我该如何执行这些操作?
我尝试重新初始化视图并运行该函数,这显然有效,但不实用。
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)
}
}
}
}
}
视图上的手势可能会改变相机或场景的状态,从而阻止相机在调用
centerCameraOnDot
时动画到新位置。
在运行
centerCameraOnDot
函数之前,您应该确保对相机或场景的任何与手势相关的修改都已重置或正确处理。
在将相机设置为新位置之前,请重置可能因手势而改变的任何变换或约束。这可能就像将相机设置为已知状态或删除手势添加的约束一样简单。
并检查是否有任何活动手势。如果有,您可能需要等到它们完成或强制结束它们。
相机动画开始时暂时禁用手势识别器,并在动画完成后重新启用它们。这可以防止相机移动期间出现任何手势干扰。
func centerCameraOnDot(dotPosition: SCNVector3) {
let fixedDistance: Float = 6.0
let newCameraPosition = dotPosition.normalized().scaled(to: fixedDistance)
// Disable gestures here
sceneView.gestureRecognizers?.forEach { $0.isEnabled = false }
// Reset camera state if needed
// e.g., cameraNode.transform = SCNMatrix4Identity
// 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) {
// Re-enable gestures here
self.sceneView.gestureRecognizers?.forEach { $0.isEnabled = true }
}
SCNTransaction.commit()
}
ColdLogic建议另一种方法:
如果您只想在特定手势期间发生特定的事情,则必须使用手势识别器。在 SceneKit 应用程序中使用您可以通过检查点击次数、触摸状态和其他因素,使用 TouchBegan 完成同样的事情。但使用
或UIPanGestureRecognizer
UITapGestureRecognizer
更容易。
UIPanGestureRecognizer
或
UITapGestureRecognizer
等手势识别器可以更好地控制用户交互如何影响场景和相机。通过使用手势识别器,您可以更轻松地在相机动画期间禁用或修改手势交互。例如,您可以在相机动画时禁用平移手势识别器,以防止意外干扰。
override func viewDidLoad() {
super.viewDidLoad()
setupScene()
// Other setup methods
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:)))
sceneView.addGestureRecognizer(panGesture)
}
@objc func handlePanGesture(_ gesture: UIPanGestureRecognizer) {
// Handle the pan gesture
if gesture.state == .began {
// Optionally disable camera animation here
} else if gesture.state == .changed {
// Update camera or scene based on pan movement
} else if gesture.state == .ended {
// Optionally re-enable camera animation here
}
}
这种方法可以帮助防止您遇到手势干扰 centerCameraOnDot
功能的问题。