首页 文章

iOS SceneKit 到 UIView 投影问题

提问于
浏览
1

我的相机根节点前面有一个 SCNView,SCNNode 为 SCNPlane 几何体。

通过 SCNView,在 UIView 中,我添加了 UIImages(标记) - 橙色圆圈。

在 Motion 监听器中,我试图以某种方式定位标记,以便它们将粘附到平面的每个边缘的中心。

正确的标记对齐 - 当设备处于直线位置时:
正确的标记对齐 - 当设备处于直线位置时

我正在使用 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()

你可以在这里找到类似的方法:场景工具包:计划的 projectPoint 被取代

所以我希望那些标记在设备运动过程中贴在平面的边缘。但问题是:当旋转设备时 - 标记从平面边缘漂移
旋转设备时 - 标记从平面边缘漂移

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

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

1 回答

  • 3

    这里的问题是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-clipping 问题更新

    projectPoint方法返回一个 3D SCNVector,x 任何 y coords 我们知道是屏幕坐标。 z 坐标告诉我们该点相对于远和近剪裁平面的位置(z = 0 在剪切平面附近,z = 1 远剪裁平面)。如果为近剪裁平面设置负值,则会渲染摄影机后面的对象。我们没有负近剪裁平面,但我们也没有任何逻辑可以说明如果那些投影点位置落在 z 远和 z 近距离之外会发生什么。

    我已更新上面的代码以包含此 zNear 和 zFar 检查并相应地切换 UIView 可见性。

    TL;博士

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

相关问题