Карты мира ARKit - Как сохранить / получить доступ, используя пользовательский тип файла UTI? - PullRequest
0 голосов
/ 30 августа 2018

Я не могу найти документацию, в которой указан тип файла, в котором ARKit сохраняет свою карту мира. Насколько я знаю, это .arexperience файл. По сути, я пытаюсь изменить браузер документов, чтобы иметь возможность выборочно выбирать файл .arexperience для загрузки.

Я попытался включить поддерживаемые типы документов, в частности public.arexperience, используя LSHandlerRank в качестве типа String и Value в качестве альтернативного, а также CFBundleTypeRole в качестве Type String и Value в качестве средства просмотра.

Я также установил Поддерживаемый браузер документов на ДА в info.plist Дополнительные свойства типа документа .

9.7.18 РЕДАКТИРОВАТЬ: я получаю следующую ошибку. Есть мысли по этому поводу? Я очистил папку сборки, закрыл XCode, удалил и переустановил проект и перезагрузил компьютер, но без изменений. Command CompileSwift failed with a nonzero exit code

9.13.18 РЕДАКТИРОВАТЬ: Эта ошибка возникает в результате новой установки бета-версии XCode 10 (которая включает Swift 4.2) и вашего примера проекта. Есть идеи, что происходит? Исследования показывают, что ошибка SIGABRT является результатом неиспользуемой розетки, но использование вспомогательного редактора, который позволяет мне просматривать соединения, не отображает явную проблему ... enter image description here

Ответы [ 2 ]

0 голосов
/ 08 сентября 2018

ARWorldMap - это не формат файла и даже не унифицированный формат данных. Когда ARKit создает карту мира, она содержит все ARAnchors, присутствующие в сеансе, включая настраиваемые подклассы привязки, определенные любым приложением, в котором работает ARKit. Вы можете увидеть это в примере кода Apple:

  • Создание постоянного опыта AR сохраняет снимок изображения в привязке, чтобы помочь вам переориентироваться для возобновления сеанса с карты мира позже.
  • SwiftShot сохраняет местоположение игрового поля в якоре перед отправкой карты мира другому игроку, чтобы оба игрока знали, где находится доска, перед началом игры.

Кроме того, ARKit даже не сохраняет карту мира файлы - вы несете ответственность за сериализацию ARWorldMap экземпляров (и того, что они содержат) в двоичные данные и сохранение их в файл , (Или делать что-то еще с этими данными, например отправлять их по сети. Или сериализовать некоторый граф объектов , содержащий и ARWorldMap вместо только самой карты. И так далее.)

Таким образом, нет единого формата «Карта мира ARKit» - каждое приложение, использующее ARKit, сохраняет свои собственные пользовательские данные на карте мира, и эти данные обычно не имеют значения для других приложений. (Есть карты SwiftShot, карты ThisApp, карты ThatApp и т. Д.)


Теперь вы могли бы определить свой собственный формат файла - просто объявите, что когда ваше приложение NSKeyedArchiver содержит ARWorldMap и записывает полученный Data в файл, это * Файл 1031 *, который имеет UTI com.example.myapp и т. Д. И вы могли бы объявить это типом документа, видимым пользователю, и реализовать поддержку обозревателя документов. (Как подробно описано в ответе @ BlackMirrorz.)

Но должен ты?

На практике карты мира ARKit - это (в основном) переходные данные . Чтобы иметь возможность перемещать на ранее сохраненную карту мира, устройство, загружающее карту, должно находиться в реальной среде, очень похожей на устройство, которое сохранило карту - например, в той же комнате, с теми же условиями освещения, с тем же самым материалом в комнате (вчерашний грязный стол не всегда грязный так же, как сегодняшний) и т. д. Чем старше получается карта пустоши, тем больше вероятность, что реальная окружающая среда, которую он описывает, больше не достаточно похожа.

Вы можете использовать ARWorldMap для ограниченных форм персистентности, но на самом деле не имеет смысла рассматривать их как пользовательские документы в смысле, например, документа Word / Pages или фотографии - если вы копируете их между устройствами или для других людей в других частях света они не слишком полезны для принимающей стороны.

Для многих приложений единственным значимым способом восстановления сеансов с карт мира является сохранение только одной карты - последней среды, в которой использовалось приложение, - и попытка возобновить работу с этой карты при запуске. (В противном случае предположим, что пользователь находится в другой среде, и начните все сначала.)

Если имеет смысл для вашего приложения, чтобы помнить несколько прошлых сред - скажем, приложение для дизайна интерьера, которое размещает виртуальную мебель и может помнить разные комнаты дома - это, вероятно, более полезно для Ваш пользователь должен сделать пользовательский интерфейс для выбора сохраненных карт более интегрированным с опытом работы вашего приложения, а не выводить их в общий браузер документов.

0 голосов
/ 31 августа 2018

