swift 如果高度Y已知,如何在scenekit中获取射线X Z的坐标

ztmd8pv5  于 11个月前  发布在  Swift
关注(0)|答案(1)|浏览(76)

hittest方法假定与某些对象相交。我需要在不与对象相交的情况下通过点击scnview来获取世界坐标。这个地方没有物体,但我必须把它放在一定的高度。如何通过点击计算X和Z坐标?

lvmkulzt

lvmkulzt1#

一种方法是在所需的y位置创建一个临时的透明xz平面,并在命中测试中使用它。这有局限性,因为您无法创建覆盖整个场景的xz平面。此外,如果相机从xz平面的边缘观看场景,则无法获得x和z世界坐标。
下面是一些示例代码。我在handleTap(recognizer:)中创建了一个清晰的xz-plane,并在renderer(updateAtTime time:)中执行了命中测试,然后删除了xz-plane。我必须在renderer中执行命中测试,因为xz平面直到下一个render循环才生效。

import UIKit
import QuartzCore
import SceneKit

struct Constants {
    static let planeWidth: CGFloat = 8
    static let planeHeight: CGFloat = 8
}

class GameViewController: UIViewController, SCNSceneRendererDelegate {
    
    var scnView: SCNView!
    var scnScene: SCNScene!
    var cameraNode: SCNNode!
    var xzPlaneNode: SCNNode?
    var tapLocation = CGPoint.zero
    var hud = Hud()

    var someY: CGFloat = 1.0

    override func viewDidLoad() {
        super.viewDidLoad()
        setupView()
        setupScene()
        setupCamera()
        setupLighting()
        setupHud()

        addSphereAt(yPos: someY, color: .red)
        
        // gray colored plane for illustrative purposes, only (not used in hitTest)
        _ = addXZPlaneAt(yPos: someY, color: .lightGray.withAlphaComponent(0.5))

        let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap))
        scnView.addGestureRecognizer(tap)
    }
    
    func addSphereAt(yPos: CGFloat, color: UIColor) {
        let sphere = SCNNode(geometry: SCNSphere(radius: 0.1))
        sphere.geometry?.firstMaterial?.diffuse.contents = color
        sphere.position = SCNVector3(-1, yPos, 2)
        scnScene.rootNode.addChildNode(sphere)
    }
    
    func addXZPlaneAt(yPos: CGFloat, color: UIColor) -> SCNNode {
        let plane = SCNNode(geometry: SCNPlane(width: Constants.planeWidth, height: Constants.planeHeight))
        plane.geometry?.firstMaterial?.diffuse.contents = color
        plane.geometry?.firstMaterial?.isDoubleSided = true
        plane.transform = SCNMatrix4Rotate(plane.transform, -.pi / 2, 1, 0, 0)  // rotate horizontally
        plane.position = SCNVector3(0, yPos, 0)
        scnScene.rootNode.addChildNode(plane)
        return plane
    }
    
    @objc func handleTap(recognizer: UITapGestureRecognizer) {
        xzPlaneNode = addXZPlaneAt(yPos: someY, color: .clear)
        xzPlaneNode?.name = "xzPlane"
        tapLocation = recognizer.location(in: scnView)
    }
    
    func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
        if xzPlaneNode != nil {
            let hitResults = scnView.hitTest(tapLocation, options: [.searchMode: SCNHitTestSearchMode.all.rawValue])
            if let result = hitResults.first(where: { $0.node.name == "xzPlane" }) {
                doSomethingWith(worldCoords: result.worldCoordinates)
            } else {
                hud.clear()
            }
            xzPlaneNode?.removeFromParentNode()
            xzPlaneNode = nil
        }
    }
    
    func doSomethingWith(worldCoords: SCNVector3) {
        hud.worldCoords = worldCoords
    }
    
    // MARK: - Setup functions
    
    func setupView() {
        scnView = self.view as? SCNView
        scnView.backgroundColor = UIColor(red: 0.9, green: 1, blue: 0.9, alpha: 1)
        scnView.allowsCameraControl = true
        scnView.showsStatistics = false
        scnView.autoenablesDefaultLighting = true
        scnView.isPlaying = true
    }
    
    func setupScene() {
        scnScene = SCNScene()
        scnView.scene = scnScene
        scnView.delegate = self  // needed for renderer
    }
    
    func setupCamera() {
        cameraNode = SCNNode()
        cameraNode.camera = SCNCamera()
        cameraNode.position = SCNVector3(0, 0, 10)
        scnScene.rootNode.addChildNode(cameraNode)
    }
    
    func setupLighting() {
        let lightNode = SCNNode()
        lightNode.light = SCNLight()
        lightNode.light?.type = .ambient
        cameraNode.addChildNode(lightNode)  // move light with camera
    }
    
    func setupHud() {
        hud = Hud(size: view.bounds.size)
        hud.setup()
        scnView.overlaySKScene = hud
    }
}

这是HUD代码

import SpriteKit
import SceneKit

class Hud: SKScene {

    var worldCoords: SCNVector3! {
        didSet {
            CoordsLabel.text = String(format: "World Coords = (x: %.1f, y: %.1f, z: %.1f)", worldCoords.x, worldCoords.y, worldCoords.z)
        }
    }

    let CoordsLabel = SKLabelNode(fontNamed: "Menlo-Bold")

    func setup() {
        CoordsLabel.position = CGPoint(x: 0.5 * frame.width, y: 0.2 * frame.height)
        CoordsLabel.fontSize = 16
        CoordsLabel.fontColor = UIColor.black
        addChild(CoordsLabel)
        worldCoords = SCNVector3(-1, 2, 3)
    }
    
    func clear() {
        CoordsLabel.text = "World Coords =         Unknown         ."
    }
}

带灰色平面

我添加了一个灰色的平面,所以您可以看到我上面提到的限制(它不在命中测试中使用)。灰色平面将不包括在真实的代码中。在透明平面上的所有点击返回与红色球体相同的y世界坐标(y = 1.0)。

无灰面

相关问题