Apple Engine

Apple, iPhone, iOS, その周辺のことについて

がんばって USD ファイルをテキストで書いてみる

SCNScene から USDA が書きだせたので調べながら手書きで書いてみる。
今回は Apple 製品で動くものなので、実際の USD の仕様と異なるので注意。

本来は公式ページから GitHub へ行きツール群をビルドしそこから振る舞いを調べるべきなのだが、自分の環境だと USD 関連でビルドに必要な PySide がビルドできないため諦めた。
多分、Qt が悪さをしている。

graphics.pixar.com

 

プリミティブを描画してみる

USDA では最初の行に「#usda 1.0」が必要になる。

ひとまず、球体を描画してみる。

#usda 1.0

def Sphere "sphere" {}

f:id:x67x6fx74x6f:20190124191541p:plain

 

立方体、円錐などが描画できる。

#usda 1.0

def Cube "cube" {}
#usda 1.0

def Cone "cone" {}

f:id:x67x6fx74x6f:20190124191545p:plain f:id:x67x6fx74x6f:20190124191536p:plain

 

空のノード追加してみる

「def Scope "ノード名"」で SceneKit で言う所の SCNNode として定義し追加できる。

#usda 1.0

def Scope "Node" {
}

 

また、入れ子にもできる。

#usda 1.0

def Scope "Node"
{
    def Scope "Node2"
    {
    }
}

 

メッシュ(ジオメトリ)をつくってみる

メッシュは「def Mesh」で定義でき、 基本的には SceneKit のカスタムジオメトリと作り方は同じで、 頂点の位置とインデックス、面を貼るためのカウント、テクスチャ用の UV、法線を設定してジオメトリが完成する。

以下のコードでは三角形のメッシュを描画する。

#usda 1.0

def Mesh "CustomMesh"
{
    uniform bool doubleSided = 0
    float3[] extent = [(0, 0, 0), (1, 1, 0)]
    int[] faceVertexCounts = [3]
    int[] faceVertexIndices = [0, 1, 2]
    normal3f[] normals = [(0, 0, 1),(0, 0, 1),(0, 0, 1)]
    point3f[] points = [(0, 0, 0),(1, 0, 0),(0, 1, 0)]
    float2[] primvars:Texture_uv = [(0.0, 0.0),(1.0, 0.0),(0.0, 1.0)](
        interpolation = "vertex"
    )
}

 

以下、コードを軽く説明。

パラメーター 説明
doubleSided メッシュの表示設定。0 で片面、1で両面。省略可。
extent メッシュの大きさ。省略可。
faceVertexCounts 面の頂点数。3で Xcode では三角ポリゴン意外は表示できない模様
faceVertexIndices あとで設定するポイントの配列の順番。時計回りで座標プラス値方向に面が貼られる。
normals 各頂点の法線。
points 各頂点の座標。
primvars:Texture_uv 各頂点の 0 〜 1 の UV 値。「(interpolation = "vertex")」は頂点を参照してつけるためこれを省略するとテクスチャが貼られない。

f:id:x67x6fx74x6f:20190124191559p:plain

 

ちなみにポイント4点のメッシュを作る場合は三角形のポリゴンを2つ組み合わせる。

#usda 1.0

def Mesh "CustomMesh"
{
    uniform bool doubleSided = 0
    float3[] extent = [(0, 0, 0), (1, 1, 0)]
    int[] faceVertexCounts = [3,3]
    int[] faceVertexIndices = [0, 1, 2, 2, 1, 3]
    normal3f[] normals = [(0, 0, 1),(0, 0, 1),(0, 0, 1),(0, 0, 1)]
    point3f[] points = [(0, 0, 0),(1, 0, 0),(0, 1, 0),(1, 1, 0)]
    float2[] primvars:Texture_uv = [(0.0, 0.0),(1.0, 0.0),(0.0, 1.0),(1.0, 1.0)](
        interpolation = "vertex"
    )
}

f:id:x67x6fx74x6f:20190124191604p:plain

 

scn ファイルに変換してテクスチャを貼るとこんな感じ。

f:id:x67x6fx74x6f:20190124191555p:plain

 

カメラを追加する

