最近3Dスキャンとかで3Dモデル吐き出すとかありますよね。まぁあるんです。あったんです。
でこれらのモデルの体積がほしいなーと思っても、複雑な形状のmeshの体積を求めるのは地味に難しいです。ぱっとアルゴリズム思いつく人はきっと数学に慣れ親しんでいる人くらいでは。
そんなわけで地味に面倒なので Surveying3Dというライブラリ (C#/.NET Standard2.0) /CLIアプリ(.NET Core Global Tools) を作りました。3次元測量みたいなニュアンス。 今はwavefrontのobjフォーマットに対応しています。そのうちglTFにも対応したいと思ってます。 使い方とかはREADMEに書いてあります。ここではどういうアルゴリズムが使われているかを紹介します。
どうやるの
複雑なメッシュのすべての三角ポリを分割して考え、各3角系の3点と原点を結ぶ三角錐を考えます。この3角錐体積を求めるのは小学生でもできそうです。 しかし、原点から3角ポリの各点までの直線間になにも交叉するmeshがなければいいですが(原点中心に球とかcubeがある場合は交差しない), 単純なオブジェクトでも中心が原点から外れていたり, 複雑なmeshでは大概原点から3点までの距離に他のポリゴンとがぶるだろうと思います。 ではどうするかというと、原点から3角ポリゴンまでの"符号付"の体積を求めて全部加算します。
具体的に
wavefrontのobjファイルは面を表から見た時に頂点を半時計に回るように定義しています。この半時計周りであることを利用します。各頂点を p = [x, y, z] みたいな列ベクトルを考えます。で各頂点の列ベクトルで[p1, p2, p3]という3x3の行列作ります。 三角錐の体積を計算するためにこの3x3の行列式を計算し6で割ります。で行列式で体積を求めるとよいことがあって、符号付の体積が得られます。右手座標系で考えたとき、面が原点を向いていたら負、逆であれば正の体積の体積が得られます。 なのでモデルが中心からずれていようが、複雑でも符号付のおかげでいい感じに体積が求められます。
注意事項
GitHubのほうにも書いたんですが、この方法で体積計算するには
- オブジェクトの表面が閉じていること。
- 面の定義を3点で行うこと
の2つがあります。表面が閉じていないと当然ながら体積とは、みたいな感じになるので閉じてないといけません。 自分が使った3Dスキャナでは表面が完全には閉じていなかった(床と接地している部分が開いていた)のでスキャンしたものは、一度と面が閉じているか確認したほうがいいかもしれません。 あとwavefrontのobjは面の定義を3点以上で出来てしまうので(Blenderのデフォルトのcubeとか吐き出すと4点で面張ってます)、3点で面貼るようにモデルをいじってから使いましょう(四角面はあまりないだろうという侮り)。Blenderではctrl+tで四角面を三角面にスッとできます。