hittest方法假定与某些对象相交。我需要在不与对象相交的情况下通过点击scnview来获取世界坐标。这个地方没有物体,但我必须把它放在一定的高度。如何通过点击计算X和Z坐标?
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)。
无灰面
1条答案
按热度按时间lvmkulzt1#
一种方法是在所需的y位置创建一个临时的透明xz平面,并在命中测试中使用它。这有局限性,因为您无法创建覆盖整个场景的xz平面。此外,如果相机从xz平面的边缘观看场景,则无法获得x和z世界坐标。
下面是一些示例代码。我在handleTap(recognizer:)中创建了一个清晰的xz-plane,并在renderer(updateAtTime time:)中执行了命中测试,然后删除了xz-plane。我必须在renderer中执行命中测试,因为xz平面直到下一个render循环才生效。
这是HUD代码
带灰色平面
我添加了一个灰色的平面,所以您可以看到我上面提到的限制(它不在命中测试中使用)。灰色平面将不包括在真实的代码中。在透明平面上的所有点击返回与红色球体相同的y世界坐标(y = 1.0)。
无灰面