「def Camera」で定義できる。
以下、SceneKit のカメラ設定と名前が同じなので分かりやすいだろう。

下から2つの xformOp:transform と xformOpOrder はカメラの位置、回転、拡大縮小を設定するトランスフォームの値。

#usda 1.0

def Camera "camera"
{
    float2 clippingRange = (100, 10000)
    float focalLength = 20.784609
    float focusDistance = 300
    float fStop = 5.6
    float horizontalAperture = 36
    float verticalAperture = 24
    matrix4d xformOp:transform = ( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0.5, 0.5, 1.0, 1) )
    uniform token[] xformOpOrder = ["xformOp:transform"]
}

f:id:x67x6fx74x6f:20190124191550p:plain

 

その他設定

初期設定。
defaultPrim は起点となる Scope(ノード)、endTimeCode、startTimeCode、timeCodesPerSecond はアニメーション設定。
upAxis は上方向の軸。

#usda 1.0
(
    defaultPrim = "Node"
    endTimeCode = 1
    startTimeCode = 1
    timeCodesPerSecond = 60
    upAxis = "Z"
)

def Sphere "sphere" {}

 

まとめ

気合いがあれば書けるがかなり面倒。

Blender とかで直接書き出せるようになってほしい。

SceneKit アプリで使用しているシーンを Pixar USD として書き出す

f:id:x67x6fx74x6f:20190124184858j:plain

Model I/O framework は Metal で使用する一連をアセット操作するものがああり、ファイルを読み込んでアセットにしたり、アセットを別のファイル形式に書き出すことができる。

アセットの設定は Model I/O の MDLAsset を使用するのだが、MDLAsset では SceneKit のシーンファイル以外にも SCNScene 自体を読み込みアセットとして作成すること可能。

自分の場合 iOS でファイル操作をするのが面倒だったので macOS の NSOpenPanel で書き出すフォルダを調べてそこに書き出したが、 iOS の場合はアプリのドキュメントフォルダに保存するなどで書き出せると思われる。

 

注意点

SCNScene が正確に書き出されるわけではないの注意。
現状、基礎情報、ジオメトリ、カメラのみ書き出され、ライトなどは無視される。

 

ジオメトリはマテリアル、位置や回転や拡大縮小、アニメーション情報など付加されないので、この書き出しは Xcode のコマンドラインから USDZ に変換する用のものと考えてよいと思われる。

また、ワールド座標が Z 軸が上の Z up で書き出されるのでかなり罠。
USD の仕様に合わせていると思われ、xcrun usdz_converter では Y up かつ Physically Based のマテリアルが再設定される。

 

内容的には Xcode 上のデバッグの View UI Hierarchy で SCNScene を選択した際の「Export」から吐き出されるファイルに近い。  

f:id:x67x6fx74x6f:20190124185003p:plain
Debug 画面の View UI Hierarchy

 

コード

SceneKit は Model I/O を内包しているが、SceneKit の機能を参照する Model I/O の機能であるため、以下のインポートが必要。

import SceneKit.ModelIO

 

以下のコードで SCNScene と書き出し先のフォルダを設定する。

var assets = MDLAsset.init(scnScene: scene)

var fileURL = URL(string: "書き出しフォルダまでのパス")!
fileURL.appendPathComponent("export.usda")

if(MDLAsset.canExportFileExtension("usda")){
    do {
        try self.assets.export(to: fileURL)
    } catch let error {
        DispatchQueue.main.async {
            print(error.localizedDescription)
        }
    }
}else{
    print("This file format (extension) is not supported")
}

 

コードではアスキーファイルの USDA を設定しているが、バイナリ版の USDC も可能。
USDZ での書き出しはできない。

iOS の場合、USDC は USDZ 同様に QuickLook でプレビューできるため、USDC ファイルを直接もしくは Mac などの AirDrop でファイルアプリに保存すると AR のプレビューができる。
(ただし、そのままだとワールド座標が Z up になる)

 

まとめ

使い道はあまりないが、アプリで使用している SCNScene の USD 書き出しが簡単できることがわかった。

Xcode 10 系で scnassets フォルダにファイルを追加すると CodeSign failed エラーになる件

