【three.js】GeometryクラスからBufferGeometryを使うよう書き換える


# はじめに

[以前書いた記事](https://qiita.com/baikichiz/items/4d8cf1a4f0f58d986152)で、Geometryクラスを使って三角柱を描画しましたが、three.js r125でGeometryクラスはコアから削除されました（[Release Notes](https://github.com/mrdoob/three.js/releases/tag/r125)）。
そこで、代替となる[BufferGeometry](https://threejs.org/docs/#api/en/core/BufferGeometry)クラスを使って同様の三角柱を描画してみます。

# Geometry -> BufferGeometry書き換え方法の概略

Geometryクラスでは、以下のようにモデルの頂点と面を定義していました。
* verticesプロパティ（配列）に頂点を表すVector3を格納する
* ポリゴンを構成する面情報をFace3クラスとして生成し、facesプロパティ（配列）に格納する


BufferGeometryクラスにおいては、モデルの頂点情報は以下のようにセットします。
1. [BufferAttribute](https://threejs.org/docs/#api/en/core/BufferAttribute)オブジェクトを生成し、頂点の座標を詰める。
1. BufferGeometryの[setAttribute](https://threejs.org/docs/#api/en/core/BufferGeometry.setAttribute)メソッドを呼び出すことで、頂点座標の詰まったBufferAttributeをセットする。

また、Face3クラスも廃止されているので、ポリゴンを構成する面情報（ここでは面を構成する頂点のインデックス）は以下のように渡します。
1. フラットな配列にインデックスを格納する
1. BufferGeometryの[setIndex](https://threejs.org/docs/#api/en/core/BufferGeometry.setIndex)メソッドを呼び出すことで、インデックス配列をセットする。

以下で具体的に書き換え前後のコードを示していきます。

# Lineに渡すGeometryの書き換え
1つめは、`createOneAxis`という関数内で生成しているLineオブジェクトに渡すGeometryです。ここでは面の情報はいらないので、頂点の渡し方だけが変更されることになります。

以前のGeometryを使用したコードは以下です：
```createOneAxis-Geometry.js
var createOneAxis = function (color, vertex) {
    var material = new THREE.LineBasicMaterial({
        color: color
    });
    var vertices = [
        new THREE.Vector3(0, 0, 0),
        vertex
    ];
    var geometry = new THREE.Geometry();
    geometry.vertices = vertices;

    var line = new THREE.Line(geometry, material);
    return line;
};
```

これを概略で示したように、BufferGeometryを使うように書き換えます：
```createOneAxis-BufferGeometry.js
    var createOneAxis = function (color, vertex) {
        var material = new THREE.LineBasicMaterial({
            color: color
        });
        
        // 頂点Vector3の配列を準備する。
        var vertices = [
            new THREE.Vector3(0, 0, 0),
            vertex
        ];
        
        var geometry = new THREE.BufferGeometry();
        
        // 配列要素数に合わせたサイズのバッファを持つBufferAttributeを作成する。
        var position = new THREE.BufferAttribute(
        	// 頂点（Vector3）1つにつきx,y,zで3つのfloat値となるのでverticesの要素数 * 3 のサイズとする。
        	new Float32Array(vertices.length * 3),
        	// x,y,zの3つの値で1つのデータの組とするので3を与える。
        	3
        );
        
        // vertices配列内のVector3が示す座標値をBufferAttributeにコピー。
        position.copyVector3sArray(vertices);
        
        // BufferGeometryにBufferAttributeをセット。
        geometry.setAttribute('position', position);

        var line = new THREE.Line(geometry, material);
        return line;
    };
```

# Meshに渡すGeometryの書き換え

次は、Meshに渡すGeometryの書き換えです。ここでは頂点に加えて、面情報（Face3クラス）の部分の書き換えも発生します。

[以前書いた記事](https://qiita.com/baikichiz/items/4d8cf1a4f0f58d986152)の`createTriangle`関数内、この部分が該当します：

```createTriangle-Geometry.js
    var vertices = [
        new THREE.Vector3(-halfLength, halfHeight, distanceToLine),   // 上面の三角形の頂点
        new THREE.Vector3(0, halfHeight, -distanceFromVertex),
        new THREE.Vector3(halfLength, halfHeight, distanceToLine),
        new THREE.Vector3(-halfLength, -halfHeight, distanceToLine),   // 下面の三角形の頂点
        new THREE.Vector3(0, -halfHeight, -distanceFromVertex),
        new THREE.Vector3(halfLength, -halfHeight, distanceToLine),
    ];
    var faces = [
        new THREE.Face3(0, 2, 1), // 上面
        new THREE.Face3(3, 4, 5), // 下面
        new THREE.Face3(0, 3, 2), // 手前側面
        new THREE.Face3(2, 3, 5),
        new THREE.Face3(0, 1, 3), // 左側面
        new THREE.Face3(1, 4, 3),
        new THREE.Face3(2, 5, 4), // 右側面
        new THREE.Face3(4, 1, 2),
    ];

    var geometry = new THREE.Geometry();
    var i = 0;
    for (i = 0; i < vertices.length; i++) {
        geometry.vertices.push(vertices[i]);
    }
    for (i = 0; i < faces.length; i++) {
        geometry.faces.push(faces[i]);
    }
```

これをBufferGeometryを使うように書き換えると、以下のようになります：

```createTriangle-BufferGeometry.js

    // 頂点 (Vector3) の配列を準備
	var vertices = [
        new THREE.Vector3(-halfLength, halfHeight, distanceToLine),   // 上面の三角形の頂点
        new THREE.Vector3(0, halfHeight, -distanceFromVertex),
        new THREE.Vector3(halfLength, halfHeight, distanceToLine),
        new THREE.Vector3(-halfLength, -halfHeight, distanceToLine),   // 下面の三角形の頂点
        new THREE.Vector3(0, -halfHeight, -distanceFromVertex),
        new THREE.Vector3(halfLength, -halfHeight, distanceToLine),
	];

    // ポリゴン面を構成する頂点のインデックス (Face3の代替となる情報)
	var faces = [
        0, 2, 1, // 上面
        3, 4, 5, // 下面
        0, 3, 2, // 手前側面
        2, 3, 5,
        0, 1, 3, // 左側面
        1, 4, 3,
        2, 5, 4, // 右側面
        4, 1, 2
	];
	
	var geometry = new THREE.BufferGeometry();

    // 頂点情報をBufferGeometryにセット
    // BufferAttributeを生成する代わりに、setFromPointsを呼ぶと内部でいいようにやってくれる
	geometry.setFromPoints(vertices);

    // ポリゴン面を構成する頂点のインデックスをセット
	geometry.setIndex(faces);
```

ここでは、頂点情報をBufferGeometryにセットする際に、手動でBufferAttributeを生成せずに済む[setFromPoints](https://threejs.org/docs/#api/en/core/BufferGeometry.setFromPoints)メソッドを使っています。

また、リファレンスでは[setIndex](https://threejs.org/docs/#api/en/core/BufferGeometry.setIndex)メソッドの引数としてBufferAttributeを渡すよう指定されていますが、[BufferGeometryのコード](https://github.com/mrdoob/three.js/blob/master/src/core/BufferGeometry.js)を見ると配列を渡しても正常に処理されるように実装されています。そのため、ここでは配列をそのまま渡しています。

以上で書き換えは完了となります。

# 書き換え後のコード全体

書き換え後のコード全体は以下のようになります：

```html
<!DOCTYPE html>
<html>
	<head>
		<meta charset=utf-8>
		<title>My first three.js app</title>
		<style>
			body { margin: 0; }
			canvas { width: 100%; height: 100% }
		</style>
	</head>
	<body>
		<script src="lib/141/three.js"></script>
		<script>
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 三角柱をつくる。原点は底面正三角形の重心、柱の高さの半分の位置とする。
// length: 底面の正三角形の辺の長さ。
// height: 三角柱の高さ
function createTriangle(length, height) {
	var faceColor = 0x00ff00;
	var halfHeight = height / 2.0;
	var halfLength = length / 2.0;
	// 正三角形の重心から辺へ下ろした垂線の長さ
	var distanceToLine = halfLength * Math.tan(Math.PI / 6.0);
	// 正三角形の頂点から対辺へ垂線を下ろしたときの、頂点と重心の間の距離
	var distanceFromVertex = halfLength / Math.tan(Math.PI / 6.0) - distanceToLine;
	
    // 頂点 (Vector3) の配列を準備
	var vertices = [
        new THREE.Vector3(-halfLength, halfHeight, distanceToLine),   // 上面の三角形の頂点
        new THREE.Vector3(0, halfHeight, -distanceFromVertex),
        new THREE.Vector3(halfLength, halfHeight, distanceToLine),
        new THREE.Vector3(-halfLength, -halfHeight, distanceToLine),   // 下面の三角形の頂点
        new THREE.Vector3(0, -halfHeight, -distanceFromVertex),
        new THREE.Vector3(halfLength, -halfHeight, distanceToLine),
	];

    // ポリゴン面を構成する頂点のインデックス (Face3の代替となる情報)
	var faces = [
        0, 2, 1, // 上面
        3, 4, 5, // 下面
        0, 3, 2, // 手前側面
        2, 3, 5,
        0, 1, 3, // 左側面
        1, 4, 3,
        2, 5, 4, // 右側面
        4, 1, 2
	];
	
	var geometry = new THREE.BufferGeometry();

    // 頂点情報をBufferGeometryにセット
    // BufferAttributeを生成する代わりに、setFromPointsを呼ぶと内部でいいようにやってくれる
	geometry.setFromPoints(vertices);

    // ポリゴン面を構成する頂点のインデックスをセット
	geometry.setIndex(faces);
	
	var material = new THREE.MeshBasicMaterial({ color: faceColor });
	// 三角柱のワイヤーフレームを描く
	var wireframeGeometry = new THREE.EdgesGeometry(geometry);
	var wireframeMaterial = new THREE.LineBasicMaterial({ color: 0x000000, linewidth: 2 });
	
	var triangleMesh = new THREE.Mesh(geometry, material);
	var wireframe = new THREE.LineSegments(wireframeGeometry, wireframeMaterial);
	
	triangleMesh.add(wireframe);
	return triangleMesh;
}

// x, y, z軸を赤、緑、青で描く
// length: 軸の長さ
function createAxes(length)
{
    var createOneAxis = function (color, vertex) {
        var material = new THREE.LineBasicMaterial({
            color: color
        });
        
        // 頂点Vector3の配列を準備する。
        var vertices = [
            new THREE.Vector3(0, 0, 0),
            vertex
        ];
        
        var geometry = new THREE.BufferGeometry();
        
        // 配列要素数に合わせたサイズのバッファを持つBufferAttributeを作成する。
        var position = new THREE.BufferAttribute(
        	// 頂点（Vector3）1つにつきx,y,zで3つのFloat32値となるのでverticesの要素数 * 3 のサイズとする。
        	new Float32Array(vertices.length * 3),
        	// x,y,zの3つの値で1つのデータの組とするので3を与える。
        	3
        );
        
        // vertices配列内のVector3が示す座標値をBufferAttributeにコピー。
        position.copyVector3sArray(vertices);
        
        // BufferGeometryにBufferAttributeをセット。
        geometry.setAttribute('position', position);

        var line = new THREE.Line(geometry, material);
        return line;
    };


    return [
        createOneAxis(0x770000, new THREE.Vector3(length, 0, 0)),
        createOneAxis(0x007700, new THREE.Vector3(0, length, 0)),
        createOneAxis(0x000077, new THREE.Vector3(0, 0, length))
    ];

}

var mesh = createTriangle(10, 20);
scene.add(mesh);
var axes = createAxes(20);
axes.forEach(function (a) { scene.add(a);})

camera.position.z = 30;
camera.position.y = 30;
camera.position.x = 15;
camera.lookAt(0, 0, 0);

function animate() {
    requestAnimationFrame(animate);
    //mesh.rotation.x += 0.01;
    mesh.rotation.y += 0.01;
    renderer.render(scene, camera);
}
animate();
		</script>
	</body>
</html>

```