ジオメトリを変形させる

## 概要

Grimoire.jsをつかってジオメトリを作って変形させる話。

[Grimoire.js](https://grimoire.gl)についての基本的なことは他の記事を参照ください。

シェーダーに関する説明はあまりしません。

できたもの↓

https://pnlybubbles-gomibako.github.io/grimoirejs-advent-calendar-14/

## 新しいジオメトリを作る

今回は板(ita)ジオメトリを作ります。変形させるためにポリゴンの分割数(div)を指定できるようにします。

### ジオメトリの登録

ジオメトリをタグから使えるようにするためには`GeometryFactory.addType()`を用います。

```javascript
GeometryFactory.addType("ita", {
  div: {
    converter: 'Vector3',
    defaultValue: '2,2,2',
  },
}, (gl, attrs) => {
  const geo = new Geometry;
  //
  // ジオメトリの実装
  //
  return geo;
});
```

これでGomlファイルから下のようにタグでジオメトリを利用できるよになります。

```xml
<geometry type="ita" name="myIta" div="10,1,1" />
```

第2引数のオブジェクトはGOMLから指定可能な属性を定義しています。今回は軸方向での分割数を指定可能にしています。

```javascript
{
  div: {
    converter: 'Vector3',
    defaultValue: '2,2,2',
  },
}
```

ここで定義した属性はジオメトリの実装の部分でアクセスできます。タグで指定された文字列は`converter`を通り、今回の場合は`Vector3`オブジェクトとして得られます。

```javascript
(gl, attrs) => {
  console.log(attrs.div) // Vector3(2,2,2)
}
```

### ジオメトリの作成

先程のジオメトリの実装部分で、`Geometry`のインスタンスに情報を追加していきます。

今回は具体的な実装(`Ita`)に関しては割愛します。

```javascript
const ita = new Ita(attr.div);
const geometry = new Geometry(gl);
geometry.addAttributes(ita.vertices, primitiveLayout);
geometry.addIndex('default', ita.indices);
geometry.addIndex('wireframe', ita.wireframeIndices, WebGLRenderingContext.LINES);
```

`geometry.addAttributes(vertices, layout);`で頂点の情報を追加します。verticiesでは頂点の座標、法線、テクスチャ座標、などが1つの配列にはいっています。layoutではその配列の構成を定義します。インターリーブ配列の場合は次のようになります。[インターリーブ配列に関してはこちらが参考になります](https://wgld.org/d/webgl/w088.html)

```javascript
// 今回の場合verticesは以下のような配列なので
// vertices = [Px1, Py1, Pz1, Nx1, Ny1, Ny1, Tx1, Ty1, Px2, Py2, Pz2, Nx2, Ny2, Ny2, Tx2, Ty2...]
const primitiveLayout = {
  POSITION: {
    size: 3, // x, y, z の3要素で1かたまり
    stride: 32, // 次の要素のPOSITIONまで32byte
  },
  NORMAL: {
    size: 3, // x, y, z の3要素で1かたまり
    stride: 32, // 次の要素のNORMALまで32byte
    offset: 12, 
  },
  TEXCOORD: {
    size: 2, // x, y　の2要素で1かたまり
    stride: 32, // 次の要素のTEXCOORDまで32byte
    offset: 24,
  }
};
```

`geometry.addIndex(name, layout, topology);`でインデックスを追加します。nameに`default`を指定すると初期で使われるインデックスになります。`topology`を省略すると`WebGLRenderingContext.TRIANGLES`が使われます。[参考](http://miffysora.wikidot.com/primitive)

またnameで指定した`default`以外の名前はmeshタグでの`targetBuffer`で下のように指定可能になります。

```xml
<mesh geometry="ita" targetBuffer="wireframe" div="2,2,2"/>
```

<img width="404" alt="スクリーンショット 2017-01-08 18.20.44.png" src="https://qiita-image-store.s3.amazonaws.com/0/14939/08d1ff7d-f43d-8baa-50ec-ce465128c070.png">

## ジオメトリを変形させる

今回はハードウェアで変形を行いたいのでvertex shaderを用いて頂点座標をいじります。

Grimoire.jsでシェーダーを書くときは`.sort`ファイルを用います。

`#ifdef VS`の中がvertex shaderとなり、fragment shaderの時と同様に@{default:""}といった記法でタグにuniform変数を露出させることが可能です。

```glsl
@Pass {
  @DepthFunc(LEQUAL)
  @BlendFunc(SRC_ALPHA, ONE_MINUS_SRC_ALPHA)
  @CullFace(BACK)

  FS_PREC(mediump,float)

  varying vec2 vTexCoord;
  varying vec3 vNormal;
  varying vec3 vPosition;

  #ifdef VS
    attribute vec3 position;
    attribute vec3 normal;
    attribute vec2 texCoord;
    uniform mat4 _matPVM;
    uniform mat4 _matM;
    @{default:"-1"}
    uniform float radius; // 適当なパラメータをタグに露出させる
    @{default:"0.1"}
    uniform float coef; // 適当なパラメータをタグに露出させる
    void main(){
      float phi = position.x / radius;
      // 中心から離れるほどy座標を持ち上げる
      vec3 tposition = vec3(radius * sin(phi), radius * (1. - cos(phi)) / coef + position.y, position.z);
      gl_Position = _matPVM * vec4(tposition, 1.);
      vTexCoord = texCoord;
      vNormal = normalize((_matM * vec4(normal, 0.)).xyz);
      vec4 p = _matM * vec4(tposition, 1.);
      vPosition = p.xyz / p.w;
    }
  #endif

  #ifdef FS
    // フラグメントシェーダー
  #endif
}
```

```xml
<import-material typeName="mageru" src="./mageru.sort"/>
<material type="mageru" radius="2" coef="0.13" texture="tex.jpg"/>
```

作ったマテリアルをmeshで指定することで、

```xml
<mesh class="ita" geometry="ita" material="#mageru" scale="6,1,6" />
```

まがる

<img width="452" alt="スクリーンショット 2017-01-08 18.14.25.png" src="https://qiita-image-store.s3.amazonaws.com/0/14939/9d97d433-b1a3-9dd8-7ede-8dbeb08ec472.png">

javascriptを使ってmeshの属性を毎フレーム書き換えることでアニメーションさせることもできますね。

いちおう下のリンクでいじれます。

https://pnlybubbles-gomibako.github.io/grimoirejs-advent-calendar-14/

ソースコードはこちら

https://github.com/pnlybubbles-gomibako/grimoirejs-advent-calendar-14

## 🍣？

<img width="422" alt="スクリーンショット 2017-01-08 18.36.46.png" src="https://qiita-image-store.s3.amazonaws.com/0/14939/f6a5ed5d-319b-8647-deda-77ba95223f31.png">