f:id:x67x6fx74x6f:20190118191253p:plain

原因不明。

 

現象

Automatically manage signing で Team を設定し実機にビルド。
最初は実機で動作したが、scnassets フォルダにファイルを追加すると 「Command CodeSign failed with a nonzero exit code」でビルドが止まる。

 

対処法

scnassets フォルダを選択し Identity and Type (Command + Option + 1) で Target Membership のチェックを外して一度ビルド。
再度チェックを入れてビルドする。

もしくは scnassets フォルダ を使用しない。

根本的な解決方法があったら教えて欲しい。

ARKit (SceneKit) で Unreal Engine や Unity のようなグローパーティクルをつくる

 

SceneKit でグローなパーティクルのサンプルがなかったのでつくってみることにした。

つくってから気がついたが Apple 謹製のサンプルファイル Fox 2 での背景の火の粉のパーティクルがそれに当たる。

 

パーティクルにグローができる原理

f:id:x67x6fx74x6f:20190110190554p:plain
デフォルト / HDR + Bloom

3DCG におけるグローとは、カメラにおける光の滲みのような状態で、強い光源にレンズを向けた際に光が白っぽくかぶるフレア現象。
または、デジカメで強い光源がイメージセンサーに画素の許容量を超えた際に光が滲んだように周囲に広がるブルーミングのこと。

SceneKit では HDR 設定をオンにして Bloom の設定をいじり、ジオメトリでマテリアルの輝きの値である Emission をあげるとグローができる。

これと同様にパーティクルも輝きの値をあげるとグローの表現が実現できる。

 

パーティクルで追加された設定

iOS 11 の SCNParticleSystem では、パーティクルに輝度を与える以下のプロパティが追加されている。

  • particleIntensity
  • particleIntensityVariation

particleIntensity は輝度で、particleIntensityVariation 他の variation と同様にパーティクル生成時に入れた値をプラスマイナスする。

particleIntensity が 4、particleIntensityVariation 2 の場合、4 ± 2 となるので、2 〜 6 の値が生成時にランダムで設定される。

 

HDR の設定をしてみる

今回、作成したサンプルが ARKit のプロジェクトであるためカメラ設定をコードで書いているが、Scene Editor でカメラを設置して値をいじった方が楽だと思われる。

hdrCamera = sceneView.pointOfView?.camera
hdrCamera.wantsHDR = true

hdrCamera.wantsExposureAdaptation = true
hdrCamera.exposureAdaptationBrighteningSpeedFactor = 0.4
hdrCamera.exposureAdaptationDarkeningSpeedFactor = 0.6
hdrCamera.minimumExposure = -15
hdrCamera.maximumExposure = 15

hdrCamera.bloomIntensity = 2.0
hdrCamera.bloomThreshold = 0.6
hdrCamera.bloomBlurRadius = 30

 

軽く説明すると wantsHDR = true で HDR カメラ表示に変更、wantsExposureAdaptation と以下で露出の自動調整を行なっている。

その下の bloomIntensity、bloomThreshold、bloomBlurRadius でブルーム効果の設定を行なっている。
文字通り bloomIntensity でブルームの強さ、bloomThreshold でしきい値、bloomBlurRadius で光の滲み具合の半径設定している。

bloomIntensity や bloomBlurRadius を小さくしすぎたり、bloomThreshold を大きすると効果が現れないので注意。

 

パーティクルの設定

f:id:x67x6fx74x6f:20190110185434p:plain

画像や下の方にある GitHub のサンプルファイルを参照。 particleIntensity は強めの 3 を設定している。

 

今回のグローとは関係ないが、Animate Color で色を設定するとパーティクルが消えるまで左から右へと色が変化する。右側のアルファ値を 0 にすると徐々に消えていくようになる。

f:id:x67x6fx74x6f:20190110185518p:plain

 

また、Color variation で HSB の色のヴァリエージョン値を設定すると各パーティクルで色が変わる。

f:id:x67x6fx74x6f:20190110185539p:plain

 

サンプルファイル

github.com

 

まとめ

HDR とパーティクルの設定を行うだけでもわりとドラマチック?な表現ができると思われる。

