arkit锚点或节点在相机中可见并且坐在平截头体的左侧或右侧

时间:2017-10-03 07:18:25

标签: swift scenekit arkit

如何检测ARAnchor当前是否在相机中可见,我需要测试相机视图何时发生变化。 我想在不在屏幕上时将箭头放在屏幕边缘指向锚点的方向。我需要知道节点是否位于平截头体的左侧或右侧。

我现在正在这样做,但它表示如果不是并且X值看起来不对,那么引脚是可见的吗?也许渲染器视锥体与屏幕相机不匹配?

 var deltaTime = TimeInterval()

 public func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
        deltaTime = time - lastUpdateTime
        if deltaTime>1{

            if let annotation = annotationsByNode.first {

                let node = annotation.key.childNodes[0]

                if !renderer.isNode(node, insideFrustumOf: renderer.pointOfView!)
                {
                    print("Pin is not visible");
                }else {
                    print("Pin is visible");
                }
                let pnt = renderer.projectPoint(node.position)

                print("pos ", pnt.x, " ", renderer.pointOfView!.position)


            }
            lastUpdateTime = time
        }

    }

更新:代码用于显示节点是否可见,如何判断节点相对于相机平截头的左或右方向?

UPDATE2!正如Bhanu Birani的建议答案

let screenWidth = UIScreen.main.bounds.width
let screenHeight = UIScreen.main.bounds.height
let leftPoint = CGPoint(x: 0, y: screenHeight/2)
let rightPoint = CGPoint(x: screenWidth,y: screenHeight/2)

let leftWorldPos = renderer.unprojectPoint(SCNVector3(leftPoint.x,leftPoint.y,0))
let rightWorldPos = renderer.unprojectPoint(SCNVector3(rightPoint.x,rightPoint.y,0))
let distanceLeft = node.position - leftWorldPos
let distanceRight = node.position - rightWorldPos
let dir = (isVisible) ? "visible" : ( (distanceLeft.x<distanceRight.x) ? "left" : "right")

我最终使用了屏幕左右两侧的Bhanu Birani的想法,但是我得到了不同的世界位置,unProjectPoint并且还获得了距离的标量值,我将其与左/右方向进行比较。也许有更好的方法,但它对我有用

public func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
        deltaTime = time - lastUpdateTime
        if deltaTime>0.25{

            if let annotation = annotationsByNode.first {
                guard let pointOfView = renderer.pointOfView else {return}
                let node = annotation.key.childNodes[0]
                let isVisible = renderer.isNode(node, insideFrustumOf: pointOfView)

                let screenWidth = UIScreen.main.bounds.width
                let screenHeight = UIScreen.main.bounds.height
                let leftPoint = CGPoint(x: 0, y: screenHeight/2)
                let rightPoint = CGPoint(x: screenWidth,y: screenHeight/2)

                let leftWorldPos = renderer.unprojectPoint(SCNVector3(leftPoint.x, leftPoint.y,0))
                let rightWorldPos =  renderer.unprojectPoint(SCNVector3(rightPoint.x, rightPoint.y,0))
                let distanceLeft = node.worldPosition.distance(vector: leftWorldPos)
                let distanceRight = node.worldPosition.distance(vector: rightWorldPos)

                //let pnt = renderer.projectPoint(node.worldPosition)
                //guard let pnt = renderer.pointOfView!.convertPosition(node.position, to: nil) else {return}

                let dir = (isVisible) ? "visible" : ( (distanceLeft<distanceRight) ? "left" : "right")
                print("dir" , dir, " ", leftWorldPos , " ", rightWorldPos)
                lastDir=dir
                delegate?.nodePosition?(node:node, pos: dir)
            }else {
               delegate?.nodePosition?(node:nil, pos: lastDir )
            }
            lastUpdateTime = time
        }

extension SCNVector3
{

    /**
     * Returns the length (magnitude) of the vector described by the SCNVector3
     */
    func length() -> Float {
        return sqrtf(x*x + y*y + z*z)
    }

    /**
     * Calculates the distance between two SCNVector3. Pythagoras!
     */
    func distance(vector: SCNVector3) -> Float {
        return (self - vector).length()
    }


}

2 个答案:

答案 0 :(得分:11)

从以下屏幕位置投射光线:

  • leftPoint = CGPoint(0, screenHeight/2)(屏幕左侧)
  • rightPoint = CGPoint(screenWidth, screenHeight/2)(屏幕中间右侧)

将CGPoint转换为世界位置:

  • leftWorldPos = convertCGPointToWorldPosition(leftPoint)
  • rightWorldPos = convertCGPointToWorldPosition(rightPoint)

计算节点距世界两个位置的距离:

  • distanceLeft = node.position - leftWorldPos
  • distanceRight = node.position - rightWorldPos

比较距离以找到到节点的最短距离。使用最短距离矢量来定位对象的方向箭头。

以下是来自tsukimi的代码,用于检查对象是在屏幕的右侧还是在左侧:

