How can I let the plane collide with an object in RealityKit - swift

I have a ball and a plane (floor) in realityKit.
The ball falls and has to collide on the plane. But the ball is falling through the plane. When the ball hits the plane i want a print statement "OCCURED" but this is also not happening.
My code:
struct ARViewContainer: UIViewRepresentable {
let arView = ARView(frame: .zero)
func makeUIView(context: Context) -> ARView {
var subscriptions: [Cancellable] = []
let anchor = AnchorEntity()
//generate a plane
let planeModel = ModelEntity(mesh: .generatePlane(width: 2.0,
depth: 2.0))
as (Entity & HasCollision)
planeModel.position = [0,-10,-1.5]
planeModel.generateCollisionShapes(recursive: true)
//generate a ball
let ballModel = ModelEntity(mesh: .generateSphere(radius: 0.4))
as (Entity & HasCollision & HasPhysicsBody)
ballModel.generateCollisionShapes(recursive: true)
ballModel.position = [0,0, -1.5]
ballModel.physicsBody = .init()
ballModel.physicsBody?.mode = .dynamic
let sub = arView.scene.subscribe(to: CollisionEvents.Began.self,
on: ballModel) { _ in print("OCCURED!") }
subscriptions.append(sub)
anchor.addChild(ballModel)
anchor.addChild(planeModel)
arView.scene.addAnchor(anchor)
return arView
}
}

Collider and collidee
This code activates physics and prints "occured" N times, thanks to CollisionEvents' subscription:
import SwiftUI
import RealityKit
import Combine
struct ARViewContainer: UIViewRepresentable {
#State var subscriptions: [AnyCancellable] = []
let arView = ARView(frame: .zero)
let anchor = AnchorEntity()
func makeUIView(context: Context) -> ARView {
// PLANE
let planeModel = ModelEntity(mesh: .generatePlane(width: 2.0,
depth: 2.0))
as (Entity & HasPhysics)
planeModel.position = [0,-1,-1.5]
planeModel.generateCollisionShapes(recursive: false)
planeModel.physicsBody = .init()
planeModel.physicsBody?.mode = .static
// BALL
let ballModel = ModelEntity(mesh: .generateSphere(radius: 0.2),
materials: [UnlitMaterial()])
as (Entity & HasPhysics)
ballModel.position = [0, 1,-1.5]
ballModel.generateCollisionShapes(recursive: false)
ballModel.physicsBody = .init()
ballModel.physicsBody?.mode = .dynamic
DispatchQueue.main.async {
arView.scene.subscribe(to: CollisionEvents.Began.self,
on: ballModel) { _ in
print("OCCURED!")
}.store(in: &subscriptions)
}
anchor.addChild(ballModel)
anchor.addChild(planeModel)
arView.scene.addAnchor(anchor)
return arView
}
func updateUIView(_ view: ARView, context: Context) { }
}
struct ContentView : View {
var body: some View {
ARViewContainer().ignoresSafeArea()
}
}

Related

How to change model's color on tap and then change it back to original?