サンプルファイルではジオメトリが大きすぎてわかりづらいが、Depth Of Field をちゃんと設定すると背景のボケが付加できたりするとさらに効果的になる。

Blackmagic eGPU / eGPU Pro、Vega 16 / 20 どれを選ぶか

f:id:x67x6fx74x6f:20190109210617j:plain

 

Apple、Macbook Pro の Web ページに eGPU Pro のグラフが追加されていたのでまとめてみた。

www.apple.com

 

eGPU Pro と eGPU の価格差は 1.66 倍。
Apple のページを信じると妥当な感じである。

GPU 税別価格
eGPU Pro (Vega 56) 149,000
eGPU (RX 580) 89,800

 

以下、MacBook Pro 15 inch の GPU Radeon RX 560X を 1 とした時の割合。
一応、PC で動作させている GPU の差でも 40〜60% ぐらいなので許容範囲。

eGPU eGPU Pro
Unity Editor 2.3倍 3.8倍 1.65倍
Rise of the Tomb Raider 2倍 3倍 1.5倍
Blackmagic DaVinci Resolve Studio 1.8倍 2.7倍 1.5倍
Maxon Cinema 4D 3.1倍 4.7倍 1.51倍
Final Cut Pro X 1.3倍 1.6倍 1.23倍

 

MacBook Pro 15inch Vega 16 / 20

公式では Vega20 で RX 560X と比べて 60% UP らしい、ページのものと照らし合わせてみるとこの様な感じ。

Vega 20 eGPU eGPU Pro
Unity Editor 1.6倍 2.3倍 3.8倍
Rise of the Tomb Raider 1.5倍 2倍 3倍
Blackmagic DaVinci Resolve Studio 1.55倍 1.8倍 2.7倍
Maxon Cinema 4D 1.6倍 3.1倍 4.7倍

 

Vega 20 は ¥38,500(税別)なので 38500 / 6 = 6416.66。
10%当たりの価格でいうと優秀。
60%までしか増えないが。

Vega 16 は 48% ぐらいのスペックアップだと思われるので、コスパで考えるとお得だが、1万ちょいで 20 になるので悩みどころ。

 

まとめ

RX 560X の約1.4 または 1.6 倍性能の一体型を望むか、外付けでも約 2 または 3倍の性能を求めるかになる。

 

60%までの増加でよければ、eGPU よりコストが若干価格が安いので MacBook Pro 15inch + Vega 20(¥302,800〜 + ¥38,500)はよいと思われる。

RX 560 の1.6倍以上欲しいか、他の Thunderbolt 3 が接続可能な Mac なら eGPU、eGPU Pro。
大体価格に見合う性能向上なので、お金に余裕があれば Pro を買ってもよいかと。

 

MacBook Pro 13inch は CPU が 4 Core なので、持ち運ぶ用途がなく、ディスプレイがあるなら Mac mini を選んだ方が良さげ。

 

また、iMac Pro 8 Core が ¥558,800 (税別) となっているため、
他の Mac のカスタマイズで、メモリ 32GB / 1TB SSD に設定した場合に eGPU / eGPU Pro で本体価格以下の価格に近いと 5K のディスプレイを持った iMac Pro が購入できてしまうので注意。

  • iMac Pro 8 Core = 本体 ¥469,000 - 27inch 5K ディスプレイ + eGPU
  • iMac Pro 8 Core = 本体 ¥409,800 - 27inch 5K ディスプレイ + eGPU Pro

 

Blackmagic のものはそれなりにするので、ケースとボードを別に買うとやすくなるかも。

Mac でサポートしているケース一覧があるのでこちらを参照。
特に Vega 64 系や Pro WX 9100 は電源が 650W のケースじゃないと正常に動かないので注意。

support.apple.com

 

おまけ

eGPU のケースは割とでかいので、ラップトップは縦置きできるスタンドがあると便利。

2019年はじめに思う Apple におこなってもらいたいこと

なんとなく書いてみた。
あまり内容はない。

 

iPhone のカメラ性能をよくする

スマートフォンのアプリでもっとも長い時間使われているのは SNS アプリであり、 SNS のアプリで閲覧や文字を書く以外で時間が使われているのは写真(動画)撮影だろう。

