LoginSignup
2
2

More than 3 years have passed since last update.

ARKit+SceneKitでオブジェクトを配置するときのカーソル表示

Posted at

ARで任意のオブジェクトを配置するときなどに表示するカーソルについて。

やりたいこと

標準の計測アプリのこれ。
keisoku_app.png

カーソルのtransform

画面中央座標のヒットテストを行い、取得したworldTransformをもとに設定する。
ARSCNViewhitTest(_:types:) でヒットした場所を ARHitTestResultworldTransform から取得できるので、これをカーソルの場所・姿勢とする。
ここで、カーソルを SCNPlane にした場合に、カーソルの場所・姿勢=ヒットした場所・姿勢 としてしまうと平面のジオメトリと干渉してチラつくので、平面の上方向のベクトル worldTransform.columns.1 を使ってカーソルの位置を調整する。

// 平面が向いている方向(UP)に0.01mずらした位置にカーソルを設定
cursorTransform.columns.3 += worldTransform.columns.1 * 0.01
self.cursorNode.simdTransform = cursorTransform

transform を設定するのでSCNNodeが拡大縮小(scale ≠ (1.0,1.0,1.0))されている場合、拡大縮小がリセットされる点に注意。
その場合は worldTransform.columns.0 ~ 2 に拡大縮小率を掛けた値を、cursorTransform.columns.0 ~ 2 に設定する。

出来上がり

カーソルの上方向が分かり易いようにピラミッド形状にしている。
demo.gif

ソースコード

ViewController.swift
import ARKit
import SceneKit

class ViewController: UIViewController, ARSCNViewDelegate {

    @IBOutlet weak var scnView: ARSCNView!

    private let device = MTLCreateSystemDefaultDevice()!
    private let cursorNode = SCNNode()

    override func viewDidLoad() {
        super.viewDidLoad()

        // AR Session 開始
        self.scnView.delegate = self
        let configuration = ARWorldTrackingConfiguration()
        configuration.planeDetection = [.horizontal, .vertical]
        self.scnView.session.run(configuration, options: [.removeExistingAnchors, .resetTracking])
        // カーソルノード準備
        let pyramid = SCNPyramid(width: 0.1, height: 0.03, length: 0.1)
        pyramid.firstMaterial!.diffuse.contents = UIColor.yellow
        self.cursorNode.geometry = pyramid
        self.scnView.scene.rootNode.addChildNode(self.cursorNode)
        self.cursorNode.isHidden = true
    }
    //
    // アンカーが追加された
    //
    func renderer(_: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
        guard let planeAnchor = anchor as? ARPlaneAnchor else { return }

        // 平面ジオメトリノードを追加
        guard let geometory = ARSCNPlaneGeometry(device: self.device) else { return }
        geometory.update(from: planeAnchor.geometry)
        let material = SCNMaterial()
        material.lightingModel = .physicallyBased
        material.diffuse.contents = UIColor.red.withAlphaComponent(0.7)
        geometory.materials = [material]
        let planeNode = SCNNode(geometry: geometory)
        DispatchQueue.main.async {
            node.addChildNode(planeNode)
        }
    }
    //
    // アンカーが更新された
    //
    func renderer(_: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
        guard let planeAnchor = anchor as? ARPlaneAnchor else { return }

        DispatchQueue.main.async {
            for childNode in node.childNodes {
                // 平面ジオメトリを更新
                guard let planeGeometry = childNode.geometry as? ARSCNPlaneGeometry else { continue }
                planeGeometry.update(from: planeAnchor.geometry)
                break
            }
        }
    }
    //
    // フレームごとに呼び出される
    //
    func renderer(_ renderer: SCNSceneRenderer, updateAtTime _: TimeInterval) {
        DispatchQueue.main.async {
            // 画面中央でヒットテスト
            let bounds = self.scnView.bounds
            let screenCenter =  CGPoint(x: bounds.midX, y: bounds.midY)
            let results = self.scnView.hitTest(screenCenter, types: [.existingPlaneUsingGeometry])
            guard let existingPlaneUsingGeometryResult = results.first(where: { $0.type == .existingPlaneUsingGeometry }),
                  let _ = existingPlaneUsingGeometryResult.anchor as? ARPlaneAnchor else {
                // カーソル非表示
                self.cursorNode.isHidden = true
                return
            }

            // ヒットした場所のtransformをカーソルのtransformに転記
            let worldTransform = existingPlaneUsingGeometryResult.worldTransform
            var cursorTransform = worldTransform
            // 平面が向いている方向(UP)に0.01mずらした位置にカーソルを設定
            cursorTransform.columns.3 += worldTransform.columns.1 * 0.01
            self.cursorNode.simdTransform = cursorTransform

            self.cursorNode.isHidden = false
        }
    }
}
2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2