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
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.