絵を描いたり、楽器の演奏や作曲、歌唱などは長い期間の練習を経て習得する技術であるが、 カメラに関しては習得するべき技術が身体的なものではないので、機械で技能を拡張することができる。

そのため、カメラ性能を上げる戦略は正しく、 Hauwei の様にレンズやセンサーを工夫したり、Google Pixel 3 のように専用チップを開発して、カメラ機能にウエイトを置くのは当然だと思われる。

噂では次期 iPhone では3つ目のカメラを搭載するとのことだが、個人的にはカメラセンサーの向上による鮮明さと夜間で撮影した際のノイズ除去を頑張ってほしい。

 

Siri をより賢く

正直、AR や VR の世界より先に Siri 等のヴァーチャルアシスタントシステムの機能向上の方が人々の生活に役立つとは思っている。

現状、アメリカで使用できて日本で使用できない Siri の機能があるので、まずはそこから。

 

HomeKit 製品の拡充

生活の中心となる家電でとして Media Hub や Home Hub などの構想はあったが、いまだに実現されておらず、生活の中ではスマートフォンがその役割を奪ってしまった。

ただ、家電の操作をリモートで行うことができれば、便利さが上がり、生活はもっと楽なものになるだろう。
家電各社は色々な試みを行なっているが HomeKit に対応していただきたいところ。

HomeKit 対応する場合、Lightning ケーブルの様に専用のチップや契約が必要であるため、 厳しさはあるが、今年の CES ではテレビなど HomeKit 対応を謳ったものが増えてきた。

とりあえず、日本では2年間費用無償とか土下座でもして Panasonic あたりに Apple は HomeKit 対応をしてもらってほしい。
照明スイッチやエアコンなど Apple TV 経由で家に近づくと ON になるとか可能になるので。

HomeKit 対応製品が増え広まれば、Apple が人々の生活を掌握できる様になる。
Apple 製品が売れている国に関しては早急にホームオートメーションのシェアを掴んでいく必要性があると思われる。

 

AR や VR と仮想現実空間

AR に関しては Siri や HomeKit の機能強化の先の話だと思われるが、 AR グラスなど常時体験できるならアシスタントデバイスとして、製品ラインナップやプラットフォームの強化がされる。

今の ARKit ではできない遮蔽物の認識が可能になれば、2.0 で追加された空間共有とともに仮想空間での幅広い表現で可能になるだろう。

また、A12 / A12X Bionic での Metal 機能強化で macOS の GPU 機能と同等のもの(性能ではないので注意)が使える様になったため、 AR グラスや VR なども期待できそうではある。

 

Apple Watch の高機能版

Apple Watch Pro のような存在。
ある程度需要はある気はする。

現状、iPhone アプリからのデータ転送なので、プリインストールされている Apple 謹製アプリはセルラーで動く場合があるが、多くのアプリが iPhone なしではちゃんと動作しないので。

バッテリー持たなそうだけど。

 

インナーイヤー型の AirPods

現状の AirPods は、東京など人が密になっているところでは、外界の音がうるさすぎてボリュームを上げる必要があり面倒なので。
Beats X を使えばよいのだが、バッテリーの問題でインナーイヤー型の AirPods が出てくれればと思っている。

Apple の中の人は山手線とか鉄道の駅や電車で AirPods を使ってフィールドテストして欲しい。わりと辛い。

 

今後の macOS

多分、いつか iOS と macOS は同等のものになるとは思っている。
Apple A チップの機能がディスクトップに迫ったり、Metal 機能が同じ様になったりと歩み寄っており、iOS のアプリにメニューバーやタッチバーつけるぐらいの手軽さにはなってほしい。

問題があるとするなら OpenGL が廃止になり Metal でアプリの表示を描画することになる点だろう。

ただ Metal は WWDC 2014 で発表されており、もう 5 年目を迎えそうなので、もうそろそろ諦めて OpenGL から解脱してほしい。

 

まとめ

