在视图上执行手势会阻止相机动作

问题描述 投票:0回答:1

我有一个基本的 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)
                }
            }
        }
   }
}
ios swift swiftui scenekit scnnode
1个回答
0
投票

视图上的手势可能会改变相机或场景的状态,从而阻止相机在调用

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建议另一种方法:

如果您只想在特定手势期间发生特定的事情,则必须使用手势识别器。

您可以通过检查点击次数、触摸状态和其他因素,使用 TouchBegan 完成同样的事情。但使用
UIPanGestureRecognizer

UITapGestureRecognizer
 更容易。

在 SceneKit 应用程序中使用

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

 功能的问题。

© www.soinside.com 2019 - 2024. All rights reserved.