[iOS11][ARKit] SCNBoxとSCNTextでラベルみたいなノードを作成してみました

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

1 はじめに

UIKitなどのフレームワークでは、通常、UILabelのように、軽易にテキストを表示できるコントロールが用意されています。

ARKitでは、プリミティブなオブジェクトとして、立方体や球、平面などが利用可能ですが、残念ながらラベルのようなものはありません。

そこで、今回は、平面(SCNBox)と文字列(SCNSText)を組合させて、ラベルのようなNodeを作成してみました。

001

文字列の背景に当たる部分は、平面(SCNPlane)でも良さそうですが、オプションとして厚みのある背面にも対応したくて、SCNBoxにしました。

最初に、動作しているようすです。次の3つのパターンを試しています。

  • 文字、背景共に厚み無し
  • 文字のみ厚み有り
  • 文字、背景共に厚み有り

1 文字列

SCNTextは、文字列と、その厚みを指定して生成します。

let str = SCNText(string: "本日は晴天なり", extrusionDepth: 0)

フォントの指定では、同時にサイズも指定できますが、1以下を指定すると、特に日本語の場合、フォントが潰れてしまう事があるようなので、ここでは1を指定しておきます。

str.font = UIFont(name: "HiraginoSans-W6", size: 1);

SCNTextgeometryに設定したノードは、その原点が、先頭文字の左下になります。 後で追加する背後のパネルの中央に来るように、バウンディングボックスから全体のサイズを求めてづらしています。

// バウンディングボックスから縦横の長さを取得する
let (min, max) = (textNode.boundingBox)
let w = CGFloat(max.x - min.x)
let h = CGFloat(max.y - min.y)
// 中心になるように移動する
textNode.position = SCNVector3(-(w/2), -(h/2) - 0.9 , 0.001 + textThickness )

2 背景パネル

背景となるパネル部分は、最初に紹介したように、厚みも設定できるように、SCNBoxで作成しています。 また、そのサイズは、先のSCNTextのバウンディングボックスからサイズを求めています。

let panelNode = SCNNode(geometry: SCNBox(width: w * 1.1, height: h * 1.1, length: panelThickness, chamferRadius: 0))

3 縮尺

ここまでの作業で、ノード全体のサイズは、SCNTextの文字列及び、フォントの種類とサイズから決定されています。 そこで、最終的に表示したいサイズとの比率を計算し、scaleで調整しています。

let ratio = width / w
scale = SCNVector3(ratio, ratio, ratio)

4 ラベルのようなクラス

SCNBoxSCNSTextを組合させて作成した、ラベルのようなNodeを生成するクラスの全コードです。

コンストラクタで指定するパラメータは、以下のとおです。

  • text: String 表示する文字列
  • width: CGFloat 表示するサイズ(横幅)
  • textColor: UIColor テキストの表示色
  • panelColor: UIColor 背景パネルの表示色
  • textThickness: CGFloat テキストの厚み
  • panelThickness: CGFloat 背景パネルの厚み
class LabelNode: SCNNode {

    init(text: String, width: CGFloat, textColor: UIColor, panelColor: UIColor, textThickness: CGFloat, panelThickness: CGFloat) {
        super.init()

        //*******************************************************
        // テキストノードの生成
        //*******************************************************
        let str = SCNText(string: text, extrusionDepth: textThickness)
        str.font = UIFont(name: "HiraginoSans-W6", size: 1);
        let textNode = SCNNode(geometry: str)

        // バウンディングボックスから縦横の長さを取得する
        let (min, max) = (textNode.boundingBox)
        let w = CGFloat(max.x - min.x)
        let h = CGFloat(max.y - min.y)
        // 中心になるように移動する
        textNode.position = SCNVector3(-(w/2), -(h/2) - 0.9 , 0.001 + textThickness )
        // 色を設定
        textNode.geometry?.materials.append(SCNMaterial())
        textNode.geometry?.materials.first?.diffuse.contents = textColor

        //*******************************************************
        // パネルノードの生成
        //*******************************************************
        let panelNode = SCNNode(geometry: SCNBox(width: w * 1.1, height: h * 1.1, length: panelThickness, chamferRadius: 0))
        // 色を設定
        panelNode.geometry?.materials.append(SCNMaterial())
        panelNode.geometry?.materials.first?.diffuse.contents = panelColor

        addChildNode(textNode)
        addChildNode(panelNode)
        //*******************************************************
        // サイズを調整する
        //*******************************************************
        let ratio = width / w
        scale = SCNVector3(ratio, ratio, ratio)
    }
}

5 最後に

今回は、ラベル風のノードを作成してみました。このクラスがあれば、ちょっと文字を空間に表示したい時に便利かもしれません。

厚みをどれぐらいにすると気持ちいいのか、悩みどころです・・・

6 参考リンク


Introducing ARKit
Apple Developer > Documentation > ARKit
[iOS 11] はじめてのARKit #WWDC2017
[iOS 11][ARKit] 距離の計測について #WWDC2017
[iOS 11][ARKit] 平面の検出について #WWDC2017
[iOS 11][ARKit] 物理衝突を実装して、キューブを落として見る #WWDC2017
[iOS 11][ARKit] 空間に3Dテキストを表示してみる #WWDC2017
[iOS 11] ARKitでソファー設置からあの子の身長測定まで色々やってみた
[iOS 11][ARKit] 近所の駐車場で、大型ディスプレイでビデオを見る!
[iOS11][ARKit] オブジェクトの位置と回転「常にこっちを向いている怪獣」