Это казалось интересным вопросом, поэтому я подумал, что мне стоит ответить и иметь полностью рабочий пример, который может послужить основой для будущего развития вами и другими.

Возможно, это неправильный подход, но, надеюсь, он будет полезен.

Сказав это, я не совсем уверен, почему вы хотите использовать Браузер документов, чтобы позволить пользователю выбрать ARWorldMaps. Более простой подход может состоять в том, чтобы просто сохранить их в CoreData и разрешить выбор в UITableView, например. Или включите приведенную ниже логику в нечто подобное, например, когда пользовательский файл открыт, сохраните его в CoreData и представьте все полученные файлы таким образом.

В любом случае, здесь есть кое-что, что может начать ваше изучение этой темы более подробно. Хотя, пожалуйста, обратите внимание, что это никак не оптимизировано, хотя этого должно быть более чем достаточно, чтобы указать вам правильное направление ^ ______ ^.

Для вашей информации:

ARWorldMap соответствует протоколу NSSecureCoding, поэтому вы можете конвертировать карта мира в или из двоичного представления данных с использованием Классы NSKeyedArchiver и NSKeyedUnarchiver.

Поскольку мы хотим использовать Custom UTI для сохранения нашего ARWorldMap, нам сначала нужно установить его в нашем файле info.plist, где мы устанавливаем тип UTI на public.data.

enter image description here

В редакторе проекта это выглядит так:

enter image description here

Дополнительную информацию о том, как это сделать, можно найти здесь: Ray Wenderlich .

Сделав это, мы, конечно, должны сохранить наш ARWorldMap и разрешить его экспорт. Я создал typealias, как мы будем сохранять наши данные, например. ключевое значение String и значение Data (наше ARWorldMap):

typealias BMWorlMapItem = [String: Data]

/// Saves An ARWorldMap To The Documents Directory And Allows It To Be Sent As A Custom FileType
@IBAction func saveWorldMap(){

    //1. Attempt To Get The World Map From Our ARSession
    augmentedRealitySession.getCurrentWorldMap { worldMap, error in

        guard let mapToShare = worldMap else { print("Error: \(error!.localizedDescription)"); return }

        //2. We Have A Valid ARWorldMap So Save It To The Documents Directory
        guard let data = try? NSKeyedArchiver.archivedData(withRootObject: mapToShare, requiringSecureCoding: true) else { fatalError("Can't Encode Map") }

        do {

            //a. Create An Identifier For Our Map
            let mapIdentifier = "BlackMirrorzMap"

            //b. Create An Object To Save The Name And WorldMap
            var contentsToSave = BMWorlMapItem()

            //c. Get The Documents Directory
            let documentDirectory = try self.fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor:nil, create:false)

            //d. Create The File Name
            let savedFileURL = documentDirectory.appendingPathComponent("/\(mapIdentifier).bmarwp")

            //e. Set The Data & Save It To The Documents Directory
            contentsToSave[mapIdentifier] = data

            do{
                let archive = try NSKeyedArchiver.archivedData(withRootObject: contentsToSave, requiringSecureCoding: true)
                try archive.write(to: savedFileURL)

                //f. Show An Alert Controller To Share The Item
                let activityController = UIActivityViewController(activityItems: ["Check Out My Custom ARWorldMap", savedFileURL], applicationActivities: [])
                self.present(activityController, animated: true)

                print("Succesfully Saved Custom ARWorldMap")

            }catch{

                print("Error Generating WorldMap Object == \(error)")
            }

        } catch {

            print("Error Saving Custom WorldMap Object == \(error)")
        }

    }
}

Это также сохраняет данные в Documents Directory на пользовательском устройстве, чтобы мы могли проверить, что все работает как положено, например:

enter image description here

Как только данные сохранены, мы даем пользователю UIActivityAlertController, чтобы пользователь мог отправить файл на email и т. Д.

Поскольку теперь мы можем экспортировать наши данные, нам нужно обрабатывать то, как мы получаем наши данные, когда мы выбираем, как открыть их с помощью нашего пользовательского обработчика:

enter image description here

Это обрабатывается в нашем AppDelegate следующим образом:

//---------------------------
//MARK: - Custom File Sharing
//---------------------------

func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {

    //1. List Our Custom File Type Which Will Hold Our ARWorldMap
    guard url.pathExtension == "bmarwp" else { return false }

    //2. Post Our Data
    NotificationCenter.default.post(name: NSNotification.Name(rawValue: "MapReceived"), object: nil, userInfo: ["MapData" : url])

    return true
}