public func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
        deltaTime = time - lastUpdateTime
        if deltaTime>0.25{

            if let annotation = annotationsByNode.first {
                guard let pointOfView = renderer.pointOfView else {return}
                let node = annotation.key.childNodes[0]
                let isVisible = renderer.isNode(node, insideFrustumOf: pointOfView)

                let screenWidth = UIScreen.main.bounds.width
                let screenHeight = UIScreen.main.bounds.height
                let leftPoint = CGPoint(x: 0, y: screenHeight/2)
                let rightPoint = CGPoint(x: screenWidth,y: screenHeight/2)

                let leftWorldPos = renderer.unprojectPoint(SCNVector3(leftPoint.x, leftPoint.y,0))
                let rightWorldPos =  renderer.unprojectPoint(SCNVector3(rightPoint.x, rightPoint.y,0))
                let distanceLeft = node.worldPosition.distance(vector: leftWorldPos)
                let distanceRight = node.worldPosition.distance(vector: rightWorldPos)

                //let pnt = renderer.projectPoint(node.worldPosition)
                //guard let pnt = renderer.pointOfView!.convertPosition(node.position, to: nil) else {return}

                let dir = (isVisible) ? "visible" : ( (distanceLeft<distanceRight) ? "left" : "right")
                print("dir" , dir, " ", leftWorldPos , " ", rightWorldPos)
                lastDir=dir
                delegate?.nodePosition?(node:node, pos: dir)
            }else {
               delegate?.nodePosition?(node:nil, pos: lastDir )
            }
            lastUpdateTime = time
        }

以下是帮助对vector执行操作的类

extension SCNVector3 {

    init(_ vec: vector_float3) {
        self.x = vec.x
        self.y = vec.y
        self.z = vec.z
    }

    func length() -> Float {
        return sqrtf(x * x + y * y + z * z)
    }

    mutating func setLength(_ length: Float) {
        self.normalize()
        self *= length
    }

    mutating func setMaximumLength(_ maxLength: Float) {
        if self.length() <= maxLength {
            return
        } else {
            self.normalize()
            self *= maxLength
        }
    }

    mutating func normalize() {
        self = self.normalized()
    }

    func normalized() -> SCNVector3 {
        if self.length() == 0 {
            return self
        }

        return self / self.length()
    }

    static func positionFromTransform(_ transform: matrix_float4x4) -> SCNVector3 {
        return SCNVector3Make(transform.columns.3.x, transform.columns.3.y, transform.columns.3.z)
    }

    func friendlyString() -> String {
        return "(\(String(format: "%.2f", x)), \(String(format: "%.2f", y)), \(String(format: "%.2f", z)))"
    }

    func dot(_ vec: SCNVector3) -> Float {
        return (self.x * vec.x) + (self.y * vec.y) + (self.z * vec.z)
    }

    func cross(_ vec: SCNVector3) -> SCNVector3 {
        return SCNVector3(self.y * vec.z - self.z * vec.y, self.z * vec.x - self.x * vec.z, self.x * vec.y - self.y * vec.x)
    }
}




extension SCNVector3{
    func distance(receiver:SCNVector3) -> Float{
        let xd = receiver.x - self.x
        let yd = receiver.y - self.y
        let zd = receiver.z - self.z
        let distance = Float(sqrt(xd * xd + yd * yd + zd * zd))

        if (distance < 0){
            return (distance * -1)
        } else {
            return (distance)
        }
    }
}

以下是将抽头位置或任何CGPoint转换为世界变换的代码段。

@objc func handleTap(_ sender: UITapGestureRecognizer) {

    // Take the screen space tap coordinates and pass them to the hitTest method on the ARSCNView instance
    let tapPoint = sender.location(in: sceneView)
    let result = sceneView.hitTest(tapPoint, types: ARHitTestResult.ResultType.existingPlaneUsingExtent)

    // If the intersection ray passes through any plane geometry they will be returned, with the planes
    // ordered by distance from the camera
    if (result.count > 0) {

        // If there are multiple hits, just pick the closest plane
        if let hitResult = result.first {
            let finalPosition = SCNVector3Make(hitResult.worldTransform.columns.3.x + insertionXOffset,
                                                       hitResult.worldTransform.columns.3.y + insertionYOffset,
                                                       hitResult.worldTransform.columns.3.z + insertionZOffset
                    );
        }
    }
}

以下是在没有发现飞机时获得命中测试结果的代码。

// check what nodes are tapped
let p = gestureRecognize.location(in: scnView)
let hitResults = scnView.hitTest(p, options: [:])
// check that we clicked on at least one object
if hitResults.count > 0 {
    // retrieved the first clicked object
    let result = hitResults[0] 
}

答案 1 :(得分:0)

这个答案有点晚,但对于需要知道节点相对于中心的相机空间位置的人(例如左上角,居中......)非常有用。

您可以使用scene.rootNode.convertPosition(node.position,to:pointOfView)在相机空间中获取节点位置。

在相机空间,

  • (isVisible&amp;&amp;(x = 0,y = 0))表示您的节点位于相机前面。

  • (isVisible&amp;&amp;(x = 0.1))表示该节点位于右侧。

一些示例代码:

public func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
    deltaTime = time - lastUpdateTime
    if deltaTime>0.25{

        if let annotation = annotationsByNode.first {
            guard let pointOfView = renderer.pointOfView else {return}
            let node = annotation.key.childNodes[0]
            let isVisible = renderer.isNode(node, insideFrustumOf: pointOfView)

            // Translate node to camera space
            let nodeInCameraSpace = scene.rootNode.convertPosition(node.position, to: pointOfView)
            let isCentered = isVisible && (nodeInCameraSpace.x < 0.1) && (nodeInCameraSpace.y < 0.1)
            let isOnTheRight = isVisible && (nodeInCameraSpace.x > 0.1)
            // ...

            delegate?.nodePosition?(node:node, pos: dir)
        }else {
           delegate?.nodePosition?(node:nil, pos: lastDir )
        }
        lastUpdateTime = time
    }