SceneKitでベジェ曲線から3Dモデルを作ろう

3DモデルをXcodeで作る方法は、

- プリミティブなジオメトリを組み合わせて作る方法
- カスタムジオメトリを作る方法

の大きく分けて２つだと思います。

昨日は、[SceneKitでプリミティブなジオメトリから3Dモデルを作ろう](https://qiita.com/k-boy/items/14205dd569a57eb44d96)という記事で、前者の方法を紹介しました。

今回は後者の**カスタムジオメトリを作る方法**の中でも、BezierPathを使って行う方法を紹介したいと思います。

# 目標

こんな感じのシンプルなダイヤモンドを作ってみましょう。

<img width="375" alt="IMG_299955A9F155-1.jpeg" src="https://qiita-image-store.s3.amazonaws.com/0/111366/cc1a26df-cbf9-adf3-5068-d55b4aa956f7.jpeg">

# コードでSCNNodeを作成するときの基本

まず、コードでSCNNodeを作成するときの基本は以下ような形です。

1. ジオメトリ作成
2. マテリアルを当てる
3. SCNNode作成

```swift
// ジオメトリ作成
let geometry = SCNBox(width: 0.5, height: 0.5, length: 0.01, chamferRadius: 0)

// マテリアルを当てる
geometry.firstMaterial?.diffuse.contents = UIColor.yellow

// Nodeに渡してNodeをイニシャライズ
let node = SCNNode(geometry: geometry)
```

# BezierPathでジオメトリを作る

SCNShapeというSCNGeometryを継承した型があって、以下のようにinitすることができます。

```swift
convenience init(path: UIBezierPath?, 
  extrusionDepth: CGFloat)
```
https://developer.apple.com/documentation/scenekit/scnshape/1523432-init

つまり流れとしては

```swift
// path作成
let path = UIBezierPath()

// pathを渡してジオメトリ作成
let geometry = SCNShape(path: path, extrusionDepth: 0.2)

// マテリアルを当てる
geometry.firstMaterial?.diffuse.contents = UIColor.yellow

// Nodeに渡してNodeをイニシャライズ
let node = SCNNode(geometry: geometry)
```

という感じです。

# ダイヤモンドのBezierPathを作成する

## ベジェ曲線とは

まずは、ベジェ曲線とは何か確認しときましょう。

>コンピューターで図形の曲線を描くために使われている数式。曲線を始点、終点とそれぞれの方向点の4つの点の座標をもとに描画する。自由度が高く、ほとんどすべての曲線を表現できる。アドビ システムズ社のIllustratorやコーレル社のCorelDRAWなどのドロー系ソフトで曲線の表現方法として採用されているほか、PostScriptのフォントの記述方法にも採用されている。
https://kotobank.jp/word/%E3%83%99%E3%82%B8%E3%82%A7%E6%9B%B2%E7%B7%9A-8655

ベジェ曲線を使うと、点と点から曲線を描くことができます。

iOS、Swiftでもこれを使うことができます。

## Swiftでベジェ曲線

以下のように４点を結んで、ダイヤモンドを作ります。

1. 始点にmoveする
2. addLineして線を繋いでいく
3. closeして線を閉じる

<img width="561" alt="IMG_67426123B7B4-1.jpeg" src="https://qiita-image-store.s3.amazonaws.com/0/111366/f3d29543-dfa3-885a-a1ed-3ca2a9bffe94.jpeg">

これをコードで示すと以下のようになります。

```swift
let path = UIBezierPath()
path.move(to: CGPoint(x: 1, y: 0))
path.addLine(to: CGPoint(x: 0, y: -1))
path.addLine(to: CGPoint(x: -1, y: 0))
path.addLine(to: CGPoint(x: 0, y: 1))
path.close()
```

# 作ったUIBezierPathをSCNShapeに渡す

ダイヤモンドのUIBezierPathができたのでこれをSCNShapeに渡して立体的にしていきましょう。

```swift
// path作成
let path = UIBezierPath()
path.move(to: CGPoint(x: 1, y: 0))
path.addLine(to: CGPoint(x: 0, y: -1))
path.addLine(to: CGPoint(x: -1, y: 0))
path.addLine(to: CGPoint(x: 0, y: 1))
path.close()

// pathを渡してジオメトリ作成
let geometry = SCNShape(path: path, extrusionDepth: 0.2)

// マテリアルを当てる
geometry.firstMaterial?.diffuse.contents = UIColor.yellow

// Nodeに渡してNodeをイニシャライズ
let node = SCNNode(geometry: geometry)
```

extrusionDepthは厚さです。上記コードでは0.2(m)の厚さです。

# アウトプット

デバッグして、SceneKitやARKitで空間に置くと以下のようになるはずです。

<img width="375" alt="IMG_299955A9F155-1.jpeg" src="https://qiita-image-store.s3.amazonaws.com/0/111366/cc1a26df-cbf9-adf3-5068-d55b4aa956f7.jpeg">

# 応用

UIBezierPathで作れる形は何でも立体にできます。

例えば、[iOS で星型の図形を UIBezierPath で作る
](https://qiita.com/takehito-koshimizu/items/1b4e138458c29a970bc1)を参考に星型のUIBezierPathを作ることができれば、星の立体を作成することもできます。

![star.gif](https://qiita-image-store.s3.amazonaws.com/0/111366/1b822f8f-dc08-5fd3-b397-e67432d30f4c.gif)

僕のARKitのサンプルコード集の中でもやってみましたので気になる方は参考にしてください。
https://github.com/kboy-silvergym/ARKit-Emperor

# まとめ

- geometryをNodeに渡すのがコードで3Dモデルを作る基本
- UIBezierPathはmoveしてaddLineしてcloseして作る
- SCNShapeというgeometryはUIBezierPathを渡して好きな形状を作れる