Search code examples
scenekitarkitmodelio

how to create AR dome like qlone 3d scanner app using Arkit Scenekit


I am looking to create AR dome shape geometry in Scenekit, like it is created in qlone 3D scanner app. Please refer following links for visuals.

https://www.youtube.com/watch?v=0JQZmTT3KO0

https://3dscanexpert.com/qlone-3d-scanning-ios-app/

enter image description here


Solution

  • Model IO api MDLMesh.newEllipsoid creates dome, now we needs place tiles over each section of dome.

    1. Create dome and get vertices and using vertices create tiles and place them as individual SCNNode.
    import UIKit
    import SceneKit
    import ARKit
    import ModelIO
    
    class ARDome : SCNNode {
    
        private var sceneView: ARSCNView!
        private var radius: Float = 0.0
        private var radialSegments: Int = 0
        private var verticalSegments: Int = 0
        private var maxDistanceToFocusPoint: Float = 0.05
        private var minSize = SIMD3<Float>(0.01, 0.01, 0.01)
        var tilesContinuousHitCount = [SCNNode:Int]()
        
        private var extent = SIMD3<Float>(0.01, 0.01, 0.01) {
            didSet {
                extent = max(extent, minSize)
            }
        }
        
        init(sceneView: ARSCNView, radius: Float, radialSegments: Int, verticalSegments: Int) {
            super.init()
            self.sceneView = sceneView
            self.radius = radius
            self.radialSegments = radialSegments
            self.verticalSegments = verticalSegments
            //self.categoryBitMask = 0x0
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        func domeSkeleton() {
            let mesh = MDLMesh.newEllipsoid(withRadii: vector_float3(radius, radius, radius), radialSegments: radialSegments, verticalSegments: verticalSegments, geometryType: MDLGeometryType.lines, inwardNormals: true, hemisphere: true, allocator: nil)
            
            self.geometry = SCNGeometry(mdlMesh: mesh)
            self.geometry?.firstMaterial?.diffuse.contents = UIColor.systemYellow
            self.geometry?.firstMaterial?.isDoubleSided = true
        }
        
        func placeTiles() {
            guard let geometry = self.geometry else { return }
            let vertices = helper.testim(geo: geometry)
            let rings = vertices.chunked(into: radialSegments + 1)
            for base in 0..<(verticalSegments/2) {
                let bottom = base + 1
                let top = base
                for i in 0..<(radialSegments) {
                    let rectNode = helper.getNode(lb: rings[bottom][i], lt: rings[top][i], rt: rings[top][i+1], rb: rings[bottom][i+1])
                    self.addChildNode(rectNode)
                }
            }
        }
    }
    
    
    import Foundation
    import ARKit
    
    class DomeTile: SCNNode {
        public var processed = false
        init(geometry: SCNGeometry) {
            super.init()
            self.geometry = geometry
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    }
    

    Helper Class

    import UIKit
    import SceneKit
    import ARKit
    import ModelIO
    
    
    class helper {
        public static func getNode(lb: SCNVector3, lt: SCNVector3, rt: SCNVector3, rb: SCNVector3) -> DomeTile{
            let vertices: [SCNVector3] = [lb, lt, rt, rb,]
            let source = SCNGeometrySource(vertices: vertices)
            let indices: [UInt16] = [0, 1, 2, 0, 2, 3,]
            let element = SCNGeometryElement(indices: indices, primitiveType: .triangles)
            let geometry = SCNGeometry(sources: [source], elements: [element])
            geometry.firstMaterial?.isDoubleSided = true
            geometry.firstMaterial?.diffuse.contents  = UIColor.systemBlue.withAlphaComponent(0.7)
            let node = DomeTile(geometry: geometry)
            return node
        }
        
        public static func testim(geo: SCNGeometry) -> [SCNVector3] {
            //let rect = SCNBox(width: 40.0, height: 40.0, length: 5.0, chamferRadius: 0.0)
            let srcs = geo.sources(for: .vertex)
            guard let src = srcs.first else { exit(1)}
            
            let bperC = src.bytesPerComponent
            let stride = src.dataStride / bperC
            let offset = src.dataOffset / bperC
            let vectorCount = src.vectorCount
            
            let vertices = src.data.withUnsafeBytes { (buffer : UnsafePointer<Float>) -> [SCNVector3] in
                var result = Array<SCNVector3>()
                for i in 0...vectorCount - 1 {
                    let start = i * stride + offset
                    let x = buffer[start]
                    let y = buffer[start + 1]
                    let z = buffer[start + 2]
                    result.append(SCNVector3(x, y, z))
                }
                return result
            }
            return vertices
        }
    }
    
    extension Array {
        func chunked(into size: Int) -> [[Element]] {
                  return stride(from: 0, to: count, by: size).map {
                Array(self[$0 ..< Swift.min($0 + size, count)])
            }
        }
    }