3

I want to create app where you can set points in real world in detected surface and after you have 4 or more points it creates plane/polygon between them with texture. Basic controller configuration:

@IBOutlet weak var sceneView: ARSCNView!

let configuration = ARWorldTrackingConfiguration()
var vectors: [SCNVector3] = []
var mostBottomYAxis: Float? {
    didSet {
        for (index, _) in vectors.enumerated() {
            vectors[index].y = self.mostBottomYAxis ?? vectors[index].y
        }
    }
}
var identifier: UUID?

override func viewDidLoad() {
    super.viewDidLoad()
    
    self.sceneView.debugOptions = [ARSCNDebugOptions.showFeaturePoints, ARSCNDebugOptions.showWorldOrigin]
    self.sceneView.showsStatistics = true
    
    self.configuration.worldAlignment = .gravity // Y axis is Up and Down
    self.configuration.planeDetection = .horizontal // Detect horizontal surfaces
    self.sceneView.session.run(configuration)
    self.sceneView.autoenablesDefaultLighting = true
    self.sceneView.delegate = self
    
    self.registerGestureRecognizers()
}

Tap gesture method for adding vertex to surface:

@objc func handleTap(sender: UITapGestureRecognizer) {
    let sceneView = sender.view as! ARSCNView
    let tapLocation = sender.location(in: sceneView)
    
    if let raycast = sceneView.raycastQuery(from: tapLocation, allowing: .estimatedPlane, alignment: .horizontal),
       let result = sceneView.session.raycast(raycast).first {
        self.addItem(raycastResult: result)
    }
}

func addItem(raycastResult: ARRaycastResult) {
    let transform = raycastResult.worldTransform
    let thirdColumn = transform.columns.3
    let vector = SCNVector3(x: thirdColumn.x, y: mostBottomYAxis ?? thirdColumn.y, z: thirdColumn.z)
    self.vectors.append(vector)
    self.addItem(toPosition: vector)
}

and methods for drawing polygon:

func getGeometry(forVectors vectors: [SCNVector3]) -> SCNGeometry {
    let polygonIndices = Array(0...vectors.count).map({ Int32($0) })
    let indices: [Int32] = [Int32(vectors.count)] + /* We have a polygon with this count of points */
    polygonIndices /* The indices for our polygon */
    
    let source = SCNGeometrySource(vertices: vectors)
    let indexData = Data(bytes: indices,
                         count: indices.count * MemoryLayout<Int32>.size)
    let element = SCNGeometryElement(data: indexData,
                                     primitiveType: .polygon,
                                     primitiveCount: 1,
                                     bytesPerIndex: MemoryLayout<Int32>.size)
    
    // MARK: - Texture
    

    let textureCoordinates = [
        CGPoint(x: 0, y: 0),
        CGPoint(x: 1, y: 0),
        CGPoint(x: 0, y: 1),
        CGPoint(x: 1, y: 1)
    ]

    let uvSource = SCNGeometrySource(textureCoordinates: textureCoordinates)
    
    let geometry = SCNGeometry(sources: [source, uvSource],
                               elements: [element])
    return geometry
}

func getNode(forVectors vectors: [SCNVector3]) -> SCNNode {
    
    let polyDraw = getGeometry(forVectors: vectors)
    
    let material = SCNMaterial()
    material.isDoubleSided = true
    material.diffuse.contents = UIImage(named: "Altezo_colormix_brilant")

    material.diffuse.wrapS = .repeat
    material.diffuse.wrapT = .repeat

    polyDraw.materials = [material]
    
    let node = SCNNode(geometry: polyDraw)
    return node
}

extension PlacePointsForPlaneViewController: ARSCNViewDelegate {

    func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
        guard let planeAnchor = anchor as? ARPlaneAnchor else { return }
        
        let y = planeAnchor.transform.columns.3.y
        if mostBottomYAxis == nil {
            mostBottomYAxis = y
            identifier = planeAnchor.identifier
        }
        if mostBottomYAxis! > y {
            mostBottomYAxis = y
            identifier = planeAnchor.identifier
        }
        
        // Remove existing plane nodes
        node.enumerateChildNodes { (childNode, _) in
            childNode.removeFromParentNode()
        }
        
        if planeAnchor.identifier == self.identifier {
            if self.vectors.count > 3 {
                let modelNode = getNode(forVectors: self.vectors)
                node.addChildNode(modelNode)
            }
        }
        
    }
}

I hope it's mostly self explained. I have vectors which contains manually added points. I want to be working only with one surface that is most bottom (thats what do mostBottomYAxis and identifier).

What am I doing wrong? Why polygon is not drawn exactly between points? When I tried to draw line between two points it's working.

One more problem. How to set texture coordinates to correctly draw texture in polygon and to get it working not only for 4 verticies but for more (dynamically as user add more points and can change their positions)?

Thanks for help

Result in sample app

Edit: After I post question I tried to different nodes to add my custom polygon and it's working fine if I added to sceneView rootNode:

  sceneView.scene.rootNode.addChildNode(modelNode)

So this helps with position of polygon. But still I have problems with texture. How to set texture offset/transform to get it working? To have texture fill this custom geometry and repeat texture to edges.

Libor Zapletal
  • 13,752
  • 20
  • 95
  • 182

0 Answers0