Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to calculate quadrangle for visible part of vertical plane?

My goal is to calculate the "visible" part of vertical plane that is anchored to some ARPlaneAnchor and represent it with quadrangle as shown in the picture below:

Piture

My current approach is based on few hit tests, which unfortunately seems to not giving me the satisfying results.

First, when I detect a ARPlaneAnchor I add a big invisible SCNNode to its main node.

func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
    guard let planeAnchor = anchor as? ARPlaneAnchor else { return }

    let planeNode = SCNNode(geometry: SCNPlane(width: 100.0, height: 100.0))
    planeNode.name = "infinite_plane"

    planeNode.position = SCNVector3(planeAnchor.center.x, planeAnchor.center.y, planeAnchor.center.z)
    planeNode.eulerAngles = SCNVector3(-0.5 * .pi, 0, 0)

    planeNode.geometry?.firstMaterial?.diffuse.contents = UIColor.clear

    node.addChildNode(planeNode)

    node.enumerateChildNodes { childNode, _ in
        childNode.removeFromParentNode()
    }
}

Then, when it's time to calculate the visible part of vertical plane (it doesn't matter when, it can be after some screen touch or each time the new frame is being rendered) I take the center of the screen and perform some hit tests.

let center = CGPoint(x: sceneView.bounds.midX, y: sceneView.bounds.midY)
let hitTestResults = sceneView.hitTest(center, types: [.existingPlaneUsingGeometry, .estimatedVerticalPlane])
if let hitTestResult = hitTestResults.first,
   let hitAnchor = hitTestResult.anchor,
   let hitNode = sceneView.node(for: hitAnchor) {
    
    let width = sceneView.bounds.width
    let height = sceneView.bounds.height
    // A B
    // C D
    guard let aPosition = sceneView.hitTest(CGPoint(x: 0, y: 0)).first?.worldCoordinates,
          let bPosition = sceneView.hitTest(CGPoint(x: width, y: 0)).first?.worldCoordinates,
          let cPosition = sceneView.hitTest(CGPoint(x: 0, y: height)).first?.worldCoordinates,
          let dPosition = sceneView.hitTest(CGPoint(x: width, y: height)).first?.worldCoordinates else {
        return
    }
    let geometry = SCNGeometry.polygon(vertices: [aPosition, bPosition, dPosition, cPosition])
    let node = SCNNode(geometry: geometry)
    node.geometry?.firstMaterial?.diffuse.contents = UIColor.green
    node.geometry?.firstMaterial?.isDoubleSided = true
    sceneView.scene.rootNode.addChildNode(node)
}

extension SCNGeometry {

    static func polygon(vertices: [SCNVector3]) -> SCNGeometry {
        let source = SCNGeometrySource(vertices: vertices)

        var indices = vertices.indices.map { UInt32($0) }
        indices.insert(UInt32(vertices.count), at: 0)

        let data = Data(bytes: indices, count: indices.count * MemoryLayout<Int32>.size)

        let element = SCNGeometryElement(
            data: data,
            primitiveType: .polygon,
            primitiveCount: 1,
            bytesPerIndex: MemoryLayout<Int32>.size
        )

        return SCNGeometry(sources: [source], elements: [element])
    }
}

And here I stopped. The drawn polygon doesn't seem to be representing the visible part of the vertical plane.

The question is, how where my estimatations are wrong and how should I detect the "visible" part of vertical plane correctly?

like image 855
Nominalista Avatar asked Oct 26 '22 16:10

Nominalista


1 Answers

Try this approach:

extension ViewController: ARSCNViewDelegate {
    
    func renderer(_ renderer: SCNSceneRenderer,
                 didAdd node: SCNNode,
                  for anchor: ARAnchor) {
        
        guard let planeAnchor = anchor as? ARPlaneAnchor
        else { return }
        
        let width = CGFloat(planeAnchor.extent.x)
        let height = CGFloat(planeAnchor.extent.z)
        
        let myPlane = SCNPlane(width: width,
                              height: height)

        myPlane.materials.first?.diffuse.contents = UIColor(white: 0.0, 
                                                            alpha: 0.5)
        
        let planeNode = SCNNode(geometry: myPlane)
        let x = CGFloat(planeAnchor.center.x)
        let y = CGFloat(planeAnchor.center.y)
        let z = CGFloat(planeAnchor.center.z)
        
        planeNode.position = SCNVector3(x, y, z)
        planeNode.eulerAngles.x = -Float.pi / 2
        
        node.addChildNode(planeNode)
    }      
    
    func renderer(_ renderer: SCNSceneRenderer,
              didUpdate node: SCNNode,
                  for anchor: ARAnchor) {
        
        guard let planeAnchor = anchor as? ARPlaneAnchor,
              let planeNode = node.childNodes.first,
              let myPlane = planeNode.geometry as? SCNPlane
        else { return }
        
        let width = CGFloat(planeAnchor.extent.x)
        let height = CGFloat(planeAnchor.extent.z)
        myPlane.width = width
        myPlane.height = height
        
        let x = CGFloat(planeAnchor.center.x)
        let y = CGFloat(planeAnchor.center.y)
        let z = CGFloat(planeAnchor.center.z)
        planeNode.position = SCNVector3(x, y, z)
    }
}
like image 139
Andy Jazz Avatar answered Nov 16 '22 18:11

Andy Jazz