我有一个互动的地球,我想要检测地球上的点击,这样我就可以获得他们的3D位置.

当我覆盖函数100时,我能够检测到手势何时开始,并且从touches'参数中我可以获得地球上点击的3D位置.

问题是,这个函数可以从任何手势(拖动、放大等)执行,但我只想在单击或点击时执行一些操作.

我可以将点击手势添加到场景视图中,但从点击手势调用的函数不能为我提供点击在地球内的3D位置.

Is there any other function I can override to detect only taps while getting touches: Set?
If not, is there any way I can filter out the touches to only tap gestures?

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")
    }

    public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let touch = touches.first, touch.view == self.sceneView else {
            return
        }

        let touchLocation = touch.location(in: sceneView)
        let hitTestResults = sceneView.hitTest(touchLocation, options: nil)
        
        let touchLocation3D = sceneView.unprojectPoint(SCNVector3(Float(touchLocation.x), Float(touchLocation.y), 0.0))
        
        print(touchLocation3D)
        
        if let tappedNode = hitTestResults.first?.node {
            print(tappedNode)
            // Handle the tapped node
            if tappedNode.name == "NewYorkDot" {
                // This is the New York dot, perform your action here
                print("Tapped on New York! Position: \(tappedNode.position)")
            } else if tappedNode.name == "RegularDot" {
                // Handle other nodes if needed
                print("Tapped on a regular dot. Position: \(tappedNode.position)")
            }
        }
    }

    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)
            
        }
   }
}


typealias GenericControllerRepresentable = UIViewControllerRepresentable

@available(iOS 13.0, *)
private struct GlobeViewControllerRepresentable: GenericControllerRepresentable {
    var particles: SCNParticleSystem? = nil

    func makeUIViewController(context: Context) -> GlobeViewController {
        let globeController = GlobeViewController(earthRadius: 1.0)
        updateGlobeController(globeController)
        return globeController
    }
    
    func updateUIViewController(_ uiViewController: GlobeViewController, context: Context) { }
}

@available(iOS 13.0, *)
public struct GlobeView: View {
    public var body: some View {
        GlobeViewControllerRepresentable()
    }
}

推荐答案

使用ColdLogicadvice,使用UITapGestureRecognizer来检测SceneKit中的点击手势确实可能是您的场景的推荐方法.

You attach a UITapGestureRecognizer to the SCNView. That recognizer is specifically for detecting tap gestures.
In the handleTap method, you perform a hit test using the tap location. That is to find the 3D coordinates of the tap on the globe.
The hit test results give you the 3D position, which is what you are interested in for your interactive globe.

[User Interaction] 
       |
   [Tap Gesture]
       |
[handleTap Method] --> [Hit Test on sceneView]
       |
[3D Position Calculation]
       |
[Handle 3D Tap Position]

这样,您就不必使用touchesBegan手动解析touch 事件,对于此特定用例而言,这可能会更加复杂且效率较低.

使用点击测试结果来获得地球上的水龙头的3D位置:您的GlobeViewController类将是:

// Previous Code

public class GlobeViewController: GenericController {
    // Existing properties and initializers

    public override func viewDidLoad() {
        super.viewDidLoad()
        setupScene()
        
        // Other setup methods

        // Add Tap Gesture Recognizer
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
        sceneView.addGestureRecognizer(tapGesture)
    }

    @objc func handleTap(_ gestureRecognize: UIGestureRecognizer) {
        let p = gestureRecognize.location(in: sceneView)
        let hitResults = sceneView.hitTest(p, options: [:])
        
        if let hitResult = hitResults.first {
            let position3D = hitResult.worldCoordinates
            // Use the 3D position as needed
            print("3D Tap Position: \(position3D)")
        }
    }
    
    // Rest of the class
}
  • UITapGestureRecognizer用于检测sceneView上的轻拍.
  • 当识别到轻击时,触发handleTap.它在点击位置执行点击测试,并获得第一个结果.
  • hitResult.worldCoordinates表示水龙头在地球上的3D坐标.

这应该会专门检测点击,并在不被拖动或放大等其他手势触发的情况下获得它们的3D位置.


为了扩展handleTap功能,如果在点击位置有几个对象,则命中测试结果可以包括多个命中.根据应用程序的需要,可以遍历这些结果以查找所需的 node 或第一个命中 node .

@objc func handleTap(_ gesture: UITapGestureRecognizer) {
    let touchLocation = gesture.location(in: sceneView)
    let hitTestResults = sceneView.hitTest(touchLocation, options: nil)

    hitTestResults.forEach { result in
        let position3D = result.worldCoordinates
        // Use the 3D position as needed
        print("Tapped position in 3D: \(position3D)")

        // Example: Check if a specific node was tapped
        if let tappedNode = result.node {
            print("Tapped on node: \(tappedNode.name ?? "unknown")")
            // Perform actions specific to the tapped node
        }
    }
}

The forEach loop iterates over each hit test result. result.worldCoordinates provides the 3D coordinates of the hit location.
You can also access the node property of each hit result to perform node-specific actions.

Ios相关问答推荐

带有动画层的UIView表示不会在工作表中设置动画

在Swift5中,将某些属性值从一个类复制到另一个类实例

为什么Reaction-Native-Vision-Camera不能与闪光灯一起使用?

如何在SwiftUI中扩展双击的可检测区域?

UITableViewCell编程约束问题

为什么EKEventStore().questFullAccessToEvents()可以与模拟器一起使用,而不能与真实设备一起使用?

如何在KMM项目中处理PHPickerViewController的回调?

如何使用 SwiftData 从列表中删除子项目?

如何在Swift中同时实现三个手势?

当应用程序被杀死时是否还能获取位置?

使用 Objective-C 创建 iOS 框架

在 Swift 项目中使用情节提要加载 obj-c 文件

react 在 android 上运行的本机应用程序,但在 iOS 上出现问题,详细信息在描述中共享

TextField 重复输入 SwiftUI

在 Swift 中为 UITextField/UIView 摇动动画

检测 iOS UIDevice 方向

在 iOS 7 上更改标签栏色调 colored颜色

iOS 7 半透明模态视图控制器

iPhone:什么是 WWDR 中级证书?

使用 xib 创建可重用的 UIView(并从情节提要中加载)