Как вы можете видеть, когда наш пользовательский файл получен через AppDelegate, отправляется Notification, который мы зарегистрируем в нашем ViewController viewDidLoad, например:

 NotificationCenter.default.addObserver(self, selector: #selector(importWorldMap(_:)), name: NSNotification.Name(rawValue: "MapReceived"), object: nil)

Теперь у нас есть все эти настройки, которые нам, конечно, нужны для извлечения данных, чтобы их можно было использовать. Что достигается так:

/// Imports A WorldMap From A Custom File Type
///
/// - Parameter notification: NSNotification)
@objc public func importWorldMap(_ notification: NSNotification){

    //1. Remove All Our Content From The Hierachy
    self.augmentedRealityView.scene.rootNode.enumerateChildNodes { (existingNode, _) in existingNode.removeFromParentNode() }

    //2. Check That Our UserInfo Is A Valid URL
    if let url = notification.userInfo?["MapData"] as? URL{

        //3. Convert Our URL To Data
        do{
            let data = try Data(contentsOf: url)

            //4. Unarchive Our Data Which Is Of Type [String: Data] A.K.A BMWorlMapItem
            if let mapItem = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as! BMWorlMapItem,
               let archiveName = mapItem.keys.first,
               let mapData = mapItem[archiveName] {

                //5. Get The Map Data & Log The Anchors To See If It Includes Our BMAnchor Which We Saved Earlier
                if  let unarchivedMap = try? NSKeyedUnarchiver.unarchivedObject(ofClasses: [ARWorldMap.classForKeyedUnarchiver()], from: mapData),
                    let worldMap = unarchivedMap as? ARWorldMap {

                    print("Extracted BMWorldMap Item Named = \(archiveName)")

                    worldMap.anchors.forEach { (anchor) in if let name = anchor.name { print ("Anchor Name == \(name)") } }

                    //5. Restart Our Session
                    let configuration = ARWorldTrackingConfiguration()
                    configuration.planeDetection = .horizontal
                    configuration.initialWorldMap = worldMap
                    self.augmentedRealityView.session.run(configuration, options: [.resetTracking, .removeExistingAnchors])
                }

            }

        }catch{

            print("Error Extracting Data == \(error)")
        }
    }
}

Теперь наши данные извлечены, нам просто нужно перенастроить нашу Session и загрузить карту.

Вы заметите, что я регистрирую AnchorNames, чтобы проверить, был ли процесс успешным, поскольку я создаю пользовательский ARAnchor с именем BMAnchor, который я создаю с помощью UITapGestureRecognizer, например так:

//------------------------
//MARK: - User Interaction
//------------------------

/// Allows The User To Create An ARAnchor
///
/// - Parameter gesture: UITapGestureRecognizer
@objc func placeAnchor(_ gesture: UITapGestureRecognizer){

    //1. Get The Current Touch Location
    let currentTouchLocation = gesture.location(in: self.augmentedRealityView)

    //2. Perform An ARSCNHiteTest For Any Feature Points
    guard let hitTest = self.augmentedRealityView.hitTest(currentTouchLocation, types: .featurePoint).first else { return }

    //3. Create Our Anchor & Add It To The Scene
    let validAnchor = ARAnchor(name: "BMAnchor", transform: hitTest.worldTransform)
    self.augmentedRealitySession.add(anchor: validAnchor)

}

Когда это извлекается, я затем генерирую модель с помощью ARSCNViewDelegate, что снова полезно для проверки того, что наш процесс прошел успешно:

//-------------------------
//MARK: - ARSCNViewDelegate
//-------------------------

extension ViewController: ARSCNViewDelegate{

    func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {

        //1. Check We Have Our BMAnchor
        if let name = anchor.name, name == "BMAnchor" {

            //2. Create Our Model Node & Add It To The Hierachy
            let modelNode = SCNNode()

            guard let sceneURL = SCNScene(named: "art.scnassets/wavingState.dae") else { return nil }

            for childNode in sceneURL.rootNode.childNodes { modelNode.addChildNode(childNode) }

            return modelNode

        }else{

            return SCNNode()
        }

    }

}

Надеюсь, это укажет вам правильное направление ...

А вот полный рабочий пример для вас и всех остальных, с которыми можно экспериментировать и адаптироваться к вашим потребностям: Совместное использование ARWorldMaps

Все, что вам нужно сделать, это дождаться начала сеанса, поместить вашу модель и нажать Сохранить. Получив оповещение, отправьте его по электронной почте себе, а затем проверьте свою электронную почту и щелкните файл .bmarwp, который автоматически загрузится в приложении ^ _________ ^,

В Document Based App вы можете легко использовать пользовательские типы файлов.

Ниже приведены требования для вашего info.plist:

(a) Типы документов:

enter image description here

(b) Экспортируемые ИМП типа:

enter image description here

Раздел информации о вашем проекте выглядит так:

enter image description here

Таким образом, вы в конечном итоге получите разные экраны, например:

enter image description here

Работает как в основном приложении на основе документов, которое я создал, так и в приложении Apple Files.

Надеюсь, это поможет ...

...