About the cursor to be displayed when placing an arbitrary object in AR.
This is a standard measurement app.
Perform a hit test on the center coordinates of the screen and set based on the acquired worldTransform
.
Since the location hit by hitTest (_: types :)
of ʻARSCNView can be obtained from
worldTransform of ʻARHitTestResult
, this is used as the cursor location and orientation.
Here, when the cursor is set to SCNPlane
, if the location / orientation of the cursor = the location / orientation of the hit, it interferes with the geometry of the plane and flickers, so the vector in the upward direction of the planeworldTransform.columns.1 Use
to adjust the cursor position.
//0 in the direction (UP) facing the plane.Set the cursor at a position shifted by 01m
cursorTransform.columns.3 += worldTransform.columns.1 * 0.01
self.cursorNode.simdTransform = cursorTransform
Note that scaling is reset if SCNNode
is scaled (scale
≠ (1.0,1.0,1.0)) because it sets a transform.
In that case, set worldTransform.columns.0 ~ 2 multiplied by the scaling factor to cursorTransform.columns.0 ~ 2.
It has a pyramid shape so that the upward direction of the cursor can be easily understood.
Source code
ViewController.swift
import ARKit
import SceneKit
class ViewController: UIViewController, ARSCNViewDelegate {
@IBOutlet weak var scnView: ARSCNView!
private let device = MTLCreateSystemDefaultDevice()!
private let cursorNode = SCNNode()
override func viewDidLoad() {
super.viewDidLoad()
//AR Session started
self.scnView.delegate = self
let configuration = ARWorldTrackingConfiguration()
configuration.planeDetection = [.horizontal, .vertical]
self.scnView.session.run(configuration, options: [.removeExistingAnchors, .resetTracking])
//Cursor node preparation
let pyramid = SCNPyramid(width: 0.1, height: 0.03, length: 0.1)
pyramid.firstMaterial!.diffuse.contents = UIColor.yellow
self.cursorNode.geometry = pyramid
self.scnView.scene.rootNode.addChildNode(self.cursorNode)
self.cursorNode.isHidden = true
}
//
//Anchor added
//
func renderer(_: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
guard let planeAnchor = anchor as? ARPlaneAnchor else { return }
//Added planar geometry node
guard let geometory = ARSCNPlaneGeometry(device: self.device) else { return }
geometory.update(from: planeAnchor.geometry)
let material = SCNMaterial()
material.lightingModel = .physicallyBased
material.diffuse.contents = UIColor.red.withAlphaComponent(0.7)
geometory.materials = [material]
let planeNode = SCNNode(geometry: geometory)
DispatchQueue.main.async {
node.addChildNode(planeNode)
}
}
//
//Anchor updated
//
func renderer(_: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
guard let planeAnchor = anchor as? ARPlaneAnchor else { return }
DispatchQueue.main.async {
for childNode in node.childNodes {
//Update plane geometry
guard let planeGeometry = childNode.geometry as? ARSCNPlaneGeometry else { continue }
planeGeometry.update(from: planeAnchor.geometry)
break
}
}
}
//
//Called frame by frame
//
func renderer(_ renderer: SCNSceneRenderer, updateAtTime _: TimeInterval) {
DispatchQueue.main.async {
//Hit test in the center of the screen
let bounds = self.scnView.bounds
let screenCenter = CGPoint(x: bounds.midX, y: bounds.midY)
let results = self.scnView.hitTest(screenCenter, types: [.existingPlaneUsingGeometry])
guard let existingPlaneUsingGeometryResult = results.first(where: { $0.type == .existingPlaneUsingGeometry }),
let _ = existingPlaneUsingGeometryResult.anchor as? ARPlaneAnchor else {
//Hide cursor
self.cursorNode.isHidden = true
return
}
//Post the transform of the hit location to the transform of the cursor
let worldTransform = existingPlaneUsingGeometryResult.worldTransform
var cursorTransform = worldTransform
//0 in the direction (UP) facing the plane.Set the cursor at a position shifted by 01m
cursorTransform.columns.3 += worldTransform.columns.1 * 0.01
self.cursorNode.simdTransform = cursorTransform
self.cursorNode.isHidden = false
}
}
}
Recommended Posts