My Swift code below uses func touchesBegan
to place a SCN object in the ARKit view. The problem is – it's only placing the object one time. I would like to create the code in a way that users can select any area to place the SCN object and it can place it as many times as they want too.
Here's GitHub link.
class ViewController: UIViewController, ARSCNViewDelegate {
@IBOutlet var sceneView: ARSCNView!
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// Handle the shooting
guard let frame = sceneView.session.currentFrame else { return }
let camMatrix = SCNMatrix4(frame.camera.transform)
let direction = SCNVector3Make(camMatrix.m31 * 5.0,
camMatrix.m32 * 10.0,
camMatrix.m33 * 5.0)
let position = SCNVector3Make(camMatrix.m41, camMatrix.m42, camMatrix.m43)
let scene = SCNScene(named: "art.scnassets/dontCare.scn")!
sceneView.scene = scene
}
}
Tip: If you use RealityKit, read this post.
touchesBegan(:with:)
Use the following code to get a desired effect (place as many objects into a scene as you want):
At first create an extension for your convenience:
import ARKit
extension SCNVector3 {
static func + (lhs: SCNVector3, rhs: SCNVector3) -> SCNVector3 {
return SCNVector3(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z)
}
}
Then use it in your ViewController for adding a pointOfView.position
to desiredVector
:
class ViewController: UIViewController {
@IBOutlet var sceneView: ARSCNView!
override func touchesBegan(_ touches: Set<UITouch>,
with event: UIEvent?) {
sceneView.isMultipleTouchEnabled = true
guard let pointOfView = sceneView.pointOfView // Camera of SCNScene
else { return }
let cameraMatrix = pointOfView.transform
let desiredVector = SCNVector3(cameraMatrix.m31 * -0.5,
cameraMatrix.m32 * -0.5,
cameraMatrix.m33 * -0.5)
// What the extension SCNVector3 is for //
let position = pointOfView.position + desiredVector
let sphereNode = SCNNode()
sphereNode.geometry = SCNSphere(radius: 0.05)
sphereNode.geometry?.firstMaterial?.diffuse.contents = UIColor.green
sphereNode.position = position
sceneView.scene.rootNode.addChildNode(sphereNode)
}
override func viewDidLoad() {
super.viewDidLoad()
let scene = SCNScene(named: "art.scnassets/myScene.scn")!
sceneView.scene = scene
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let config = ARWorldTrackingConfiguration()
sceneView.session.run(config)
}
}
And if you want to retrieve a 3D model from
.scn
file use the following code:(Instead of the
sphereNode
):
var model = SCNNode()
let myScene = SCNScene(named: "art.scnassets/ship.scn")
// Model's name in a Scene graph hierarchy.
// Pay particular attention – it's not a name of .scn file.
let nodeName = "ship"
model = (myScene?.rootNode.childNode(withName: nodeName, recursively: true))!
model.position = position
sceneView.scene.rootNode.addChildNode(model)
Use the following code if you want to add models using plane detection and Hit-testing :
At first create an extension for your convenience:
extension float4x4 {
var simdThree: SIMD3<Float> {
let translation = self.columns.3
return SIMD3<Float>(translation.x, translation.y, translation.z)
}
}
Then use it in the ViewController:
class ViewController: UIViewController {
@IBOutlet weak var sceneView: ARSCNView!
override func viewDidLoad() {
super.viewDidLoad()
addGesture()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
sceneView.delegate = self // for ARSCNViewDelegate
let config = ARWorldTrackingConfiguration()
config.planeDetection = [.horizontal]
sceneView.session.run(config)
}
func addGesture() {
let tapGesture = UITapGestureRecognizer(target: self,
action: #selector(addModel))
sceneView.addGestureRecognizer(tapGesture)
}
// Don't forget to drag-and-drop TapGestureRecognizer object from library
@objc func addModel(recognizer: UIGestureRecognizer) {
let tap: CGPoint = recognizer.location(in: sceneView)
let results: [ARHitTestResult] = sceneView.hitTest(tap,
types: .existingPlaneUsingExtent)
guard let hitTestResult = results.first
else { return }
let translation = hitTestResult.worldTransform.simdThree
let x = translation.x
let y = translation.y
let z = translation.z
guard let scene = SCNScene(named: "art.scnassets/myScene.scn"),
let robotNode = scene.rootNode.childNode(withName: "robot",
recursively: true)
else { return }
robotNode.position = SCNVector3(x, y, z)
robotNode.scale = SCNVector3(0.02, 0.02, 0.02)
sceneView.scene.rootNode.addChildNode(robotNode)
}
}
And, you have to implement a logic inside two
renderer()
methods forARPlaneAnchor
s:
extension ViewController: ARSCNViewDelegate {
func renderer(_ renderer: SCNSceneRenderer,
didAdd node: SCNNode,
for anchor: ARAnchor) { // your logic here.... }
func renderer(_ renderer: SCNSceneRenderer,
didUpdate node: SCNNode,
for anchor: ARAnchor) { // your logic here.... }
}