Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift : ARKit Save ARPlaneAnchor for next session

ARKit is quite new and I am quite new in swift... So I'm having some troubles...

I'd like to save the ARPlaneAnchor detected during a session and reload them when I relaunch my app. My phone will always be at the same place and I'd like to scan the room one time. And remembering the Anchor I found in the room everytime I launch the app.

I tried several solutions :

Solution1 : Save the ARPlaneAnchor using : NSKeyedArchiver.archiveRootObject(plane, toFile: filePath)

I got this error :

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[ARPlaneAnchor encodeWithCoder:]: unrecognized selector sent to instance

I think that maybe I can't save this kind of data locally

Solution2 : Store the datas of the ARPlaneAnchor then intantiate them when I launch the app. the datas are mainly float. I could creat ARAnchor easily, I could cast them as ARPlaneAnchor, but I could not modify the "center" and "extend" parameter of the ARPlaneAnchor because they only have a getter and not a setter. So I can't create the good anchors.

I am open to anysolution. I think I need to store the ARAnchor object, but for now I could not find a way to do it without a crash! So if someone can help me I would be very grateful.

like image 242
mcarlier Avatar asked Jan 30 '23 22:01

mcarlier


1 Answers

First... if your app is restricted to a situation where the device is permanently installed and the user can never move or rotate it, using ARKit to display overlay content on the camera feed is sort of a "killing mosquitos with a cannon" kind of situation. You could just as well work out at development time what kind of camera projection your 3D engine needs, use a "dumb" camera feed with your 3D engine running on top, and not need iOS 11 or an ARKit-capable device.

So you might want to think about your use case or your technology stack some more before you commit to specific solutions and workarounds.


As for your more specific problem...

ARPlaneAnchor is entirely a read-only class, because its use case is entirely read-only. It exists for the sole purpose of giving ARKit a way to give you information about detected planes. However, once you have that information, you can do with it whatever you want. And from there on, you don't need to keep ARPlaneAnchor in the equation anymore.

Perhaps you're confused because of the typical use case for plane detection (and SceneKit-based display):

  1. Turn on plane detection
  2. Respond to renderer(_:didAdd:for:) to receive ARPlaneAnchor objects
  3. In that method, return virtual content to associate with the plane anchor
  4. Let ARSCNView automatically position that content for you so it follows the plane's position

If your plane's position is static with respect to the camera, though, you don't need all that.

You only need ARKit to handle the placement of your content within the scene if that placement needs ongoing management, as is the case when plane detection is live (ARKit refines its estimates of plane location and extent and updates the anchor accordingly). If you did all your plane-finding ahead of time, you won't be getting updates, so you don't need ARKit to manage updates.

Instead your steps can look more like this:

  1. Know where a plane is (position in world space).
  2. Set the position of your virtual content to the position of the plane.
  3. Add the content to the scene directly.

In other words, your "Solution 2" is a step in the right direction, but not far enough. You want to archive not an ARPlaneAnchor instance itself, but the information it contains — and then when unarchiving, you don't need to re-create an ARPlaneAnchor instance, you just need to use that information.

So, if this is what you do to place content with "live" plane detection:

func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
    guard let planeAnchor = anchor as? ARPlaneAnchor else { return }
    let extent = planeAnchor.extent
    let center = planeAnchor.center
    // planeAnchor.transform not used, because ARSCNView automatically applies it
    // to the container node, and we make a child of the container node

    let plane = SCNPlane(width: CGFloat(extent.x), height: CGFloat(extent.z))
    let planeNode = SCNNode(geometry: plane)
    planeNode.eulerAngles.x = .pi / 2
    planeNode.simdPosition = center
    node.addChildNode(planeNode)
}

Then you can do something like this for static content placement:

struct PlaneInfo { // something to save and restore ARPlaneAnchor data
    let transform: float4x4
    let center: float3
    let extent: float3
}
func makePlane(from planeInfo: PlaneInfo) { // call this when you place content
    let extent = planeInfo.extent
    let center = float4(planeInfo.center, 1) * planeInfo.transform
    // we're positioning content in world space, so center is now
    // an offset relative to transform

    let plane = SCNPlane(width: CGFloat(extent.x), height: CGFloat(extent.z))
    let planeNode = SCNNode(geometry: plane)
    planeNode.eulerAngles.x = .pi / 2
    planeNode.simdPosition = center.xyz
    view.scene.rootNode.addChildNode(planeNode)
}

// convenience vector-width conversions used above
extension float4 {
    init(_ xyz: float3, _ w: Float) {
        self.init(xyz.x, xyz.y, xyz.z, 1)
    }
    var xyz: float3 {
        return float3(self.x, self.y, self.z)
    }
}
like image 135
rickster Avatar answered Feb 04 '23 03:02

rickster