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.
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):
renderer(_:didAdd:for:)
to receive ARPlaneAnchor
objectsARSCNView
automatically position that content for you so it follows the plane's positionIf 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:
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)
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With