简体   繁体   English

带有自定义相机的 SceneKit unprojectPoint()

[英]SceneKit unprojectPoint() with custom camera

I am developing a molecular visualizer for macOS / iPadOS with SceneKit.我正在使用 SceneKit 为 macOS / iPadOS 开发分子可视化工具。 Long story short, I want that when the user clicks (or touches) the screen at a certain position, a new atom is placed (in this example just a SCNSphere).长话短说,我希望当用户在某个 position 处单击(或触摸)屏幕时,会放置一个新原子(在此示例中只是一个 SCNSphere)。

Previously, I had the allowsCameraControl property of the SCNView active, which allowed me to freely move the camera and with the unprojectPoint() method, I could successfully place a new node at touch location.以前,我激活了 SCNView 的 allowCameraControl 属性,这使我可以自由移动相机,并且使用 unprojectPoint() 方法,我可以成功地将新节点放置在触摸位置。 The limitation of the default camera controller is that it does not zoom.默认相机 controller 的限制是它不能缩放。 When you pinch the screen, it changes the FOV property of the camera instead of moving it through the Z axis.当您捏住屏幕时,它会更改相机的 FOV 属性,而不是通过 Z 轴移动它。

Therefore, I made a custom camera node with a SCNCamera.因此,我使用 SCNCamera 制作了一个自定义相机节点。 I succesfully recreated the default camera behaviour (movement, rotation) and furthermore I am able to correclty zoom into the scene.我成功地重新创建了默认的相机行为(移动、旋转),而且我能够正确放大场景。 The downside of this is that the unprojectPoint() method no longer works as expeced, as the new nodes are placed at a very close position of the camera node itself.这样做的缺点是 unprojectPoint() 方法不再像预期的那样工作,因为新节点放置在非常接近相机节点本身的 position 处。 No matter where I click on the scene, that the unprojected point will always be very close to 0, 0, 10无论我点击场景的哪个位置,那个未投影的点总是非常接近 0、0、10

internal func newNodeAt(point: CGPoint) {
        let pointVector = SCNVector3(point.x, point.y, 0.8)
        let position = self.unprojectPoint(pointVector)
        
        print("x:\(position.x), y: \(position.y), z: \(position.z)")
        
        let newSphere = SCNSphere(radius: 1)
        let newNode = SCNNode(geometry: newSphere)
        
        self.scene?.rootNode.addChildNode(newNode) 
}

The camera node is setup as folows and its directly attached to the scene root node.摄像机节点设置如下,并直接附加到场景根节点。

    internal func setupCameraNode() -> SCNNode {
        let cam = SCNCamera()
        cam.name = "camera"
        cam.zFar = 200
        cam.zNear = 0.1
        let camNode = SCNNode()
        camNode.camera = cam
        camNode.position = SCNVector3(0, 0, 5)
        camNode.name = "Camera node"
        return camNode
    }

These are the printed positions after clicking on random positions of the scene.这些是单击场景的随机位置后的打印位置。

x:-0.1988764852285385,  y: -0.05589345842599869, z: 10.920427322387695
x:-0.18989555537700653, y:  0.14564114809036255, z: 10.920427322387695
x: 0.2168566882610321,  y:  0.13085339963436127, z: 10.920427322387695
x: 0.24202580749988556, y: -0.15493911504745483, z: 10.920427322387695
x:-0.06516486406326294, y: -0.1781780868768692,  z: 10.920427322387695
x:-0.08134553581476212, y:  0.12478446960449219, z: 10.920427322387695
x:-0.25866374373435974, y:  0.1456427276134491,  z: 10.920427322387695
x: 0.217658132314682,   y:  0.16270162165164948, z: 10.920427322387695
x: 0.2053154855966568,  y: -0.12679903209209442, z: 10.920427322387695

I suppose that the unprojectPoint() is somehow related to the point of view?我想 unprojectPoint() 与观点有关吗? But I do not know how to fix this.但我不知道如何解决这个问题。 Thanks.谢谢。

I think you are on the right track, you just have to provide some kind of depth reference for the user.我认为你在正确的轨道上,你只需要为用户提供某种深度参考。 This is my code for similar, but when I call airStrike, I deal with the depth based on a plane facing the user and that's how I know where Z needs to be.这是我的类似代码,但是当我调用 airStrike 时,我根据面向用户的平面处理深度,这就是我知道 Z 需要在哪里的方式。

Just a guess without a visual, but seems there are a couple of options.只是没有视觉的猜测,但似乎有几个选择。 Create a reference plane in the middle of the molecule and ++/-- that to show where the tap will land from a depth perpective.在分子中间创建一个参考平面和 ++/-- 以显示水龙头将从深度角度降落的位置。

Or just let them put it anywhere, then select it and depth++/depth-- to get it in the right position.或者只是让他们把它放在任何地方,然后是 select 它和 depth++/depth——把它放在正确的 position 中。

@objc func handleTap(recognizer: UITapGestureRecognizer)
{
 let location: CGPoint = recognizer.location(in: gameScene)
            
 if(data.isAirStrikeModeOn == true)
 {
  let projectedPoint = gameScene.projectPoint(SCNVector3(0, 0, 0))
  let scenePoint = gameScene.unprojectPoint(SCNVector3(location.x, location.y, CGFloat(projectedPoint.z)))
  gameControl.airStrike(position: scenePoint)
 }
}

After days of testing I figured out a workaround and now I can place the nodes correctly where they should be.经过几天的测试,我找到了一种解决方法,现在我可以将节点正确放置在它们应该在的位置。

My node tree was is like this:我的节点树是这样的:

  • RootNode根节点
    • CameraNode相机节点
    • atomNodes原子节点
      • atom (individual spheres)原子(单个球体)

Therefore, all I had to do was to convert the unprojected position from the RootNode (which I suppose is the one that the camera takes the reference from) to the atomNodes, thus:因此,我所要做的就是将未投影的 position 从 RootNode(我想是相机从中获取参考的那个)转换为 atomNodes,因此:

let unprojected = unprojectPoint(SCNVector3(location.x, location.y, 0.99))                                     
let position = atomNodes.convertPosition(unprojected, from: rootNode)

The 0.99 is just a nice Z position in my view for the spheres to be placed.在我看来,0.99 只是一个不错的 Z position 用于放置球体。 (More info here ) (更多信息在这里

My advice would be to always check the node tree because the positions are relative to each other.我的建议是始终检查节点树,因为位置是相对的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM