Измерьте расстояние между двумя ARImageAnchors в ARKit - PullRequest
1 голос
/ 27 января 2020

Моя дипломная работа включает в себя воспроизведение поведения, показанного в этом видео . Я использую ARKit, и это не очень важно для iOS версии или возможностей устройства.

То, чего я пытаюсь достичь, - это соединить два разных узла SCN, когда они достаточно близки для этого, но, похоже, что их информация об их положении довольно нерегулярна и, кажется, не соответствует их фактическому положению на сцене.

Когда я печатаю информацию о двух узлах, над которыми я работаю, lldb дает мне числа, подобные этим :

  • Объект X:

Якорь .tranform.columns.3:

SIMD4<Float>(0.019269282, -0.2338481, -0.2880894, 1.0)

Узел .worldPosition:

▿ Optional<SCNVector3>
  ▿ some : SCNVector3
    - x : 0.019269282
    - y : -0.2338481
    - z : -0.2880894
  • Объект Y:

Якорь .tranform.columns.3:

SIMD4<Float>(-7.6405444, -25.205889, -26.780603, 1.0)

Узел .worldPosition:

▿ Optional<SCNVector3>
  ▿ some : SCNVector3
    - x : -7.6405444
    - y : -25.205889
    - z : -26.780603

Я имею в виду, почему так много Разница между позициями двух объектов? (как для их якоря, так и для положения узла). Их родители равны (узел root сцены) и находятся в одной и той же сцене, разве они не должны дать мне хотя бы одинаковые позиции для каждого узла?

При измерении расстояния между двумя объектами, которые я имею заметил, что следующий код

guard let p1 = a0.atomAnchor?.transform.simd_vector3,
     let p2 = a1.atomAnchor?.transform.simd_vector3 else { return }

     let d = simd_distance(p1, p2)
     print(d)

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

Мой ARSessionDelegate выполняет следующие действия:

extension ViewController: ARSessionDelegate {

    var minimumDistanceBetweenAtoms: Float {
        return 50
    }

    // MARK:- Delegate method
    func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
        guard let imageAnchor = anchor as? ARImageAnchor else {
            print("anchor not found")
            return
        }

        guard let worldScene = sceneView?.scene else { print("Scene not initialized"); return }
        guard let atomFound = atoms.first(where: { $0.referenceImage.name == imageAnchor.referenceImage.name }) else {
            print("atom not found in resources")
            return
        }

        guard atomFound.type != .referenceObject else { return }
        visibleAtoms.append(atomFound)
        atomFound.initializeAtom(inScene: worldScene, withAnchor: imageAnchor)
    }


    func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
        guard let imageAnchor = anchor as? ARImageAnchor else {
            print("anchor not found")
            return
        }
        visibleAtoms.forEach({
            guard !$0.flag else { return }
            $0.didUpdateTo(anchor: imageAnchor)
        })
    }

    func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
        visibleAtoms.forEach({ [weak self] a0 in
            guard !a0.isMoving(), !a0.flag else { return }

            self?.visibleAtoms.forEach({ a1 in
                guard !a1.isMoving(), !(a1 === a0), !a0.flag else { return }

                guard let p1 = a0.atomAnchor?.transform.simd_vector3,
                    let p2 = a1.atomAnchor?.transform.simd_vector3 else { return }


                let d = simd_distance(p1, p2)
                print(d)
                if d < minimumDistanceBetweenAtoms {
                    if let molecule = a0.combination(withAtom: a1) {
                        a0.flag = true
                        a1.flag = true
                    }
                }
            })
        })
    }

}

Рассмотрим класс Atom как инкапсулятор для поведения узла:

    func initializeAtom(inScene scene: SCNScene, withAnchor anchor: ARImageAnchor) {
        guard let object = type.atomObject() else { return }

        let (min, max) = object.boundingBox
        let size = SCNVector3Make(max.x - min.x, max.y - min.y, max.z - min.z)

        let widthRatio = Float(anchor.referenceImage.physicalSize.width) / size.x
        let heightRatio = Float(anchor.referenceImage.physicalSize.height) / size.z
        object.transform = SCNMatrix4(anchor.transform)

        guard let finalRatio = [widthRatio, heightRatio].min() else { return }

        let appearanceAction = SCNAction.scale(to: CGFloat(finalRatio / 2), duration: 0.4)
        appearanceAction.timingMode = .easeOut
        object.scale = SCNVector3Make(0, 0, 0)
        scene.rootNode.addChildNode(object)
        object.runAction(appearanceAction)


        atomAnchor = anchor
        atomScene = scene
        atomObject = object
    }

    func didUpdateTo(anchor: ARImageAnchor) {
        guard anchor == atomAnchor else { return }
        atomAnchor = anchor
        if isMoving() { atomObject?.removeAction(forKey: "movement") }
        moveTo(newAnchor: anchor)
    }

    private func moveTo(newAnchor: ARAnchor, withDurationOf duration: TimeInterval = 0.3) {
        let action = SCNAction.move(to: newAnchor.transform.vector3, duration: duration)
        atomObject?.runAction(action, forKey: "movement")
    }

Моя ARWorldTrackingConfiguration просто устанавливает эталонные изображения и запускается с аргументами [.resetTracking, .removeExistingAnchors].

У меня есть файл Utils для размещения всех необходимых мне полезных расширений, вот часть, которую я использую в этом коде:

extension simd_float4x4 {

    var vector3: SCNVector3 {
        return SCNVector3(columns.3.x, columns.3.y, columns.3.z)
    }

    var simd_vector3: simd_float3 {
        return simd_float3(columns.3.x, columns.3.y, columns.3.z)
    }

}

Подводя итог, мой вопрос: могу ли я создать SCNAction, который перемещает один SCNNode в другой, помещенный в другой ARImageAnchor?

PS .: Код Github .

...