【Swift】Vision OS で自作のオブジェクトを移動させてみる
初めに
今回は以下の記事の続きとして、自作オブジェクトを配置するだけでなく、ユーザーの入力に応じた変更をしてみたいと思います。
記事の対象者
- Swift, SwiftUI 学習者
- Vision OS に触れてみたい方
完成イメージ
完成イメージとしては以下のように、アニメーションを含む飛行機のオブジェクトを配置し、飛行機の進む方向を変更したり、飛行機のオブジェクトの大きさを大きくするなどの変更を行います。
実装
Blenderからのモデルの取り込み
今回は以下のようにアニメーションを含むモデルを Blender からエクスポートして使ってみたいと思います。Blender からのエクスポートについてはこちらの記事をご覧ください。
なお、以下のリンクに今回使用する plane オブジェクトを USDZ形式で保存しておきました。リンクを押すと Dropbox が開きます。プレビューはできませんが、ダウンロードして使用できるかと思います。
ダウンロードした USDZ形式のファイルは Xcode のプロジェクトにドラッグ&ドロップすればそのまま使用できます。
App の設定
App のコードの全文は以下のようになります。
import SwiftUI
@main
struct SampleApp: App {
var body: some Scene {
WindowGroup {
PlaneView()
}
.windowStyle(.volumetric)
.defaultSize(width: 2.5, height: 1, depth: 2.5, in: .meters)
}
}
今回実装する画面は飛行機を表示させる PlaneView()
のみなので、他の画面の実装がなくシンプルです。
なお、今回表示させる飛行機のオブジェクトはアニメーションを含むため、defaultSize
としてウィンドウのサイズを指定しています。こうすることで、オブジェクトが見切れることがなくなるかと思います。
PlaneView の作成
次に PlaneView
の実装を行います。
最終的なコードの全文は以下のようになります。
import SwiftUI
import RealityKit
import RealityKitContent
struct PlaneView: View {
@State var enlarge = false
@State var rotate = false
let attachmentID = "attachmentID"
var body: some View {
VStack {
RealityView { content, attachments in
if let scene = try? await Entity(named: "PlaneScene", in: realityKitContentBundle) {
let animation = scene.availableAnimations[0]
scene.playAnimation(animation.repeat())
content.add(scene)
}
if let sceneAttachment = attachments.entity(for: attachmentID) {
sceneAttachment.position = SIMD3<Float>(0, -0.2, 0.9)
sceneAttachment.transform.rotation = simd_quatf(angle: -0.5, axis: SIMD3<Float>(1,0,0))
content.add(sceneAttachment)
}
} update: { content, attachments in
if let scene = content.entities.first {
let ix:Float = 0.0
let iy:Float = rotate ? 0.707107 : 0.0
let iz:Float = 0.0
let r:Float = rotate ? 0.707107 : 0.0
let uniformScale: Float = enlarge ? 1.5 : 1.0
let uniformRotate = simd_quatf(ix: ix, iy: iy, iz: iz, r: r)
scene.transform.scale = [uniformScale, uniformScale, uniformScale]
scene.transform.rotation = uniformRotate
}
} placeholder: {
ProgressView()
.progressViewStyle(.circular)
.controlSize(.large)
} attachments: {
Attachment(id: attachmentID) {
VStack {
Toggle("Enlarge RealityView Content", isOn: $enlarge)
.toggleStyle(.button)
Toggle("Rotate RealityView Content", isOn: $rotate)
.toggleStyle(.button)
}
.padding()
.glassBackgroundEffect()
}
}
}
}
}
#Preview {
PlaneView()
}
上記のコードをより詳しくみるために、まずは以下のコードから実装してみます。
import SwiftUI
import RealityKit
import RealityKitContent
struct PlaneView: View {
@State var enlarge = false
@State var rotate = false
var body: some View {
VStack {
RealityView { content in
if let scene = try? await Entity(named: "PlaneScene", in: realityKitContentBundle) {
let animation = scene.availableAnimations[0]
scene.playAnimation(animation.repeat())
content.add(scene)
}
}
}
}
}
#Preview {
PlaneView()
}
上記のコードを実行するとこちらのように飛行機が奥側から手前側に飛んでくることがわかります。
処理の内容としては、まず RealityView
を定義し、content
にモデルを追加していく処理になります。なお、PlaneScene
は先ほどの飛行機のモデルをダウンロードして、Xcodeのプロジェクト内に配置した後、適切な場所に移動させたモデルになります。この辺りは多少微調整が必要になるかと思います。
以下の部分では PlaneScene
に登録されているアニメーションを取り出して、それをリピートする形で付与しています。
let animation = scene.availableAnimations[0]
scene.playAnimation(animation.repeat())
余談ですが、自分が試したところ、Blender で登録されたアニメーションに関しては Blender > Reality Converter > Xcode の流れを経ても消えないことがわかりました。
Blender などの3Dモデリングツールでアニメーションまで実装して、それをSwiftUIでループさせるか、モデルのみをモデリングツールで実装して、SwiftUIでアニメーションを実装するか、どちらのツールが使いやすいかは個人の好みで分かれるかと思います。
この辺りのメリット、デメリットは調べてみたいところではあります。
次に以下のように、attachments
を追加してみましょう。
import SwiftUI
import RealityKit
import RealityKitContent
struct PlaneView: View {
+ @State var enlarge = false
+ @State var rotate = false
+ let attachmentID = "attachmentID"
var body: some View {
VStack {
RealityView { content, attachments in
if let scene = try? await Entity(named: "PlaneScene", in: realityKitContentBundle) {
let animation = scene.availableAnimations[0]
scene.playAnimation(animation.repeat())
content.add(scene)
}
+ if let sceneAttachment = attachments.entity(for: attachmentID) {
+ sceneAttachment.position = SIMD3<Float>(0, -0.2, 0.9)
+ sceneAttachment.transform.rotation = simd_quatf(angle: -0.5, axis: SIMD3<Float>(1,0,0))
+ content.add(sceneAttachment)
+ }
+ } attachments: {
+ Attachment(id: attachmentID) {
+ VStack {
+ Toggle("Enlarge RealityView Content", isOn: $enlarge)
+ .toggleStyle(.button)
+ Toggle("Rotate RealityView Content", isOn: $rotate)
+ .toggleStyle(.button)
+ }
+ .padding()
+ .glassBackgroundEffect()
+ }
+ }
}
}
}
#Preview {
PlaneView()
}
上記のコードを実行するとこちらのようになります。
画面の手前側に二つの Toggle ボタンが表示されるようになりましたが、まだタップしても変化はありません。
以下のコードではそれぞれ、飛行機のモデルを大きくするかどうか、回転させるかどうかの状態を保持している変数と、attachment を実装する際に必要になるIDを保持する変数を定義しています。
@State var enlarge = false
@State var rotate = false
let attachmentID = "attachmentID"
以下のコードでは attachment の位置の調整、追加を行なっています。
if let sceneAttachment = attachments.entity(for: attachmentID) {
sceneAttachment.position = SIMD3<Float>(0, -0.2, 0.9)
sceneAttachment.transform.rotation = simd_quatf(angle: -0.5, axis: SIMD3<Float>(1,0,0))
content.add(sceneAttachment)
}
以下の部分では attachment の位置を調整しています。
この場合の位置は attachment が付属するオブジェクト、つまり今回の場合だと飛行機のオブジェクトの中心点に対する相対的な位置で決められます。
以下の場合では飛行機のオブジェクトの中心から下側に -0.2, 手前側に 0.9 としています。
sceneAttachment.position = SIMD3<Float>(0, -0.2, 0.9)
以下の部分では attachment を回転させています。
回転させる大きさは -0.5 であり、回転の軸は x軸となっています。
x軸はユーザーから見て左右に走っている軸であり、このように設定することで、ユーザーの高い目線から attachment が見やすくなっています。
sceneAttachment.transform.rotation = simd_quatf(angle: -0.5, axis: SIMD3<Float>(1,0,0))
attachment を横から見ると以下の画像のように少し傾いていることがわかります。このようにすることで上から見た時により見やすくなります。
以下のコードでは具体的な attachment の実装を行なっています。
先ほどの if let sceneAttachment = attachments.entity(for: attachmentID)
で指定したIDと同様のIDを指定することで、attachments の位置などを適用させることができます。
attachments: {
Attachment(id: attachmentID) {
VStack {
Toggle("Enlarge RealityView Content", isOn: $enlarge)
.toggleStyle(.button)
Toggle("Rotate RealityView Content", isOn: $rotate)
.toggleStyle(.button)
}
.padding()
.glassBackgroundEffect()
}
}
最後に以下の部分を追加していきます。
import SwiftUI
import RealityKit
import RealityKitContent
struct PlaneView: View {
@State var enlarge = false
@State var rotate = false
let attachmentID = "attachmentID"
var body: some View {
VStack {
RealityView { content, attachments in
if let scene = try? await Entity(named: "PlaneScene", in: realityKitContentBundle) {
let animation = scene.availableAnimations[0]
scene.playAnimation(animation.repeat())
content.add(scene)
}
if let sceneAttachment = attachments.entity(for: attachmentID) {
sceneAttachment.position = SIMD3<Float>(0, -0.2, 0.9)
sceneAttachment.transform.rotation = simd_quatf(angle: -0.5, axis: SIMD3<Float>(1,0,0))
content.add(sceneAttachment)
}
} update: { content, attachments in
+ if let scene = content.entities.first {
+ let ix:Float = 0.0
+ let iy:Float = rotate ? 0.707107 : 0.0
+ let iz:Float = 0.0
+ let r:Float = rotate ? 0.707107 : 0.0
+ let uniformScale: Float = enlarge ? 1.5 : 1.0
+ let uniformRotate = simd_quatf(ix: ix, iy: iy, iz: iz, r: r)
+ scene.transform.scale = [uniformScale, uniformScale, uniformScale]
+ scene.transform.rotation = uniformRotate
+ }
+ } placeholder: {
+ ProgressView()
+ .progressViewStyle(.circular)
+ .controlSize(.large)
} attachments: {
Attachment(id: attachmentID) {
VStack {
Toggle("Enlarge RealityView Content", isOn: $enlarge)
.toggleStyle(.button)
Toggle("Rotate RealityView Content", isOn: $rotate)
.toggleStyle(.button)
}
.padding()
.glassBackgroundEffect()
}
}
}
}
}
#Preview {
PlaneView()
}
以下の部分ではオブジェクトの大きさと回転の変更を行なっています。
大きさに関しては enlarge
の三項演算子を用いて、大きくするかどうかを切り替えています。
回転に関しては simd_quatf
を使って四次元数で表現しています。この辺りの値は別途計算する必要があり複雑だったので、今回は省かせていただきます。
enlarge
同様に rotate
の三項演算子を用いて、回転をするかどうかを切り替えています。
if let scene = content.entities.first {
let ix:Float = 0.0
let iy:Float = rotate ? 0.707107 : 0.0
let iz:Float = 0.0
let r:Float = rotate ? 0.707107 : 0.0
let uniformScale: Float = enlarge ? 1.5 : 1.0
let uniformRotate = simd_quatf(ix: ix, iy: iy, iz: iz, r: r)
scene.transform.scale = [uniformScale, uniformScale, uniformScale]
scene.transform.rotation = uniformRotate
}
以下の部分では placeholder
として、モデルを読み込んでいる際に表示させるビューを指定しています。今回は ProgressView
を表示させています。
placeholder: {
ProgressView()
.progressViewStyle(.circular)
.controlSize(.large)
}
以上のコードで完成イメージと同じようなビューが作成でき、飛行機のモデルの大きさ、回転方向も変更できるようになっているかと思います。
まとめ
最後まで読んでいただいてありがとうございました。
オブジェクトを配置するだけでなく、大きさや回転、位置なども変更できると VisionOS で表現できることの幅が格段に広がるかと思います。
どの機能を実装する場合もオブジェクトの位置や大きさ、回転などを実装することはあると思うので、早く使用方法に慣れていきたいと思います。
Discussion