three.jsで表示するJSONファイルを手書きするときの最低限の要素

# はじめに
[Spectacles](https://github.com/tt-acm/Spectacles.WebViewer/ "Spectacles Viewer")（[viewer](http://tt-acm.github.io/Spectacles.WebViewer/)）という、Webブラウザ上で簡単に3Dデータを表示できるthree.jsをもとにしたJavaScriptのライブラリがあります。「BIM」とか「AEC」とか書いてあるので建築業界向けのやつですね。
これを使うにあたっては基本的には上記URL内で誘導されているRevit、GrasshopperのExporterを使えば良いのですが、中身そもそもどうなってんの？ということでJSONファイルを自分で書いたときのメモです。（手書きといっても出力にはコードを書きました）
3D全然分からない者が最低限何かの物体が表示できれば…というものなのでガラスの透明感の表現などの領域には及んでいません、すみません。

# JSONファイルの構成
GitHubのthree.jsのwikiで[JSONのフォーマット](https://github.com/mrdoob/three.js/wiki/JSON-Object-Scene-format-4 "JSONのフォーマット")が公開されています。（「Work in progress.」と書いてあるので途中っぽいですが）
3D初心者のためこれだけだと何が何やら分からなかったので中身の把握をやっていきます。
大まかに「geometries」「materials」「object」の三つで構成されています。

## geometries
物体一つ一つの座標と形状の定義です。
「geometries」項目の中に配列の形で個々の図形が記述されます。
`{"geometries":[{図形の情報},{図形の情報},{図形の情報}]}`
図形の情報は以下の形で書いていきます。

```JSON:
"geometries": [
  {
    "uuid":"一意のID",
    "type":"Geometry",
    "data": {
      "vertices":[X0,Y0,Z0,X1,Y1,Z1,...,Xn,Yn,Zn],
      "normals": [],
      "uvs": [],
      "faces": [0,0,1,2,0,1,2,3,...,0,0,4,5],
      "scale":1.0,
      "visible":true,
      "castShadow":true,
      "receiveShadow":false,
      "doubleSided":true
    }
  },
  {図形の情報}, ・・・ ,{図形の情報}
]
```

重要な情報は「uuid」「vertices」「faces」の三つです。
これ以外の項目は上の通りで構いません。
・uuid
　一意のUUIDです。ここで宣言したUUIDを使って「object」内でこの図形を呼び出し、図形を表示させます。
・vertices
　```[0点目のX,0点目のY,0点目のZ,1点目のX,1点目のY,1点目のZ,...,n点目のX,n点目のY,n点目のZ]```
　のように3つごとの塊で認識される実数値の配列です。
　負の値も大丈夫です。
　Y座標が高さ、Z座標が奥行きとなっています。
・faces
　```[0,三角形の頂点1,三角形の頂点2,三角形の頂点3,0,三角形の頂点1,三角形の頂点2,三角形の頂点3,...,0,三角形の頂点1,三角形の頂点2,三角形の頂点3]```
　のように4つごとの塊で認識される三角形の頂点の配列です。（最初これが分かりませんでした）
　それぞれの三角形の頂点の番号は「vertices」で定義した頂点のインデックスになっています。
　直方体を描いた場合は実際こういう感じで三角形ごとに切れているということです。3D表現を知っている人には常識だそうです。
<img width=30% alt="直方体" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/343311/4f6a286b-c957-248d-681c-d21a66356476.png">


## materials
各物体に割り当てる色や模様、質感の定義です。今回は表示だけできればいいので色についてだけ扱います。
「materials」項目の中に配列の形で個々のマテリアル情報が記述されます。
`{"materials":[{マテリアル},{マテリアル},{マテリアル}]}`
マテリアル情報は以下の形で書いていきます。

```JSON:
"materials": [
  {
    "uuid":"一意のUUID",
    "type":"MeshLambertMaterial",
    "color":9895680,
    "ambient":9895680,
    "emissive":0,
    "opacity":1.0,
    "transparent":false,
    "wireframe":false,
    "shading":1
  },
  {マテリアル},・・・,{マテリアル}
]
```
ここでは「uuid」「color」「ambient」の三つを変更します。他の値は上の通りで構いません。
・ uuid
　一意のUUIDです。ここで宣言したUUIDを使って「object」内で図形とこのマテリアルを関連付けます。
・color
　OLE値で表現された色数値です。OLE値はRGB値を変換したもので、変換式は[こちら](http://www.pneity.org/rgb-ole.html)で提示されており、以下の形になっています。
`OLE = red + (green * 256) + (blue * 256 * 256)`
`RGB = "red % 255" + "(green / 256) % 256" + "(blue / 256 / 256) % 256"`
　上の例の「9895680」だとRGBでは「00ff96」で、明るめの緑ということになります。
・ambient
　環境光の色です。
　colorと同じ値にしましたが、別の色でもいいとは思います。個人的には同じ色にしておいた方が若干見やすいです。


## object
物体一つ一つをマテリアルと関連付けて表示するための定義です。
ここは他の二つと少し違い、`{"object":{色々設定, "children":[{描画したい図形}, ・・・,{描画したい図形}]}}`という形になっています。
### 設定部分
設定部分は以下の値を設定します。ここでは「uuid」「name」「layers」を変更します。
「uuid」はこれまでの通り一意のUUIDです。今回は他から参照してませんが念のため書いてます。消しても動作はします。
「name」はただの名前なので特に気を使う必要はありません。消しても動作はします。
「layers」ではレイヤとして使いたい名前のリストを「,」でそのまま繋げて記述します。「"A","B","C"」ではなく「"A,B,C"」となることに注意です。

```JSON:
"object":{
  "uuid":"一意のUUID",
  "name":"sample name",
  "type":"Scene",
  "matrix":[1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0],
  "userData":{"layers":"A,B,C"},
  "children":[・・・]
}
```

### children
ここで表示したい図形とマテリアルを関連付け、一つの物体にしていきます。

```JSON:
"children": [
  {
    "uuid":"一意のUUID",
    "name":" ",
    "type":"RevitElement",
    "matrix":[1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0],
    "children":[
      {
      "uuid":"描画したい図形のUUID",
      "name":" ",
      "type":"Mesh",
      "matrix":[1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0],
      "geometry":"描画したい図形のUUID",
      "material":"設定したいマテリアルのIIUD"
      }
    ],
    "userData":{
      "sample_1":"40.0",
      "sample_2":"50.0",
      "sample_3":"0.9",
      "layer":"A"
    }
  },{},・・・,{}
]
```
「children」の中にも「children」があったりして複雑です。ここでは自分自身の「uuid」を設定するほか、これまでに定義してきた「uuid」を使って物体の図形とマテリアルを関連付けています。
「type」で「RevitElement」と書いてるのは嘘ですね、手書きなので…。「type」を書かなくても動作はしましたが念のため残しています。
「userData」についてはSpectaclesの画面で各部品についての情報を確認するための設定です。各部品をクリック（黄色のハイライト）するとこんな感じで情報が表示されて便利です。最後の「layer」は上で設定した「layers」のうちの一つです。
<img width=50% alt="userDataの表示" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/343311/ba7425d1-40cd-985e-db3c-f22cf1f51646.png">

## まとめる
これまで書いたものをまとめると以下の形になります（長いので折り畳み）。文字コードはUTF-8です。
UUIDのつけ方はRevit Exporterに倣いました。

<details>
<summary>JSON全文</summary>
<div>

```JSON:
{
	"geometries": [
	{
		"uuid":"abef3476-2342-46f1-8931-81d22ab55256-MaterialNode_9895680_0",
		"type": "Geometry",
		"data":{
			"vertices":[
				2220.0,0.0,20.0,2180.0,0.0,20.0,2180.0,0.0,-20.0,2220.0,0.0,-20.0,2220.0,430.0,20.0,2180.0,430.0,20.0,2180.0,430.0,-20.0,2220.0,430.0,-20.0
			],
			"normals": [],
 			"uvs": [],
			"faces": [
				0,0,1,2,0,0,2,3,0,4,5,6,0,4,6,7,0,0,3,4,0,3,4,7,0,3,2,7,0,2,7,6,0,6,1,2,0,6,5,1,0,0,1,5,0,0,4,5
			],
			"scale":1.0,
			"visible":true,
			"castShadow":true,
			"receiveShadow":false,
			"doubleSided":true
		}
	},
	{
		"uuid":"a0c57d79-188e-4315-9233-f2105a2f5262-MaterialNode_16774400_0",
		"type": "Geometry",
		"data":{
			"vertices":[
				2220.0,0.0,630.0,2180.0,0.0,630.0,2180.0,0.0,590.0,2220.0,0.0,590.0,2220.0,430.0,630.0,2180.0,430.0,630.0,2180.0,430.0,590.0,2220.0,430.0,590.0
			],
			"normals": [],
 			"uvs": [],
			"faces": [
				0,0,1,2,0,0,2,3,0,4,5,6,0,4,6,7,0,0,3,4,0,3,4,7,0,3,2,7,0,2,7,6,0,6,1,2,0,6,5,1,0,0,1,5,0,0,4,5
			],
			"scale":1.0,
			"visible":true,
			"castShadow":true,
			"receiveShadow":false,
			"doubleSided":true
		}
	},
	{
		"uuid":"cfa8d5e4-d255-4b4b-924a-a92807a7ed49-MaterialNode_16743680_0",
		"type": "Geometry",
		"data":{
			"vertices":[
				2200.55,428.2,0.0,2215.0,428.2,0.0,2215.0,430.0,0.0,2185.0,430.0,0.0,2185.0,428.2,0.0,2199.45,428.2,0.0,2199.45,383.0,0.0,2185.0,383.0,0.0,2185.0,381.2,0.0,2215.0,381.2,0.0,2215.0,383.0,0.0,2200.55,383.0,0.0,2200.55,428.2,610.0,2215.0,428.2,610.0,2215.0,430.0,610.0,2185.0,430.0,610.0,2185.0,428.2,610.0,2199.45,428.2,610.0,2199.45,383.0,610.0,2185.0,383.0,610.0,2185.0,381.2,610.0,2215.0,381.2,610.0,2215.0,383.0,610.0,2200.55,383.0,610.0
			],
			"normals": [],
 			"uvs": [],
			"faces": [
				0,0,11,12,0,11,12,23,0,0,1,12,0,1,12,13,0,1,2,13,0,2,13,14,0,2,3,14,0,3,14,15,0,3,4,15,0,4,15,16,0,4,5,16,0,5,16,17,0,5,6,17,0,6,17,18,0,6,7,18,0,7,18,19,0,7,8,19,0,8,19,20,0,8,9,20,0,9,20,21,0,9,10,21,0,10,21,22,0,10,11,22,0,11,22,23,0,1,2,3,0,1,3,4,0,0,5,6,0,6,11,0,0,7,8,9,0,9,10,7,0,13,14,15,0,15,16,13,0,12,17,18,0,12,18,23,0,19,20,21,0,19,21,22
			],
			"scale":1.0,
			"visible":true,
			"castShadow":true,
			"receiveShadow":false,
			"doubleSided":true
		}
	}
],
"materials": [
	{
		"uuid":"MaterialNode_9895680_0",
		"type":"MeshLambertMaterial",
		"color":9895680,
		"ambient":9895680,
		"emissive":0,
		"opacity":1.0,
		"transparent":false,
		"wireframe":false,
		"shading":1
	},
	{
		"uuid":"MaterialNode_16774400_0",
		"type":"MeshLambertMaterial",
		"color":16774400,
		"ambient":16774400,
		"emissive":0,
		"opacity":1.0,
		"transparent":false,
		"wireframe":false,
		"shading":1
	},
	{
		"uuid":"MaterialNode_16743680_0",
		"type":"MeshLambertMaterial",
		"color":16743680,
		"ambient":16743680,
		"emissive":0,
		"opacity":1.0,
		"transparent":false,
		"wireframe":false,
		"shading":1
	}
],
"object": {
	"uuid":"4bc6463b-9f4f-4705-afeb-89d8f71dfecd",
	"name": "sample name",
	"type":"Scene",
	"matrix":[1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0],
	"userData":{"layers":"柱,梁"},
	"children": [
		{
			"uuid":"abef3476-2342-46f1-8931-81d22ab55256",
			"name":" ",
			"type":"RevitElement",
			"matrix":[1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0],
			"children":[
				{
				"uuid":"abef3476-2342-46f1-8931-81d22ab55256-MaterialNode_9895680_0",
				"name":" ",
				"type":"Mesh",
				"matrix":[1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0],
				"geometry":"abef3476-2342-46f1-8931-81d22ab55256-MaterialNode_9895680_0",
				"material":"MaterialNode_9895680_0"
				}
			],
			"userData":{
				"H(cm)":"40.0",
				"B(cm)":"40.0",
				"layer":"柱"
			}
		},
		{
			"uuid":"a0c57d79-188e-4315-9233-f2105a2f5262",
			"name":" ",
			"type":"RevitElement",
			"matrix":[1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0],
			"children":[
				{
				"uuid":"a0c57d79-188e-4315-9233-f2105a2f5262-MaterialNode_16774400_0",
				"name":" ",
				"type":"Mesh",
				"matrix":[1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0],
				"geometry":"a0c57d79-188e-4315-9233-f2105a2f5262-MaterialNode_16774400_0",
				"material":"MaterialNode_16774400_0"
				}
			],
			"userData":{
				"H(cm)":"40.0",
				"B(cm)":"40.0",
				"layer":"柱"
			}
		},
		{
			"uuid":"cfa8d5e4-d255-4b4b-924a-a92807a7ed49",
			"name":" ",
			"type":"RevitElement",
			"matrix":[1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0],
			"children":[
				{
				"uuid":"cfa8d5e4-d255-4b4b-924a-a92807a7ed49-MaterialNode_16743680_0",
				"name":" ",
				"type":"Mesh",
				"matrix":[1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0],
				"geometry":"cfa8d5e4-d255-4b4b-924a-a92807a7ed49-MaterialNode_16743680_0",
				"material":"MaterialNode_16743680_0"
				}
			],
			"userData":{
				"H(cm)":"48.8",
				"B(cm)":"30.0",
				"t1(cm)":"1.1",
				"t2(cm)":"1.8",
				"layer":"梁"
			}
		}
	]
}
}

```

</div>
</details>

### 上のコードで表示できたもの
単純な柱と梁（H型）です。
<img width=50% alt="外観" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/343311/5f15e89a-b8ba-04c1-0691-1bf079005192.png">


# おまけ（Revit Exporter）
2020年9月現在、[Revit Exporter](https://github.com/tt-acm/Spectacles.RevitExporter "Revit Exporter")は最近のRevit（少なくともRevit 2019）に対応していないようです。
usingを一か所修正してif文を一行追加したら使えるようになったのでこれも記録しておきます。

```C#:SpectaclesExportContext.cs
//using Autodesk.Revit.Utility ←この行を下の行に修正
using AUtodesk.Revit.DB.Visual
```

```C#:Command.cs
foreach (var fi in collector){
    if(fi.Category == null) continue; //←この行を追加
    string category = fi.Category.Name;
```

ちなみにRevitの標準サンプルファイルrac_advanced_sample_projectにExporterを使って変換したものをSpectaclesで表示するとこんな感じです。
<img width=30% alt="外観" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/343311/4cb654c5-00ca-a6c9-9cd1-68231fe73c00.png"> <img width=30% alt="外から見た図" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/343311/0091b9db-bc69-2e48-9137-c7ba95f5d576.png"> <img width=30% alt="中から見た図" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/343311/296cea63-9623-a596-9c3e-211e03a82857.png">
ガラスもちゃんと透けてます。すごいですね。

# 最後に
これをさらに頑張って色々していけばあんな感じになっていくんだな…と思うと果てしないですね。大人しくExporter使います。