1

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

Amit Singh
  • 63
  • 8

1 Answers1

1

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)])
        }
    }
}
Amit Singh
  • 63
  • 8
  • This is super useful, thank you very much - the only thing I don't quite understand is where the DomeTile class you reference is defined? It doesn't seem to be in SceneKit, ModelIO, or ARKit, and google throws up very little apart from this answer. If it's a class you've defined yourself, would you mind sharing, or describing an alternative so that others can replicate this solution? thank you! – mistertim Jan 10 '22 at 12:58
  • So, by a process of elimination i managed to work out that `DomeTile` is a subclass of `SCNNode` and substituting the superclass there gets rid of the compiler error, however i've got one outstanding doubt, the call: `self.geometry = SCNGeometry(mdlMesh: mesh)` currently throws a compiler error for me: "Argument passed for call that takes no arguments", which the docs bear out - it doesn't appear that you can instantiate an SCNGeometry from an MDLMesh that way. Any idea of a workaround? – mistertim Jan 10 '22 at 13:07
  • 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") } } – Amit Singh Jan 18 '22 at 10:09
  • added DomeTile class – Amit Singh Jan 18 '22 at 10:11