Apple はハードとソフトを上手く融合させてユーザー体験を提供するメーカーではあるが、来るべき PC/Mac などのコンピューターの終焉(専門職以外使わなくなる)とスマートフォンのコモディティ化を考えると、Siri や AR などを絡めたアシスタントや Apple Music などサービスの提供を強化していく流れになるだろう。

ARKit や SceneKit で使用している simd について - クォータニオンでの関数編 その2 -

今回はクォータニオンで使用する線形補間の関数について。
基本的には球状での回転の補間となる。

用意されている関数は以下のもの。

  • simd_slerp
  • simd_slerp_longest
  • simd_spline
  • simd_bezier

 

ちなみに線形補間に関しては iOS でサンプルアプリがあるのでそちらを参考に。

Rotating a Cube by Transforming Its Vertices | Apple Developer Documentation

 

ちゃんとした説明は WWDC 2018 でも行なっているのでそちらを参照。

developer.apple.com

 

以下、コードを書いていく。
macOS の Swift Playground で試していたので、iOS で使用する場合は NSColor を UIColor に変更するべし。

 

simd_slerp

クォータニオン q0 と q1 の間の最短の弧に沿った線形補間を行う。

f:id:x67x6fx74x6f:20190105032736p:plain
simd_slerp

let rotations: [simd_quatf] = [
    simd_quatf(angle: 0,
                axis: simd_normalize(simd_float3(x: 0, y: 0, z: 1))),
    simd_quatf(angle: .pi * 0.05,
                axis: simd_normalize(simd_float3(x: 0, y: 1, z: 0))),
    simd_quatf(angle: .pi * 0.8,
                axis: simd_normalize(simd_float3(x: 1, y: 0, z: -1))),
    simd_quatf(angle: .pi * 0.15,
                axis: simd_normalize(simd_float3(x: 0, y: 1, z: 0))),
    simd_quatf(angle: .pi * 0.2,
                axis: simd_normalize(simd_float3(x: -1, y: 0, z: 1)))
]

for i in 0 ... rotations.count - 2 {
    for t: Float in stride(from: 0, to: 1, by: 0.001) {
        let q = simd_slerp(
            rotations[i],
            rotations[i + 1],
            t
        )

        let node = SCNNode()
        let sphere = SCNNode(geometry: SCNSphere(radius: CGFloat(t/4)))
        sphere.geometry?.firstMaterial?.diffuse.contents = NSColor(red: 1.0, green: CGFloat(t), blue: CGFloat(t), alpha: 1.0)
        node.addChildNode(sphere)
        node.simdPosition = q.act(float3(0,0,5))
        scene.rootNode.addChildNode(node)
    }
}

 

simd_slerp_longest

クォータニオン q0 と q1 の間からもっとも遠い弧に沿った線形補間を行う。

f:id:x67x6fx74x6f:20190105032817p:plain
simd_slerp_longest (正面だとわかりづらいので斜めに始点移動)

let rotations: [simd_quatf] = [
    simd_quatf(angle: 0,
                axis: simd_normalize(simd_float3(x: 0, y: 0, z: 1))),
    simd_quatf(angle: .pi * 0.05,
                axis: simd_normalize(simd_float3(x: 0, y: 1, z: 0))),
    simd_quatf(angle: .pi * 0.8,
                axis: simd_normalize(simd_float3(x: 1, y: 0, z: -1))),
    simd_quatf(angle: .pi * 0.15,
                axis: simd_normalize(simd_float3(x: 0, y: 1, z: 0))),
    simd_quatf(angle: .pi * 0.2,
                axis: simd_normalize(simd_float3(x: -1, y: 0, z: 1)))
]

for i in 0 ... rotations.count - 2 {
    for t: Float in stride(from: 0, to: 1, by: 0.001) {
        let q = simd_slerp_longest(
            rotations[i],
            rotations[i + 1],
            t
        )

        let node = SCNNode()
        let sphere = SCNNode(geometry: SCNSphere(radius: CGFloat(t/4)))
        sphere.geometry?.firstMaterial?.diffuse.contents = NSColor(red: 1.0, green: CGFloat(t), blue: CGFloat(t), alpha: 1.0)
        node.addChildNode(sphere)
        node.simdPosition = q.act(float3(0,0,5))
        scene.rootNode.addChildNode(node)
    }
}

 

