iOS SceneKit到UIView投影问题

3

我有一个SCNView,里面包含了一个位于相机根节点前方的SCNPlane几何形状的SCNNode。

在UIView中展示SCNView时,我添加了一些UIImages(标记)- 橙色圆圈。

在Motion监听器中,我试图将这些标记定位在平面每个边缘的中心位置。

当设备处于直立状态时,正确的标记对齐方式如下所示: A proper Markers alignment - while device is in straight position

我是通过从SceneKit对象到UIView的投影来实现这一点的:

//world coordinates
let v1w =  sm.node.convertPosition(sm.node.boundingBox.min, 
    to: self.sceneView.scene?.rootNode)
let v2w =  sm.node.convertPosition(sm.node.boundingBox.max, 
    to: self.sceneView.scene?.rootNode)

//projected coordinates
let v1p = self.sceneView.projectPoint(v1w)
let v2p = self.sceneView.projectPoint(v2w)

//frame rectangle
let rect = CGRect.init(x: CGFloat(v1p.x), y: CGFloat(v2p.y), 
    width: CGFloat(v2p.x - v1p.x), height: CGFloat(v1p.y - v2p.y))

var frameOld = sm.marker.frame

switch sm.position
{
case .Top:
    frameOld.origin.y = rect.minY - frameOld.size.height/2
    frameOld.origin.x = rect.midX - frameOld.size.width/2
case .Bottom:
    frameOld.origin.y = rect.maxY - frameOld.size.height/2
    frameOld.origin.x = rect.midX - frameOld.size.width/2
case .Left:
    frameOld.origin.y = rect.midY - frameOld.size.height/2
    frameOld.origin.x = rect.minX - frameOld.size.width/2
case .Right:
    frameOld.origin.y = rect.midY - frameOld.size.height/2
    frameOld.origin.x = rect.maxX - frameOld.size.width/2
}

sm.marker.frame = frameOld
self.view.layoutSubviews()

你可以在这里找到类似的方法:Scene Kit: projectPoint calculated is displaced

我希望这些标记能够在设备运动期间固定在平面边缘。但问题是:当旋转设备时,标记会从平面边缘漂移当旋转设备时,标记会从平面边缘漂移

查看问题视频:https://youtu.be/XBgNDDX5ZI8

我在github上创建了一个基本项目来重现这个问题:https://github.com/mgontar/SceneKitProjectionIssue

2个回答

4
这里的问题在于你用来计算中点的CGRect基于边界框的投影坐标系。为了得到正确的视图空间坐标的中点,需要使用模型视图投影矩阵转换边界框的两个角点,以执行相同的变换。希望代码更清晰易懂。
//world coordinates
let v1w =  sm.node.convertPosition(sm.node.boundingBox.min, to: self.sceneView.scene?.rootNode)
let v2w =  sm.node.convertPosition(sm.node.boundingBox.max, to: self.sceneView.scene?.rootNode)

//calc center of BB in world coordinates
let center = SCNVector3Make(
    (v1w.x + v2w.x)/2,
    (v1w.y + v2w.y)/2,
    (v1w.z + v2w.z)/2)

//calc each mid point
let mp1w = SCNVector3Make(v1w.x, center.y, center.z)
let mp2w = SCNVector3Make(center.x, v2w.y, center.z)
let mp3w = SCNVector3Make(v2w.x, center.y, center.z)
let mp4w = SCNVector3Make(center.x, v1w.y, center.z)

//projected coordinates
let mp1p = self.sceneView.projectPoint(mp1w)
let mp2p = self.sceneView.projectPoint(mp2w)
let mp3p = self.sceneView.projectPoint(mp3w)
let mp4p = self.sceneView.projectPoint(mp4w)

var frameOld = sm.marker.frame

switch sm.position
{
case .Top:
    frameOld.origin.y = CGFloat(mp1p.y) - frameOld.size.height/2
    frameOld.origin.x = CGFloat(mp1p.x) - frameOld.size.width/2
    sm.marker.isHidden = (mp1p.z < 0 || mp1p.z > 1)
case .Bottom:
    frameOld.origin.y = CGFloat(mp2p.y) - frameOld.size.height/2
    frameOld.origin.x = CGFloat(mp2p.x) - frameOld.size.width/2
    sm.marker.isHidden = (mp2p.z < 0 || mp2p.z > 1)
case .Left:
    frameOld.origin.y = CGFloat(mp3p.y) - frameOld.size.height/2
    frameOld.origin.x = CGFloat(mp3p.x) - frameOld.size.width/2
    sm.marker.isHidden = (mp3p.z < 0 || mp3p.z > 1)
case .Right:
    frameOld.origin.y = CGFloat(mp4p.y) - frameOld.size.height/2
    frameOld.origin.x = CGFloat(mp4p.x) - frameOld.size.width/2
    sm.marker.isHidden = (mp4p.z < 0 || mp4p.z > 1)
}

这是一个很棒的小样例项目!

方框边缘上的点线

z-剪裁问题的更新

projectPoint 方法返回一个 3D 的 SCNVector,其中 x 和 y 坐标是屏幕坐标。而 z 坐标告诉我们该点相对于远和近剪裁面的位置(z = 0 表示近剪裁面,z = 1 表示远剪裁面)。如果你为近剪裁面设置了负值,则会呈现相机后面的物体。虽然我们没有负的近剪裁面,但我们也没有逻辑来判断那些投影点是否超出了 z 远或 z 近范围。

我已经更新了上面的代码,包括这个 zNear 和 zFar 检查,并相应地切换 UIView 的可见性。

简而言之

当相机旋转 180 度时可见的标记位于相机后面,但它们仍然被投影到视图平面上。由于我们没有检查它们是否在相机后面,它们仍然被显示。


你的回答非常出色和完整,感谢你! - Maksym Gontar
然而,还有一个问题,请尝试转身180度,看到标记出现 https://youtu.be/HzDMdsP_CQY - Maksym Gontar
1
@MaxGontar 很少有机会需要我亲自移动来重现错误 :-D。已编辑上面的回复,现在应该没问题了。 - lock
谢谢,非常好用!还有感谢您的解释! - Maksym Gontar

0
为了进行正确的投影,您应该在世界坐标系中计算中点,然后将点投影到场景视图中。
        let min = node.boundingBox.min
        let max = node.boundingBox.max
        
        //world coordinates
        var mp1w = SCNVector3Make((min.x+max.x)/2, max.y, (min.z+max.z)/2) //top
        var mp2w = SCNVector3Make((min.x+max.x)/2, min.y, (min.z+max.z)/2) //bottom
        var mp3w = SCNVector3Make(min.x, (min.y+max.y)/2, (min.z+max.z)/2) //left
        var mp4w = SCNVector3Make(max.x, (min.y+max.y)/2, (min.z+max.z)/2) //right
        
        mp1w = node.convertPosition(mp1w, to: self.sceneView.scene.rootNode)
        mp2w = node.convertPosition(mp2w, to: self.sceneView.scene.rootNode)
        mp3w = node.convertPosition(mp3w, to: self.sceneView.scene.rootNode)
        mp4w = node.convertPosition(mp4w, to: self.sceneView.scene.rootNode)

        //projected coordinates
        let mp1p = self.sceneView.projectPoint(mp1w) //top
        let mp2p = self.sceneView.projectPoint(mp2w) //bottom
        let mp3p = self.sceneView.projectPoint(mp3w) //left
        let mp4p = self.sceneView.projectPoint(mp4w) //right

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接