Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

413
Views
Draw custom polygon dynamically from manually added verticies in real time

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.

over 4 years ago · Santiago Trujillo
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!