Im trying to create a functionality in ARKit where if the user taps on the modelEntity it changes its colour to lets say blue so it indicates that it's selected. But then if the user taps on another entity, the previously selected entity's material changes back to what it was before it was selected.
So i am able to change its colour with this code:
let selectedMaterial = SimpleMaterial(color: .link, isMetallic: false)
selectedEntity.model?.materials[0] = selectedMaterial
But how do I change it back after I 'deselect' it, when i select another modelEntity?
Because I've been trying to save it's material to a variable, but I'm having a problem with it, because lets say there are two modelEntites, A and B. When I tap the "A" entity it changes colour, then I tap on the "B" entity then "B" entity's colour changes and the "A" entity's material changes back to the original (like how it should be working), but when I tap again on the "A" entity the "B" entity's material goes back to the original but the "A" entity's colour doesn't change.
This is how I'm trying to make it work:
enum EntityState {
case unselected
case selected
case correctName
case wrongName
}
private var entitiesState: [String: EntityState] = [String: EntityState]()
private var modelEntities: [ModelEntity] = [ModelEntity]()
private var modelEntitiesMaterials: [String: [Material]] = [String: [Material]]()
//This is how i place a modelEntity
#objc private func placeObject() {
let modelName = self.model.name ?? ""
let entity = try! Entity.load(named: modelName)
let geomChildrens = entity.findEntity(named: "Geom")
if let geomChildrens = geomChildrens {
for children in geomChildrens.children {
let childModelEntity = children as! ModelEntity
childModelEntity.collision = CollisionComponent(shapes: [ShapeResource.generateConvex(from: childModelEntity.model!.mesh)])
entitiesState[childModelEntity.name] = EntityState.unselected
modelEntities.append(childModelEntity)
}
}
let modelEntity = ModelEntity()
modelEntity.addChild(entity)
let anchorEntity = AnchorEntity(.plane(.horizontal, classification: .any, minimumBounds: .zero))
anchorEntity.addChild(modelEntity)
arView.installGestures([.all],for: modelEntity)
arView.scene.addAnchor(anchorEntity)
}
private func selectEntity(withSelectedEntity: ModelEntity?) {
modelInformationView.isHidden = false
//If we didnt hit any modelEntity
guard let selectedEntity = withSelectedEntity else {
//Unselect the selected entity if there is one.
for entity in modelEntities {
if(entitiesState[entity.name] == .selected) {
entitiesState[entity.name] = .unselected
}
}
colorModelEntities()
return
}
if(entitiesState[selectedEntity.name] == .selected) {
// If its already selected, just unselect
entitiesState[selectedEntity.name] = .unselected
} else {
//First unselect the previously selected entity.
for entity in modelEntities {
if(entitiesState[entity.name] == .selected) {
entitiesState[entity.name] = .unselected
}
}
//Select the entity.
entitiesState[selectedEntity.name] = .selected
}
colorModelEntities()
}
private func colorModelEntities() {
let selectedMaterial = SimpleMaterial(color: .link, isMetallic: false) //Blue
for entity in modelEntities {
let keyExists = modelEntitiesMaterials[entity.name] != nil
if keyExists {
entity.model!.materials = modelEntitiesMaterials[entity.name]!
}
if(entitiesState[entity.name] == .selected) {
//Color blue the selected item
entity.model?.materials[0] = selectedMaterial
}
}
}
#objc private func handleTap(sender: UITapGestureRecognizer) {
let tapLocation: CGPoint = sender.location(in: arView)
let result: [CollisionCastHit] = arView.hitTest(tapLocation)
guard let hitTest: CollisionCastHit = result.first, hitTest.entity.name != "Ground Plane"
else {
selectEntity(withSelectedEntity: nil)
return
}
let entity: ModelEntity = hitTest.entity as! ModelEntity
let keyExists = modelEntitiesMaterials[entity.name] != nil
if !keyExists {
modelEntitiesMaterials[entity.name] = entity.model!.materials
}
selectEntity(withSelectedEntity: entity)
}
Solution for a single model
🥧 It's easy as Apple pie 🥧
I used a regular single box scene built in Reality Composer.
import UIKit
import RealityKit
class GameViewController: UIViewController {
#IBOutlet var arView: ARView!
var me: ModelEntity? = nil
var counter = 0
override func viewDidLoad() {
super.viewDidLoad()
let scene = try! Experience.loadBox()
print(scene)
me = scene.findEntity(named: "simpBld_root") as? ModelEntity
me?.model?.materials[0] = UnlitMaterial(color: .red)
me?.generateCollisionShapes(recursive: true)
arView.scene.anchors.append(scene)
}
override func touchesBegan(_ touches: Set<UITouch>,
with event: UIEvent?) {
guard let point = touches.first?.location(in: arView) else { return }
let ray = arView.ray(through: point)
let castHits = arView.scene.raycast(origin: ray?.origin ?? [0,0,0],
direction: ray?.direction ?? [0,0,0])
if castHits.first != nil {
counter += 1
if counter % 2 == 1 {
me?.model?.materials[0] = UnlitMaterial(color: .green)
} else {
me?.model?.materials[0] = UnlitMaterial(color: .red)
}
}
}
}
Solution for multiple models
Here I used three boxes scene built in Reality Composer (I merely copy-pasted original box).
import UIKit
import RealityKit
class GameViewController: UIViewController {
#IBOutlet var arView: ARView!
typealias ME = ModelEntity
var me: [ModelEntity] = [ME(),ME(),ME()]
override func viewDidLoad() {
super.viewDidLoad()
let scene = try! Experience.loadBox()
for ind in 0...2 {
me[ind] = scene.children[0].children[0]
.children[ind].children[0] as! ModelEntity
me[ind].model?.materials[0] = UnlitMaterial(color: .red)
me[ind].generateCollisionShapes(recursive: true)
me[ind].name = "box \(ind + 1)"
}
arView.scene.anchors.append(scene)
print(scene)
}
override func touchesBegan(_ touches: Set<UITouch>,
with event: UIEvent?) {
guard let point = touches.first?.location(in: arView) else { return }
let ray = arView.ray(through: point)
let castHits = arView.scene.raycast(origin: ray?.origin ?? [0,0,0],
direction: ray?.direction ?? [0,0,0])
guard let name = castHits.first?.entity.name else { return }
if castHits.first != nil {
switch name {
case "box 1": changeTo(0, .green);
changeTo(1, .red); changeTo(2, .red)
case "box 2": changeTo(1, .green);
changeTo(0, .red); changeTo(2, .red)
case "box 3": changeTo(2, .green);
changeTo(0, .red); changeTo(1, .red)
default: break
}
print(name)
}
}
func changeTo(_ element: Int, _ color: UIColor) {
me[element].model?.materials[0] = UnlitMaterial(color: color)
}
}
This answer might be helpful as well.

