バイナリフォーマットstlファイルをC++で読み込む

# はじめに
　流体解析みたいな数値シミュレーションコードを書いていると、境界条件としてstlファイル等のCADデータを使いたくなります。でも「C++ stlファイル 読み込み」とか検索しても、Standard Template Libraryを使ったファイルの読み込み方の記事ばかり出てくるんですよね...
　後輩に聞いたら「stlファイルのバイナリ形式は構造が単純なので意外と簡単に読み込める」と言っていたので、試しにやってみたら本当にすんなりできました。

# 概要
　本記事では**バイナリフォーマットのstlファイルをC++で読み込みます**。アスキーフォーマットをご使用の場合はバイナリに変換する必要があります。バイナリstlファイルの構造は

・[HIRAMINE.com](https://www.hiramine.com/programming/3dmodelfileformat/stlfileformat.html)
・[Wikipedia](https://ja.wikipedia.org/wiki/Standard_Triangulated_Language)

を参考にさせていただきました。

# バイナリフォーマットstlファイルの構造

　バイナリフォーマットのstlファイルでは、まず任意の文字列（char[]型 80バイト）があって、次にポリゴン数（unsigned int型 4バイト）、そこから各ポリゴンのデータが続きます。各ポリゴンのデータは法線ベクトル（float型 4バイト×3成分）、頂点座標3つ（float型 4バイト×3成分×3点）の順で保存されています。

| バイト数 | データ型     | 内容                             |
| -------- | ------------ | -------------------------------- |
| 80       | char[]       | ヘッダー（任意の文字列）         |
| 4        | unsigned int | ポリゴン数                       |
| 4        | float        | ポリゴン1つ目の法線ベクトルx方向 |
| 4        | float        | ポリゴン1つ目の法線ベクトルy方向 |
| 4        | float        | ポリゴン1つ目の法線ベクトルz方向 |
| 4        | float        | ポリゴン1つ目の頂点１つ目のx座標 |
| 4        | float        | ポリゴン1つ目の頂点１つ目のy座標 |
| 4        | float        | ポリゴン1つ目の頂点１つ目のz座標 |
| 4        | float        | ポリゴン1つ目の頂点2つ目のx座標  |
| 4        | float        | ポリゴン1つ目の頂点2つ目のy座標  |
| 4        | float        | ポリゴン1つ目の頂点2つ目のz座標  |
| 4        | float        | ポリゴン1つ目の頂点3つ目のx座標  |
| 4        | float        | ポリゴン1つ目の頂点3つ目のy座標  |
| 4        | float        | ポリゴン1つ目の頂点3つ目のz座標  |
| 2        | --           | 未使用データ                     |
| 4        | float        | ポリゴン2つ目の法線ベクトルx方向 |
| ...      | ...          | ...                              |

# ソースコード全容
　先にソースコードの全容を示します。コードを簡単にするために、座標構造体の`coord`とポリゴン構造体の`polygon`を定義しました。

```c++
#include <iostream>
#include <fstream>
#include <vector>
using namespace std;

// 座標
struct coord{
    float x,y,z;
};

// ポリゴン
struct polygon{
    // 頂点座標
	coord v0,v1,v2;    
    // 法線ベクトル
	coord normal;
    // セット
	void set(coord v0_, coord v1_, coord v2_, coord normal_){
		v0 = v0_ ;
		v1 = v1_ ;
		v2 = v2_ ;
		normal = normal_;
	}    
};

int main(){

	// ファイルオープン
	ifstream fin( "StanfordBunny.stl", ios::in | ios::binary );
	if(!fin){
		cout << "file is not found." << endl;
		return 0;
	}

	// ポリゴン数の読み込み
	unsigned int num_polygons;
	fin.seekg(80,ios_base::beg);
	fin.read((char*) &num_polygons, sizeof(unsigned int) );

	// 各ポリゴンの法線と座標の読み込み
	vector<polygon> poly(num_polygons);
	for(int i=0; i<num_polygons; i++){
		coord vbuff[3];
		coord nbuff;
		fin.read((char*) &nbuff.x, sizeof(float));
		fin.read((char*) &nbuff.y, sizeof(float));
		fin.read((char*) &nbuff.z, sizeof(float));
		for(int j=0; j<3; j++){
			fin.read((char*) &vbuff[j].x, sizeof(float));
			fin.read((char*) &vbuff[j].y, sizeof(float));
			fin.read((char*) &vbuff[j].z, sizeof(float));
		}
		poly[i].set(vbuff[0],vbuff[1],vbuff[2],nbuff);
		fin.seekg(2,ios_base::cur);
	}
    
    // ファイルのクローズ
	fin.close();
	
}

```

# 解説

　各部分について簡単に解説します。

## ファイルオープン

　ifstreamを使ってstlファイルをバイナリで読み込みます。

```c++
// ファイルオープン
ifstream fin( "StanfordBunny.stl", ios::in | ios::binary );
if(!fin){
    cout << "file is not found." << endl;
    return 0;
}
```

## ポリゴン数の読み込み

　ポリゴン数はファイルの開始位置から80バイト（ヘッダー分）後方に書かれているので、`fin`の読み込み位置を80バイトシークさせます。読み込みは通常のバイナリファイルの場合と同様です。

```c++
// ポリゴン数の読み込み
unsigned int num_polygons;
fin.seekg(80,ios_base::beg);
fin.read((char*) &num_polygons, sizeof(unsigned int) );
```

## 各ポリゴンの法線と座標の読み込み

　あらかじめ定義しておいた`polygon`構造体の`vector`をポリゴン数だけ確保し、順々にデータを格納していきます。読み込みに際して、データを一時的に保存するバッファーを`coord`構造体で定義しておきます。ポリゴン数読み込みが完了した段階で`fin`は1つ目のポリゴンのデータ開始位置を見ているので、そのまま読み込みを始めます。法線と頂点3つを読み込み終わったら次のポリゴンの読み込みに進みますが、その前に未使用データの2バイト分`fin`の位置を進めておきます。

```c++
// 各ポリゴンの法線と座標の読み込み
vector<polygon> poly(num_polygons);
for(int i=0; i<num_polygons; i++){
    coord vbuff[3];
    coord nbuff;
    fin.read((char*) &nbuff.x, sizeof(float));
    fin.read((char*) &nbuff.y, sizeof(float));
    fin.read((char*) &nbuff.z, sizeof(float));
    for(int j=0; j<3; j++){
        fin.read((char*) &vbuff[j].x, sizeof(float));
        fin.read((char*) &vbuff[j].y, sizeof(float));
        fin.read((char*) &vbuff[j].z, sizeof(float));
    }
    poly[i].set(vbuff[0],vbuff[1],vbuff[2],nbuff);
    fin.seekg(2,ios_base::cur);
}
```

# まとめ

　C++でバイナリフォーマットのsrlファイルを読み込む方法を紹介しました。ファイルのバイナリ入出力を使ったことがある方であればそんなに難しくないと思います。ご参考になれば幸いです！