In my app I am adding a USDZ object in an AR World. I want to make it so that the object follows the movement of the camera and always stays in the center of the screen. I am unable to do it.
I have tried adding the object and updating it's position in the renderer did update function but it did not work. Here is my code:
import UIKit
import ARKit
import SceneKit
import simd
class ARViewController: UIViewController, UIGestureRecognizerDelegate {
@IBOutlet weak var arView: ARSCNView!
var sceneView: ARSCNView!
var cameraNode: SCNNode!
let position = SCNVector3(x: 0, y: 5, z: 10)
var scnView: SCNView!
var scnScene: SCNScene!
var popupView = CustomViewAR()
var latitude = CLLocationDegrees()
var longitude = CLLocationDegrees()
var isShow : Bool?
let popupPlane = SCNPlane()
var popupNode = SCNNode()
var tapgest = UITapGestureRecognizer()
override func viewDidLoad() {
super.viewDidLoad()
let config = ARWorldTrackingConfiguration()
config.planeDetection = .horizontal
config.environmentTexturing = .automatic
arView.delegate = self
arView.session.run(config)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.addObject()
}
addTapGesture()
setUpPopUpView()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let configuration = ARWorldTrackingConfiguration()
arView.session.run(configuration)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
arView.session.pause()
}
func addObject(){
let scene = SCNScene()
let usdzNode = SCNNode()
//
let usdzScene = SCNScene(named: "cube.usdz")!
let usdzChildNodes = usdzScene.rootNode.childNodes
for node in usdzChildNodes {
usdzNode.addChildNode(node)
}
usdzNode.position = SCNVector3(0, 0, -2) // set the position of the object
scene.rootNode.addChildNode(usdzNode)
// scene!.rootNode.position = SCNVector3(0, 0, -1)
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light?.type = .ambient
ambientLightNode.light?.color = UIColor.darkGray
scene.rootNode.addChildNode(ambientLightNode)
// scene.rootNode.position = SCNVector3(0, 0, -1)
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
// 3: Place camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: -2)
// 4: Set camera on scene
scene.rootNode.addChildNode(cameraNode)
// arView.allowsCameraControl = true
let simdVector = SIMD3<Float>(x: 100, y: 100, z: 100)
let vector = SCNVector3(simdVector)
scene.rootNode.scale = vector
arView.scene = scene
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:)))
arView.addGestureRecognizer(panGesture)
let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(handlePinchGesture(_:)))
arView.addGestureRecognizer(pinchGesture)
let rotateGesture = UIRotationGestureRecognizer(target: self, action: #selector(handleRotateGesture(_:)))
arView.addGestureRecognizer(rotateGesture)
}
@objc func handleRotateGesture(_ gesture: UIRotationGestureRecognizer) {
guard let currentFrame = arView.session.currentFrame else {
return
}
let rotation = Float(gesture.rotation)
let rotationDelta = simd_quatf(angle: rotation, axis: simd_float3(1, 1, 1))
let usdzNode = arView.scene.rootNode.childNodes.first!
let usdzTransform = usdzNode.simdTransform
let newTransform = simd_mul(usdzTransform, simd_float4x4(rotationDelta))
usdzNode.simdTransform = newTransform
gesture.rotation = 0
}
@objc func handlePanGesture(_ gesture: UIPanGestureRecognizer) {
guard let currentFrame = arView.session.currentFrame else {
return
}
let translation = gesture.translation(in: arView)
let deltaX = Float(translation.x) / Float(arView.bounds.width)
let deltaY = Float(translation.y) / Float(arView.bounds.height)
let usdzNode = arView.scene.rootNode.childNodes.first!
var usdzTransform = usdzNode.simdTransform
// Get the camera's orientation in world space
let camera = currentFrame.camera
let cameraTransform = camera.transform.inverse
// let cameraForward = cameraTransform.forward
let rotationFactor: Float = 10.0
// Rotate the USDZ object around the world Y and X axes based on gesture translation
usdzTransform *= simd_float4x4(simd_quatf(angle: -deltaX * rotationFactor, axis: simd_float3(0, 1, 0)))
usdzTransform *= simd_float4x4(simd_quatf(angle: deltaY * rotationFactor, axis: simd_float3(1, 0, 0)))
usdzNode.simdTransform = usdzTransform
gesture.setTranslation(CGPoint.zero, in: arView)
}
@objc func handlePinchGesture(_ gesture: UIPinchGestureRecognizer) {
guard let currentFrame = arView.session.currentFrame else {
return
}
let usdzNode = arView.scene.rootNode.childNodes.first!
let usdzTransform = usdzNode.simdTransform
var scaleMatrix = matrix_identity_float4x4
let scale = Float(gesture.scale)
scaleMatrix.columns.0.x = scale
scaleMatrix.columns.1.y = scale
scaleMatrix.columns.2.z = scale
let newTransform = simd_mul(usdzTransform, scaleMatrix)
usdzNode.simdTransform = newTransform
gesture.scale = 1
}
func addTapGesture()
{
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
arView.addGestureRecognizer(tap)
}
@objc func handleTap(_ gesture: UITapGestureRecognizer) {
let location = gesture.location(in: arView)
let hitResults = arView.hitTest(location, options: nil)
if let hitNode = hitResults.first?.node {
showPopup(nextTo: hitNode)
tapgest = UITapGestureRecognizer(target: self, action: #selector(closePopup))
arView.addGestureRecognizer(tapgest)
isShow = true
}
}
@objc func dismissPopupView() {
// popupView.alpha = 0
}
func setUpPopUpView(){
popupView = CustomViewAR().loadNib() as! CustomViewAR
let geocoder = CLGeocoder()
let location = CLLocation(latitude: latitude, longitude: longitude)
geocoder.reverseGeocodeLocation(location) { (placemarks, error) in
guard error == nil else {
print("Error: \(error!)")
return
}
guard let placemark = placemarks!.first else {
print("Error: placemark is nil")
return
}
let address = "\(placemark.name ?? "Could not find info"), \( placemark.locality ?? "No City"), \( placemark.administrativeArea ?? "No Administrative Area"), \( placemark.country ?? "No Country")"
self.popupView.setValues(labelName: placemark.name!, placeAddress: address)
}
}
func showPopup(nextTo node: SCNNode) {
popupView.isHidden = false
setUpPopUpView()
popupView.bringSubviewToFront(popupView.lblAddress)
popupView.bringSubviewToFront(popupView.lblPlaceName)
popupView.lblAddress.textColor = UIColor.black
let worldPosition = node.simdWorldPosition - 30
let x = worldPosition.x + 30 // adjust as needed
let y = worldPosition.y + 30// adjust as needed
popupView.frame = CGRect(x: Int(x), y: Int(y), width: 250, height: 250)
popupPlane.width = 60 // adjust as needed
popupPlane.height = 60 // adjust as needed
popupPlane.cornerRadius = 10
popupPlane.firstMaterial?.diffuse.contents = popupView
popupNode = SCNNode(geometry: popupPlane)
// popupNode.position = SCNVector3(0, 0, -1000)
popupNode.opacity = 0.8
popupNode.simdWorldPosition = worldPosition // set position using simdWorldPosition
let constraint = SCNBillboardConstraint()
constraint.freeAxes = [.Y]
popupNode.constraints = [constraint]
arView.scene.rootNode.addChildNode(popupNode)
// Animate the popup to appear
SCNTransaction.begin()
SCNTransaction.animationDuration = 0.5
SCNTransaction.commit()
}
@objc func closePopup() {
if isShow == true {
SCNTransaction.begin()
SCNTransaction.animationDuration = 0.5
popupView.alpha = 0
SCNTransaction.commit()
popupView.isHidden = true
let configuration = ARWorldTrackingConfiguration()
arView.session.run(configuration, options: [.resetTracking, .removeExistingAnchors])
arView.setNeedsDisplay()
popupNode.isHidden = true
isShow = false
arView.removeGestureRecognizer(tapgest)
setUpPopUpView()
} else {
}
}
func addAnimation(node: SCNNode) {
let rotateOne = SCNAction.rotateBy(x: 0, y: CGFloat(Float.pi), z: 0, duration: 5.0)
let hoverUp = SCNAction.moveBy(x: 0, y: 0.2, z: 0, duration: 2.5)
let hoverDown = SCNAction.moveBy(x: 0, y: -0.2, z: 0, duration: 2.5)
let hoverSequence = SCNAction.sequence([hoverUp, hoverDown])
let rotateAndHover = SCNAction.group([rotateOne, hoverSequence])
let repeatForever = SCNAction.repeatForever(rotateAndHover)
node.runAction(repeatForever)
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if touch.view == popupView || touch.view?.isDescendant(of: popupView) == true {
return false
}else{
popupView.alpha = 0
return true
}
}
@IBAction func backBtnTapped(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
}
}
extension ARViewController: ARSCNViewDelegate, SCNSceneRendererDelegate {
func session(_ session: ARSession,
didFailWithError error: Error) {
print("Session Failed - probably due to lack of camera access")
}
func sessionWasInterrupted(_ session: ARSession) {
print("Session interrupted")
}
func sessionInterruptionEnded(_ session: ARSession) {
print("Session resumed")
}
}
extension UIView {
/** Loads instance from nib with the same name. */
func loadNib() -> UIView {
let bundle = Bundle(for: type(of: self))
let nibName = "CustomViewAR"
let nib = UINib(nibName: nibName, bundle: bundle)
return nib.instantiate(withOwner: self, options: nil).first as! UIView
}
}
Your SCNView
or ARSCNView
(depending on which you use the camera) should both have a so called pointOfView
, which is in fact a SCNNode
. You could try to add your Object(s) (from the USDZ file) to this node, with a position value of i.Ex: SCNVector3(0.0, 0.0, -2.0)
- depending on its size of course. (Instead to add it to the rootNode
of the main Scene). This should keep the Object always in front of the Camera, regardless in which direction you point the Camera or your device (if you use AR).