Is there any way where we can get the current page number in PDFView and use it in SwiftUI

I am making an app pdf reader using PDFKit but I am unable to get the current page number. I can get the total pages by pdfView.document?.pageCount. The alternative way we can use for this is to change the page by button and count it but I want the PDFView default feature Changing the page by Swipe by sitting the pdfView.usePageViewController(true) but it does not give any method to get the current page number
Code
struct ContentView: View {
let url = Bundle.main.url(forResource: "file", withExtension: "pdf")
var body: some View {
VStack{
PDFKitRepresentedView(data: try! Data(contentsOf: url!))
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
import PDFKit
import SwiftUI
struct PDFKitRepresentedView: UIViewRepresentable {
typealias UIViewType = PDFView
let data: Data
func makeUIView(context _: UIViewRepresentableContext<PDFKitRepresentedView>) -> UIViewType {
// Create a `PDFView` and set its `PDFDocument`.
let pdfView = PDFView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
pdfView.document = PDFDocument(data: data)
pdfView.backgroundColor = UIColor.red
pdfView.displayMode = .singlePage
pdfView.displayDirection = .horizontal
pdfView.usePageViewController(true)
pdfView.maxScaleFactor = pdfView.scaleFactorForSizeToFit
pdfView.minScaleFactor = pdfView.scaleFactorForSizeToFit
pdfView.autoScales = true
return pdfView
}
func updateUIView(_ pdfView: UIViewType, context _: UIViewRepresentableContext<PDFKitRepresentedView>) {
pdfView.document = PDFDocument(data: data)
}
}
Update
According to suggestion given by
workingdog support Ukraine below the coordinator class printing the result but when I use Binding to pass the currentPage to SwiftUI its not working the page number is not updating in UI and on swiping its repeating the first two pages only
New Updated code
struct ContentView: View {
#State var currentPage = -1
#State var totalPages :Int?
let url = Bundle.main.url(forResource: "file", withExtension: "pdf")
var body: some View {
VStack{
HStack{
Text("\(currentPage)/")
Text("\(totalPages ?? 0)")
}
if let url = url {
PDFKitRepresentedView(data:try! Data(contentsOf: url),totalPages: $totalPages,currentPage: $currentPage)
}
}
}
}
struct PDFKitRepresentedView: UIViewRepresentable {
typealias UIViewType = PDFView
let data: Data
#Binding var totalPages:Int?
#Binding var currentPage :Int
let pdfView = PDFView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
func makeUIView(context: Context) -> UIViewType {
pdfView.document = PDFDocument(data: data)
pdfView.backgroundColor = UIColor.red
pdfView.displayMode = .singlePage
pdfView.displayDirection = .horizontal
pdfView.usePageViewController(true)
pdfView.maxScaleFactor = pdfView.scaleFactorForSizeToFit
pdfView.minScaleFactor = pdfView.scaleFactorForSizeToFit
pdfView.autoScales = true
pdfView.delegate = context.coordinator
return pdfView
}
func updateUIView(_ pdfView: UIViewType, context _: Context) {
pdfView.document = PDFDocument(data: data)
DispatchQueue.main.async {
totalPages = pdfView.document?.pageCount
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(self, cp: $currentPage)
}
class Coordinator: NSObject, PDFViewDelegate {
var parent: PDFKitRepresentedView
#Binding var currentPage :Int
init(_ parent: PDFKitRepresentedView,cp:Binding<Int>) {
self.parent = parent
_currentPage = cp
super.init()
NotificationCenter.default.addObserver(self, selector: #selector(pageChangeHandler(_:)), name: .PDFViewPageChanged, object: nil)
}
#objc func pageChangeHandler(_ notification: Notification) {
if let thePage = parent.pdfView.currentPage,
let ndx = parent.pdfView.document?.index(for: thePage),
currentPage != ndx {
DispatchQueue.main.async {
self.currentPage = ndx
}
print("--------> currentPageIndex: \(ndx) ")
}
}
}
}
According to the docs at: https://developer.apple.com/documentation/pdfkit/pdfview there is a currentPage that returns the current page. You could then use something like this to get the index of it:
if let thePage = pdfView.currentPage, let ndx = pdfView.document?.index(for: thePage) {
print("--> currentPageIndex: \(ndx) ")
// ....
}
EDIT-1:
Try the following approach, using a Coordinator class for the PDFViewDelegate and getting notified when a .PDFViewPageChanged with a NotificationCenter.default.addObserver(...).
You will have to adjust the code for your own purpose.
struct PDFKitRepresentedView: UIViewRepresentable {
typealias UIViewType = PDFView
let data: Data
let pdfView = PDFView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
func makeUIView(context: Context) -> UIViewType {
pdfView.document = PDFDocument(data: data)
pdfView.backgroundColor = UIColor.red
pdfView.displayMode = .singlePage
pdfView.displayDirection = .horizontal
pdfView.usePageViewController(true)
pdfView.maxScaleFactor = pdfView.scaleFactorForSizeToFit
pdfView.minScaleFactor = pdfView.scaleFactorForSizeToFit
pdfView.autoScales = true
pdfView.delegate = context.coordinator
return pdfView
}
func updateUIView(_ pdfView: UIViewType, context _: Context) {
pdfView.document = PDFDocument(data: data)
}
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
class Coordinator: NSObject, PDFViewDelegate {
var parent: PDFKitRepresentedView
var prevPage = -1
init(_ parent: PDFKitRepresentedView) {
self.parent = parent
super.init()
NotificationCenter.default.addObserver(self, selector: #selector(pageChangeHandler(_:)), name: .PDFViewPageChanged, object: nil)
}
#objc func pageChangeHandler(_ notification: Notification) {
if let thePage = parent.pdfView.currentPage,
let ndx = parent.pdfView.document?.index(for: thePage),
prevPage != ndx {
print("--------> currentPageIndex: \(ndx) ")
prevPage = ndx
}
}
}
}
EDIT-2:
To access the currentPage in ContentView, that is, outside the PDFViewer,
you can use the following approach.
It uses a .onReceive(...) of a page change notification, and some minor changes of
the original code.
struct ContentView: View {
#State var currentPage = 0
let pdfViewer: PDFViewer // <--- here
init() {
if let url = Bundle.main.url(forResource: "file", withExtension: "pdf"),
let docData = try? Data(contentsOf: url) {
self.pdfViewer = PDFViewer(data: docData)
} else {
self.pdfViewer = PDFViewer(data: Data())
}
}
var body: some View {
VStack {
Text("page \(currentPage)")
pdfViewer
.onReceive(NotificationCenter.default.publisher(for: .PDFViewPageChanged)) { _ in
if let thePage = pdfViewer.pdfView.currentPage,
let ndx = pdfViewer.pdfView.document?.index(for: thePage), currentPage != ndx {
currentPage = ndx
}
}
}
}
}
struct PDFViewer: UIViewRepresentable {
typealias UIViewType = PDFView
let data: Data
let pdfView = PDFView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
func makeUIView(context: Context) -> UIViewType {
pdfView.document = PDFDocument(data: data)
pdfView.backgroundColor = UIColor.red
pdfView.displayMode = .singlePage
pdfView.displayDirection = .horizontal
pdfView.usePageViewController(true)
pdfView.maxScaleFactor = pdfView.scaleFactorForSizeToFit
pdfView.minScaleFactor = pdfView.scaleFactorForSizeToFit
pdfView.autoScales = true
return pdfView
}
func updateUIView(_ pdfView: UIViewType, context _: Context) {
pdfView.document = PDFDocument(data: data)
}
}

ARSession CurrentFrame is missing the AR interpretation Model Entities

I have the following ARView:
import SwiftUI
import UIKit
import RealityKit
import ARKit
struct ARViewContainer: UIViewRepresentable {
#EnvironmentObject var selectedFood: SelectedFood
#EnvironmentObject var arSession: ARSessionObservable
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> ARView {
let arView = ARView(frame: .zero)
let config = ARWorldTrackingConfiguration()
config.planeDetection = [.vertical, .horizontal]
config.environmentTexturing = .automatic
if ARWorldTrackingConfiguration.supportsSceneReconstruction(.mesh) {
config.sceneReconstruction = .mesh
}
arView.session.delegate = context.coordinator
arView.session.run(config)
arSession.session = arView.session
return arView
}
func updateUIView(_ uiView: ARView, context: Context) {
if (!selectedFood.food.image.isEmpty) {
let data = try! Data(contentsOf: URL(string: self.selectedFood.food.image)!)
let fileURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
try! data.write(to: fileURL)
do {
let texture = try TextureResource.load(contentsOf: fileURL)
var material = SimpleMaterial()
material.baseColor = MaterialColorParameter.texture(texture)
material.tintColor = UIColor.white.withAlphaComponent(0.99)
let entity = ModelEntity(mesh: .generatePlane(width: 0.1, height: 0.1), materials: [material])
let anchor = AnchorEntity(.plane(.any, classification: .any, minimumBounds: .zero))
anchor.addChild(entity)
uiView.scene.addAnchor(anchor)
} catch {
print(error.localizedDescription)
}
}
}
class Coordinator: NSObject, ARSessionDelegate, ARSCNViewDelegate {
var arVC: ARViewContainer
init(_ arViewContainer: ARViewContainer) {
self.arVC = arViewContainer
}
func session(_ session: ARSession, didUpdate frame: ARFrame) {
}
func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {
}
}
}
And in HomeView i have the following two variables:
#StateObject var arSession: ARSessionObservable = ARSessionObservable()
#State private var capturedImage: UIImage = UIImage()
The following button with action:
Button {
if let capturedFrame = arSession.session.currentFrame {
let ciimg = CIImage(cvPixelBuffer: capturedFrame.capturedImage)
if let cgImage = convertCIImageToCGImage(inputImage: ciimg) {
capturedImage = UIImage(cgImage: cgImage).rotate(radians: .pi / 2)
self.isShowingMail = true
}
}
} label: {
Image("ShareScreen")
.resizable()
.aspectRatio(contentMode:.fit)
.frame(width: 66, height: 66, alignment: .center)
}
Which takes the currentFrame from the session and opens a Mail sharing model with attachment:
.sheet(isPresented: $isShowingMail) {
MailComposeViewController(toRecipients: [], mailBody: nil, imageAttachment: capturedImage) {
self.isShowingMail = false
}
The mail sharing:
func makeUIViewController(context: UIViewControllerRepresentableContext<MailComposeViewController>) -> MFMailComposeViewController {
let mail = MFMailComposeViewController()
mail.mailComposeDelegate = context.coordinator
mail.setToRecipients(self.toRecipients)
if let body = mailBody {
mail.setMessageBody(body, isHTML: true)
}
if let image = imageAttachment {
if let imageData = image.pngData() {
mail.addAttachmentData(imageData, mimeType: "image/png", fileName: "image.png")
}
}
return mail
}
The problem is that on the preview there are present the Model Entities, photo below:
And when i press share, on the mail preview the model is missing from the frame:
I managed to make it work by moving arView: ARView! outside the ARViewContainer
var arView: ARView!
struct ARViewContainer: UIViewRepresentable {
func makeUIView(context: Context) -> ARView {
arView = ARView(frame: .zero)
let config = ARWorldTrackingConfiguration()
config.planeDetection = [.vertical, .horizontal]
config.environmentTexturing = .automatic
if ARWorldTrackingConfiguration.supportsSceneReconstruction(.mesh) {
config.sceneReconstruction = .mesh
}
arView.session.delegate = context.coordinator
arView.session.run(config)
return arView
}
}
And then calling the snapshot function to arView in the other View:
Button {
arView.snapshot(saveToHDR: false) { image in
let image = UIImage(data: (image?.pngData())!)
capturedImage = image!
self.isShowingMail = true
}

How to properly place SCNNode on top of the QR Code?

I want to detect QR codes in the vertical plane and place a node on top of the detected QR. For QR detection, I used Vision framework and Arkit to place the nodes as below code. Whenever placing the node, it is not attached to the QR code and placed somewhere else.
Could someone help to figure out what I have done wrong?
class ViewController: UIViewController, ARSCNViewDelegate,ARSessionDelegate{
#IBOutlet var sceneView: ARSCNView!
var qrRequests = [VNRequest]()
var detectedDataAnchor: ARAnchor?
var processing = false
let configuration = ARWorldTrackingConfiguration()
override func viewDidLoad() {
super.viewDidLoad()
self.sceneView.delegate = self
self.sceneView.session.delegate = self
self.sceneView.session.run(configuration)
startQrCodeDetection()
}
func startQrCodeDetection() {
let request = VNDetectBarcodesRequest(completionHandler: self.requestHandler)
self.qrRequests = [request]
}
public func session(_ session: ARSession, didUpdate frame: ARFrame) {
DispatchQueue.global(qos: .userInitiated).async {
do {
if self.processing {
return
}
self.processing = true
let imageRequestHandler = VNImageRequestHandler(cvPixelBuffer: frame.capturedImage,
options: [:])
try imageRequestHandler.perform(self.qrRequests)
} catch {
}
}
}
func requestHandler(request: VNRequest, error: Error?) {
if let results = request.results, let result = results.first as? VNBarcodeObservation {
guard let payload = result.payloadStringValue else {return}
var rect = result.boundingBox
let center = CGPoint(x: rect.midX, y: rect.midY)
DispatchQueue.main.async {
self.hitTestQrCode(center: center)
self.processing = false
}
} else {
self.processing = false
}
}
func hitTestQrCode(center: CGPoint) {
print("Hit Test")
if let hitTestResults = self.sceneView?.hitTest(center, types: [.featurePoint, .existingPlaneUsingExtent] ),
let hitTestResult = hitTestResults.first {
if let detectedDataAnchor = self.detectedDataAnchor,
let node = self.sceneView.node(for: detectedDataAnchor) {
node.transform = SCNMatrix4(hitTestResult.worldTransform)
} else {
self.detectedDataAnchor = ARAnchor(transform: hitTestResult.worldTransform)
self.sceneView.session.add(anchor: self.detectedDataAnchor!)
}
}
}
func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
if self.detectedDataAnchor?.identifier == anchor.identifier {
let sphere = SCNSphere(radius: 0.02)
sphere.firstMaterial?.diffuse.contents = UIColor.red
let sphereNode = SCNNode(geometry: sphere)
sphereNode.transform = SCNMatrix4(anchor.transform)
return sphereNode
}
return nil
}
}

Is it possible to have a location based anchor for an object in RealityKit?

I've created an AR app that works pretty well, but I'm not a hug fan of the objects spawning in front of the camera every time. I would prefer to have them spawn in this field, further out for the camera, and facing predetermined directions. For example, I want a car to spawn in the same parking lot space every time, so when I walk out into the lot, I can see the car parked there like I left it, no matter which way I come at it from.
How can I spawn my objects based on their location? I would think it would have to do with replacing the plane detection with latitude and longitude coordinates, but I don't know how to go about this.
Any help is greatly appreciated!
import UIKit
import RealityKit
import ARKit
class ViewController: UIViewController {
#IBOutlet var arView: ARView!
override func viewDidLoad() {
super.viewDidLoad()
arView.session.delegate = self
showModel()
overlayCoachingView()
setupARView()
arView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTap(recognizer:))))
}
func showModel(){
let anchorEntity = AnchorEntity(plane: .horizontal, minimumBounds:[0.2, 0.2])
let entity = try! Entity.loadModel(named: "COW_ANIMATIONS")
entity.setParent(anchorEntity)
arView.scene.addAnchor(anchorEntity)
}
func overlayCoachingView () {
let coachingView = ARCoachingOverlayView(frame: CGRect(x: 0, y: 0, width: arView.frame.width, height: arView.frame.height))
coachingView.session = arView.session
coachingView.activatesAutomatically = true
coachingView.goal = .horizontalPlane
view.addSubview(coachingView)
}
// Load the "Box" scene from the "Experience" Reality File
// let boxAnchor = try! Experience.loadBox()
// Add the box anchor to the scene
//arView.scene.anchors.append(boxAnchor)
func setupARView(){
arView.automaticallyConfigureSession = false
let configuration = ARWorldTrackingConfiguration()
configuration.planeDetection = [.horizontal, .vertical]
configuration.environmentTexturing = .automatic
arView.session.run(configuration)
}
//object placement
#objc
func handleTap(recognizer: UITapGestureRecognizer){
let location = recognizer.location(in:arView)
let results = arView.raycast(from: location, allowing: .estimatedPlane, alignment: .horizontal)
if let firstResult = results.first {
let anchor = ARAnchor(name: "COW_ANIMATIONS", transform: firstResult.worldTransform)
arView.session.add(anchor: anchor)
} else {
print("Object placement failed - couldn't find surface.")
}
}
func placeObject(named entityName: String, for anchor: ARAnchor) {
let entity = try! ModelEntity.loadModel(named: entityName)
entity.generateCollisionShapes(recursive: true)
arView.installGestures([.rotation, .translation], for: entity)
let anchorEntity = AnchorEntity(anchor: anchor)
anchorEntity.addChild(entity)
arView.scene.addAnchor(anchorEntity)
}
}
extension ViewController: ARSessionDelegate {
func session( session: ARSession, didAdd anchors: [ARAnchor]) {
for anchor in anchors {
if let anchorName = anchor.name, anchorName == "COW_ANIMATIONS" {
placeObject(named: anchorName, for: anchor)
} }
}
}
For geo location Apple made ARGeoTrackingConfiguration with corresponding ARGeoAnchors.
let location = CLLocationCoordinate2D(latitude: -18.9137, longitude: 47.5361)
let geoAnchor = ARGeoAnchor(name: "Tana", coordinate: location, altitude: 1250)
arView.session.add(anchor: geoAnchor)
let realityKitAnchor = AnchorEntity(anchor: geoAnchor)
arView.scene.anchors.append(realityKitAnchor)
At the moment it's working in the current cities and areas.
You can also use getGeoLocation(forPoint:completionHandler:) instance method that converts a position in the framework’s local coordinate system to GPS latitude, longitude and altitude.
arView.session.getGeoLocation(forPoint: xyzWorld) { (coord, alt, error) in
let anchor = ARGeoAnchor(coordinate: coord, altitude: alt)
}