simd_spline

クォータニオン q1 と q2 の間を補間し、q0 は始点であり、q3 は終点。
回転するシーケンス間をスムーズに補間する場合は slerp ではなくこちらを使う。

f:id:x67x6fx74x6f:20190105032905p:plain
simd_spline

let rotations: [simd_quatf] = [
    simd_quatf(angle: 0,
               axis: simd_normalize(simd_float3(x: 0, y: 0, z: 1))),
    simd_quatf(angle: 0,
               axis: simd_normalize(simd_float3(x: 0, y: 0, z: 1))),
    simd_quatf(angle: .pi * 0.05,
               axis: simd_normalize(simd_float3(x: 0, y: 1, z: 0))),
    simd_quatf(angle: .pi * 0.8,
               axis: simd_normalize(simd_float3(x: 1, y: 0, z: -1))),
    simd_quatf(angle: .pi * 0.15,
               axis: simd_normalize(simd_float3(x: 0, y: 1, z: 0))),
    simd_quatf(angle: .pi * 0.2,
               axis: simd_normalize(simd_float3(x: -1, y: 0, z: 1))),
    simd_quatf(angle: .pi * 0.2,
               axis: simd_normalize(simd_float3(x: -1, y: 0, z: 1)))
]

for i in 1 ... rotations.count - 3 {
    for t: Float in stride(from: 0, to: 1, by: 0.001) {
        let q = simd_spline(rotations[i - 1],
                            rotations[i],
                            rotations[i + 1],
                            rotations[i + 2],
                            t)

        let node = SCNNode()
        let sphere = SCNNode(geometry: SCNSphere(radius: CGFloat(t/4)))
        sphere.geometry?.firstMaterial?.diffuse.contents = NSColor(red: 1.0, green: CGFloat(t), blue: CGFloat(t), alpha: 1.0)
        node.addChildNode(sphere)
        node.simdPosition = q.act(float3(0,0,5))
        scene.rootNode.addChildNode(node)
    }
}

 

simd_bezier

De Casteljau アルゴリズムを使用しクォータニオン q0、q3 をコントロールポイントとして扱う3次ベジェ曲線の補間を行う。
補間の終点が q0 か q3 になり、曲線は q1 または q2 を通過しない。

標準的なベジェ曲線のように凸包のプロパティが球上には保持されないので注意。

f:id:x67x6fx74x6f:20190105032932p:plain
simd_bezier

let rotations: [simd_quatf] = [
    simd_quatf(angle: 0,
                axis: simd_normalize(simd_float3(x: 0, y: 0, z: 1))),
    simd_quatf(angle: .pi * 0.05,
                axis: simd_normalize(simd_float3(x: 0, y: 1, z: 0))),
    simd_quatf(angle: .pi * 0.8,
                axis: simd_normalize(simd_float3(x: 1, y: 0, z: -1))),
    simd_quatf(angle: .pi * 0.15,
                axis: simd_normalize(simd_float3(x: 0, y: 1, z: 0)))
]

for t: Float in stride(from: 0, to: 1, by: 0.001) {
    let q = simd_bezier(rotations[0],
                        rotations[1],
                        rotations[2],
                        rotations[3],
                        t)

    let node = SCNNode()
    let sphere = SCNNode(geometry: SCNSphere(radius: CGFloat(t/4)))
    sphere.geometry?.firstMaterial?.diffuse.contents = NSColor(red: 1.0, green: CGFloat(t), blue: CGFloat(t), alpha: 1.0)
    node.addChildNode(sphere)
    node.simdPosition = q.act(float3(0,0,5))
    scene.rootNode.addChildNode(node)
}

 

まとめ

クォータニオンでの線形補間を使用した回転は行列での回転より早く処理できる可能性があるため、状況によっては積極的に使用すべきだろう。

 

simd に関しては一通り説明が終わった。
ARKit のアンカーや SceneKit のノードで多くのものを素早く変更する場合に有用な機能ではある。

また、Float、Double など、型の値の取り出しが可能であるため、ARKit や SceneKit 以外にも行列の処理で使用できる状況はあると思われる。