Blockbenchのjsonを読み込むファイルローダーを作ってみた

#Blockbenchで作ったMinecraftのモデルをThree.jsで表示したい！

[![icon.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/102207/66c9dc39-157e-ddf8-d2c9-894fb87a3600.png)](https://blockbench.net/)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/102207/92990a5e-82ac-5e6d-5760-2e863a8ae364.png)

読み込み結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/102207/dcb7664a-9d14-a648-1bd8-f061947cd898.png)

#ソースコード
<font color="red">2019-07-26更新。Blockbenchのバージョンアップに伴う修正</font>

```javascript:BBModelLoader.js
// こちらはエンティティ専用、ブロック用はBBModelLoaderBlock()を使用してね
//引数
//  filename: xxxx.bbmodel のようなBlockbenchのjsonファイル
//  texture_canvas_name: CanvasやIMGなどのエレメント名"#texture"など
//  only_visible: 表示設定がオンになっているパーツのみ読み込む
//  onload: コールバック関数。自動でSceneに追加しないのでここで行う事。
//    コールバック関数の戻り引数
//         object:THREE.Object3D :  グループ化されたモデル
//         parts:{name:THREE.Object3D,...} : 連想配列。各パーツを名前で取得できる
function BBModelLoader(filename, textur_canvas_name, only_visible, onload) {
	$.getJSON(filename, function (data) {
		var parts = {};
		if (data.meta && data.meta.format == "1.0" && data["cubes"] && data["outliner"]) {
			var object = new THREE.Object3D();
			object.name = data.name;
			let used_child = [];
			data.outliner.forEach(outline => {
				if (!only_visible || outline.visibility != false) {
					// outliner
					let outliner = new THREE.Object3D();
					outliner.name = outline.name;
					let origin = [
						(outline.origin ? outline.origin[0] : 0),
						(outline.origin ? outline.origin[1] : 0),
						(outline.origin ? outline.origin[2] : 0),
					];
					data.cubes.forEach(element => {
						if (outline.children.indexOf(element.uuid) > -1 && (!only_visible || element.visibility != false)) {
							// cube
							let size = [
								Math.abs(element.from[0] - element.to[0]),
								Math.abs(element.from[1] - element.to[1]),
								Math.abs(element.from[2] - element.to[2])
							];
							let uv_offset = [
								element.uv_offset ? element.uv_offset[0] : 0,
								element.uv_offset ? element.uv_offset[1] : 0
							];
							let flip = false || element.shade == false;
							let faces = getUVFacePoints(size[0], size[1], size[2], flip);
							let inflate = (element.inflate ? element.inflate : 0) * 2;
							let mesh;
							if (size[0] > 0 && size[1] > 0 && size[2] > 0) {
								let UVdata = { width: Math.round(size[0]), height: Math.round(size[1]), depth: size[2], left: uv_offset[0], top: uv_offset[1], flip: flip, iscube: true };
								mesh = new THREE.Mesh(
									new THREE.BoxGeometry(size[0] + inflate, size[1] + inflate, size[2] + inflate),
									createBoxTexture(textur_canvas_name, UVdata.width, UVdata.height, UVdata.depth, UVdata.left, UVdata.top, flip)
								);
								mesh.userData.UVdata = UVdata;
							} else {
								/////// If not cube ///////
								if (size[0] == 0) {
									//Left Face
									let UVdata = { width: faces[0].width, height: faces[0].height, depth: 0, left: uv_offset[0] + faces[0].left, top: uv_offset[1] + faces[0].top, flip: flip, iscube: false };
									mesh = new THREE.Mesh(
										new THREE.PlaneGeometry(faces[0].width + inflate, faces[0].height + inflate),
										createTexture(textur_canvas_name, UVdata.left, UVdata.top, UVdata.width, UVdata.height, flip)
									);
									mesh.rotation.set(0, deg2rad(90), 0);
									mesh.userData.UVdata = UVdata;
								} else if (size[1] == 0) {
									//Top Face
									let UVdata = { width: faces[2].width, height: faces[2].height, z: 0, left: uv_offset[0] + faces[2].left, top: uv_offset[1] + faces[2].top, flip: flip, iscube: false };
									mesh = new THREE.Mesh(
										new THREE.PlaneGeometry(faces[2].width + inflate, faces[2].height + inflate),
										createTexture(textur_canvas_name, UVdata.left, UVdata.top, UVdata.width, UVdata.height, flip)
									);
									mesh.rotation.set(deg2rad(-90), 0, 0);
									mesh.userData.UVdata = UVdata;
								} else if (size[2] == 0) {
									//Front Face
									let UVdata = { width: faces[4].width, height: faces[4].height, depth: 0, left: uv_offset[0] + faces[4].left, top: uv_offset[1] + faces[4].top, flip: flip, iscube: false };
									mesh = new THREE.Mesh(
										new THREE.PlaneGeometry(faces[4].width + inflate, faces[4].height + inflate),
										createTexture(textur_canvas_name, UVdata.left, UVdata.top, UVdata.width, UVdata.height, flip)
									);
									mesh.rotation.set(0, 0, 0);
									mesh.userData.UVdata = UVdata;
								}
							}
							let cube = new THREE.Object3D();
							cube.add(mesh);
							cube.visible = element.visibility != false;
							cube.name = element.name;
							let center = [
								element.from[0] + size[0] / 2,
								element.from[1] + size[1] / 2,
								element.from[2] + size[2] / 2
							];
							let pos = [
								center[0] - origin[0],
								center[1] - origin[1],
								center[2] - origin[2]
							];
							cube.position.set(-pos[0], pos[1], -pos[2]);
							outliner.add(cube);

							//child added
							used_child.push(element.uuid);
						}
					});
					if (outline["rotation"]) {
						let axis = new THREE.Vector3(1, 0, 0);
						outliner.rotateOnWorldAxis(axis, deg2rad(-outline.rotation[0]));
						axis.set(0, 1, 0);
						outliner.rotateOnWorldAxis(axis, deg2rad(outline.rotation[1]));
						axis.set(0, 0, 1);
						outliner.rotateOnWorldAxis(axis, deg2rad(outline.rotation[2]));
					}
					if (outline["origin"]) {
						outliner.position.set(-origin[0], origin[1], -origin[2]);
					}
					parts[outliner.name] = outliner;
					object.add(outliner);
				}

			});
		}
		if (data.meta && data.meta.format_version == "3.0" && data["elements"] && data["outliner"]) {
			var object = new THREE.Object3D();
			object.name = data.name;
			data.outliner.forEach(outline => {
				if (!only_visible || outline.visibility != false) {
					// outliner
					let outliner = new THREE.Object3D();
					outliner.name = outline.name;
					let origin = [
						(outline.origin ? outline.origin[0] : 0),
						(outline.origin ? outline.origin[1] : 0),
						(outline.origin ? outline.origin[2] : 0),
					];
					data.elements.forEach(element => {
						if (outline.children.indexOf(element.uuid) > -1 && (!only_visible || element.visibility != false)) {
							// cube
							let size = [
								Math.abs(element.from[0] - element.to[0]),
								Math.abs(element.from[1] - element.to[1]),
								Math.abs(element.from[2] - element.to[2])
							];
							let uv_offset = [
								element.uv_offset ? element.uv_offset[0] : 0,
								element.uv_offset ? element.uv_offset[1] : 0
							];
							let flip = false || element.shade == false;
							let faces = getUVFacePoints(size[0], size[1], size[2], flip);
							let inflate = (element.inflate ? element.inflate : 0) * 2;
							let mesh;
							if (size[0] > 0 && size[1] > 0 && size[2] > 0) {
								let UVdata = { width: Math.round(size[0]), height: Math.round(size[1]), depth: size[2], left: uv_offset[0], top: uv_offset[1], flip: flip, iscube: true };
								mesh = new THREE.Mesh(
									new THREE.BoxGeometry(size[0] + inflate, size[1] + inflate, size[2] + inflate),
									createBoxTexture(textur_canvas_name, UVdata.width, UVdata.height, UVdata.depth, UVdata.left, UVdata.top, flip)
								);
								mesh.userData.UVdata = UVdata;
							} else {
								/////// If not cube ///////
								if (size[0] == 0) {
									//Left Face
									let UVdata = { width: faces[0].width, height: faces[0].height, depth: 0, left: uv_offset[0] + faces[0].left, top: uv_offset[1] + faces[0].top, flip: flip, iscube: false };
									mesh = new THREE.Mesh(
										new THREE.PlaneGeometry(faces[0].width + inflate, faces[0].height + inflate),
										createTexture(textur_canvas_name, UVdata.left, UVdata.top, UVdata.width, UVdata.height, flip)
									);
									mesh.rotation.set(0, deg2rad(90), 0);
									mesh.userData.UVdata = UVdata;
								} else if (size[1] == 0) {
									//Top Face
									let UVdata = { width: faces[2].width, height: faces[2].height, z: 0, left: uv_offset[0] + faces[2].left, top: uv_offset[1] + faces[2].top, flip: flip, iscube: false };
									mesh = new THREE.Mesh(
										new THREE.PlaneGeometry(faces[2].width + inflate, faces[2].height + inflate),
										createTexture(textur_canvas_name, UVdata.left, UVdata.top, UVdata.width, UVdata.height, flip)
									);
									mesh.rotation.set(deg2rad(-90), 0, 0);
									mesh.userData.UVdata = UVdata;
								} else if (size[2] == 0) {
									//Front Face
									let UVdata = { width: faces[4].width, height: faces[4].height, depth: 0, left: uv_offset[0] + faces[4].left, top: uv_offset[1] + faces[4].top, flip: flip, iscube: false };
									mesh = new THREE.Mesh(
										new THREE.PlaneGeometry(faces[4].width + inflate, faces[4].height + inflate),
										createTexture(textur_canvas_name, UVdata.left, UVdata.top, UVdata.width, UVdata.height, flip)
									);
									mesh.rotation.set(0, 0, 0);
									mesh.userData.UVdata = UVdata;
								}
							}
							let cube = new THREE.Object3D();
							cube.add(mesh);
							cube.visible = element.visibility != false;
							cube.name = element.name;
							let center = [
								element.from[0] + size[0] / 2,
								element.from[1] + size[1] / 2,
								element.from[2] + size[2] / 2
							];
							let pos = [
								center[0] - origin[0],
								center[1] - origin[1],
								center[2] - origin[2]
							];
							cube.position.set(-pos[0], pos[1], -pos[2]);
							outliner.add(cube);
						}
					});
					if (outline["rotation"]) {
						let axis = new THREE.Vector3(1, 0, 0);
						outliner.rotateOnWorldAxis(axis, deg2rad(-outline.rotation[0]));
						axis.set(0, 1, 0);
						outliner.rotateOnWorldAxis(axis, deg2rad(outline.rotation[1]));
						axis.set(0, 0, 1);
						outliner.rotateOnWorldAxis(axis, deg2rad(outline.rotation[2]));
					}
					if (outline["origin"]) {
						outliner.position.set(-origin[0], origin[1], -origin[2]);
					}
					parts[outliner.name] = outliner;
					object.add(outliner);
				}
			});
		}
		if (typeof onload === "function") {
			onload.call(this, object, parts);
		}
	});
}

// ブロック専用
//引数
//  filename: xxxx.bbmodel のようなBlockbenchのjsonファイル
//  texture_canvas_names: CanvasやIMGなどのエレメント名"#texture"などを配列でぶちこむ事
//  only_visible: 表示設定がオンになっているパーツのみ読み込む
//  onload: コールバック関数。自動でSceneに追加しないのでここで行う事。
//    コールバック関数の戻り引数
//         object:THREE.Object3D :  グループ化されたモデル
//         parts:{name:THREE.Object3D,...} : 連想配列。各パーツを名前で取得できる
function BBModelLoaderBlock(filename, textur_canvas_names, only_visible, onload) {
	$.getJSON(filename, function (data) {
		var parts = {};
		if (data.meta && data.meta.format_version == "3.0" && data.meta.model_format == "java_block" && data["elements"] && data["outliner"]) {
			var object = new THREE.Object3D();
			object.name = data.name;

			////// Cube Loading
			data.elements.forEach(element => {
				if (!only_visible || element.visibility != false) {
					let origin = [
						(element.origin ? element.origin[0] : 0),
						(element.origin ? element.origin[1] : 0),
						(element.origin ? element.origin[2] : 0),
					];
					let size = [
						Math.abs(element.from[0] - element.to[0]),
						Math.abs(element.from[1] - element.to[1]),
						Math.abs(element.from[2] - element.to[2])
					];
					let faces = [
						convUVFaceData(textur_canvas_names, element.faces.east),
						convUVFaceData(textur_canvas_names, element.faces.west),
						convUVFaceData(textur_canvas_names, element.faces.up),
						convUVFaceData(textur_canvas_names, element.faces.down),
						convUVFaceData(textur_canvas_names, element.faces.south),
						convUVFaceData(textur_canvas_names, element.faces.north),
					];
					let mesh;
					if (size[0] > 0 && size[1] > 0 && size[2] > 0) {
						mesh = new THREE.Mesh(
							new THREE.BoxGeometry(size[0], size[1], size[2]),
							[
								createTexture(faces[0].texture, faces[0].left, faces[0].top, faces[0].width, faces[0].height, faces[0].hflip, faces[0].vflip, faces[0].rotate),
								createTexture(faces[1].texture, faces[1].left, faces[1].top, faces[1].width, faces[1].height, faces[1].hflip, faces[1].vflip, faces[1].rotate),
								createTexture(faces[2].texture, faces[2].left, faces[2].top, faces[2].width, faces[2].height, faces[2].hflip, faces[2].vflip, faces[2].rotate),
								createTexture(faces[3].texture, faces[3].left, faces[3].top, faces[3].width, faces[3].height, faces[3].hflip, faces[3].vflip, faces[3].rotate),
								createTexture(faces[4].texture, faces[4].left, faces[4].top, faces[4].width, faces[4].height, faces[4].hflip, faces[4].vflip, faces[4].rotate),
								createTexture(faces[5].texture, faces[5].left, faces[5].top, faces[5].width, faces[5].height, faces[5].hflip, faces[5].vflip, faces[5].rotate),
							]
						);
						mesh.userData.UVdata = faces;
					} else {
						/////// If not cube ///////
						if (size[0] == 0) {
							//Left Face
							let UVdata = convUVFaceData(textur_canvas_names, element.faces.west);
							mesh = new THREE.Mesh(
								new THREE.PlaneGeometry(faces[0].width, faces[0].height),
								createTexture(UVdata.texture, UVdata.left, UVdata.top, UVdata.width, UVdata.height, UVdata.hflip, UVdata.vflip, UVdata.rotate)
							);
							mesh.rotation.set(0, deg2rad(-90), 0);
							mesh.userData.UVdata = UVdata;
						} else if (size[1] == 0) {
							//Top Face
							let UVdata = convUVFaceData(textur_canvas_names, element.faces.up);
							mesh = new THREE.Mesh(
								new THREE.PlaneGeometry(faces[2].width, faces[2].height),
								createTexture(UVdata.texture, UVdata.left, UVdata.top, UVdata.width, UVdata.height, UVdata.hflip, UVdata.vflip, UVdata.rotate)
							);
							mesh.rotation.set(deg2rad(-90), 0, 0);
							mesh.userData.UVdata = UVdata;
						} else if (size[2] == 0) {
							//Front Face
							let UVdata = convUVFaceData(textur_canvas_names, element.faces.north);
							mesh = new THREE.Mesh(
								new THREE.PlaneGeometry(faces[5].width, faces[5].height),
								createTexture(UVdata.texture, UVdata.left, UVdata.top, UVdata.width, UVdata.height, UVdata.hflip, UVdata.vflip, UVdata.rotate)
							);
							mesh.rotation.set(0, 0, 0);
							mesh.userData.UVdata = UVdata;
						}
					}
					let cube = new THREE.Object3D();
					cube.add(mesh);
					cube.visible = element.visibility != false;
					cube.name = element.name;
					cube.userData.uuid = element.uuid;
					cube.userData.origin = origin;
					let center = [
						element.from[0] + size[0] / 2,
						element.from[1] + size[1] / 2,
						element.from[2] + size[2] / 2
					];
					let pos = [
						center[0],// - origin[0],
						center[1],// - origin[1],
						center[2],// - origin[2]
					];
					if (element["rotation"]) {
						let axis = new THREE.Vector3(1, 0, 0);
						cube.rotateOnWorldAxis(axis, deg2rad(element.rotation[0]));
						axis.set(0, 1, 0);
						cube.rotateOnWorldAxis(axis, deg2rad(element.rotation[1]));
						axis.set(0, 0, 1);
						cube.rotateOnWorldAxis(axis, deg2rad(element.rotation[2]));
					}
					cube.position.set(pos[0], pos[1], pos[2]);
					object.add(cube);
					parts[cube.name] = cube;
				}
			});

			// append outliners
			data.outliner.forEach(appendOutline, object);

			// for nesting
			function appendOutline(outline, owner){
				if(typeof outline === "string"){
					object.children.forEach(element => {
						if (element.userData.uuid == outline && element.userData.origin) {
							element.position.set(
								element.position.x - element.userData.origin[0],
								element.position.y - element.userData.origin[1],
								element.position.z - element.userData.origin[2],
							);
						}
					});
					return;
				}
				if (!only_visible || outline.visibility != false) {
					// outliner
					let outliner = new THREE.Object3D();
					outliner.name = outline.name;
					outliner.userData.uuid = outline.uuid;
					let origin = [
						(outline.origin ? outline.origin[0] : 0),
						(outline.origin ? outline.origin[1] : 0),
						(outline.origin ? outline.origin[2] : 0),
					];
					outliner.userData.origin = origin;

					outline.children.forEach(child => {
						if(typeof child === "string"){
							object.children.forEach(element => {
								if (element.userData.uuid == child) {
									element.position.set(
										element.position.x - origin[0],
										element.position.y - origin[1],
										element.position.z - origin[2],
									);
									outliner.add(element);
								}
							});
						} else {
							appendOutline(child, outliner);
						}
					});

					if (outline["rotation"]) {
						let axis = new THREE.Vector3(1, 0, 0);
						outliner.rotateOnWorldAxis(axis, deg2rad(outline.rotation[0]));
						axis.set(0, 1, 0);
						outliner.rotateOnWorldAxis(axis, deg2rad(outline.rotation[1]));
						axis.set(0, 0, 1);
						outliner.rotateOnWorldAxis(axis, deg2rad(outline.rotation[2]));
					}
					
					outliner.position.set(-origin[0], -origin[1], -origin[2]);

					if(typeof owner !== "object"){
						object.add(outliner);
					} else {
						owner.add(outliner);
					}
				}
			}
		};

		if (typeof onload === "function") {
			onload.call(this, object, parts);
		}
	});
}

//// MISC Utils /////////////////////////////////////////////////////////////

// IMG->Canvas コンバーター
function imageToCanvas(element) {
	var img = document.querySelector(element);
	var texture_canvas = document.createElement("canvas");
	texture_canvas.setAttribute("width", img.width);
	texture_canvas.setAttribute("height", img.height);
	var texture_contex = texture_canvas.getContext('2d');
	texture_contex.imageSmoothingEnabled = false;
	texture_contex.beginPath();
	texture_contex.clearRect(0, 0, img.width, img.height);
	texture_contex.drawImage(img, 0, 0, img.width, img.height, 0, 0, img.width, img.height);
	return texture_canvas;
}

// キャンバス単体を張り付けたマテリアルを返す
function createTexture(element, posx, posy, width, height, hflip, vflip, rotation) {
	let mag = 1; //resample magnify
	var img = document.querySelector(element);
	var texture_canvas = document.createElement("canvas");
	texture_canvas.setAttribute("width", width * mag);
	texture_canvas.setAttribute("height", height * mag);
	var texture_contex = texture_canvas.getContext('2d');
	texture_contex.imageSmoothingEnabled = false;
	texture_contex.beginPath();
	texture_contex.clearRect(0, 0, width * mag, height * mag);
	texture_contex.scale(hflip ? -1 : 1, vflip ? -1 : 1);
	texture_contex.drawImage(img, posx, posy, width, height, hflip ? -1 * width * mag : 0, vflip ? -1 * height * mag : 0, width * mag, height * mag);
	let rotated_canvas = rotate_img(texture_canvas, rotation);
	texture_canvas.remove();
	let texture = new THREE.CanvasTexture(rotated_canvas);
	texture.magFilter = THREE.NearestFilter;
	texture.minFilter = THREE.NearestFilter;
	texture.type = THREE.FloatType;
	return new THREE.MeshLambertMaterial({ map: texture, alphaMap: createAlphaMap(rotated_canvas), transparent: true, side: THREE.DoubleSide });
}

// アルファチャンネル付きのキャンバスからアルファマップ用モノトーンテクスチャを作成
function createAlphaMap(canvas) {
	var img = canvas;
	var img_ctx = img.getContext('2d');
	var img_data = img_ctx.getImageData(0, 0, img.width, img.height);
	var texture_canvas = document.createElement("canvas");
	texture_canvas.setAttribute("width", img.width);
	texture_canvas.setAttribute("height", img.height);
	var texture_contex = texture_canvas.getContext('2d');
	for(let x = 0; x < img.width; x++){
		for(let y = 0; y < img.height; y++){
			let d = (x + y * img_data.width) * 4;
			let data = img_data.data;
			data[d  ] = data[d+3];
			data[d+1] = data[d+3];
			data[d+2] = data[d+3];
			data[d+3] = 255;
		}
	}
	texture_contex.putImageData(img_data, 0, 0);
	let texture = new THREE.CanvasTexture(texture_canvas);
	texture.magFilter = THREE.NearestFilter;
	texture.minFilter = THREE.NearestFilter;
	texture.type = THREE.FloatType;
	return texture;
}

// 立方体テクスチャを作成してマテリアルの配列を返す
function createBoxTexture(element, x, y, z, posx, posy, hflip) {
	let faces = getUVFacePoints(x, y, z, hflip);
	let img = document.querySelector(element);
	let mag = 1; //resample magnify

	let materials = [];
	for (let i = 0; i < 6; i++) {
		let texture_canvas = document.createElement("canvas");
		texture_canvas.setAttribute("width", faces[i].width * mag);
		texture_canvas.setAttribute("height", faces[i].height * mag);
		let texture_contex = texture_canvas.getContext('2d');
		texture_contex.imageSmoothingEnabled = false;
		texture_contex.beginPath();
		texture_contex.clearRect(0, 0, faces[i].width * mag, faces[i].height * mag);

		texture_contex.scale(hflip ? -1 : 1, faces[i].vflip ? -1 : 1);
		texture_contex.drawImage(img, (posx + faces[i].left), posy + faces[i].top, faces[i].width, faces[i].height, hflip ? -1 * faces[i].width * mag : 0, faces[i].vflip ? -1 * faces[i].height * mag : 0, faces[i].width * mag, faces[i].height * mag);

		let texture = new THREE.CanvasTexture(texture_canvas);
		texture.magFilter = THREE.NearestFilter;
		texture.minFilter = THREE.NearestFilter;
		materials.push(
			new THREE.MeshLambertMaterial({ map: texture, transparent: false, side: THREE.DoubleSide, alphaTest: 0.9, wireframe: false })
		)
	}
	return materials;
}

// 角度からラジアンを返す
function deg2rad(deg) {
	return deg * (Math.PI / 180)
}

//90°単位の画像回転専用関数（THREE.Textureのrotationがなんか変なので）
function rotate_img(img, rotate){
	let canvas_half= ([img.height,img.width]).sort((a,b)=>{return a-b})[1];
	let texture_canvas = document.createElement("canvas");
	texture_canvas.setAttribute("width", canvas_half * 2);
	texture_canvas.setAttribute("height", canvas_half * 2);
	let texture_contex = texture_canvas.getContext('2d');
	texture_contex.imageSmoothingEnabled  = false;
	texture_contex.clearRect(0, 0, canvas_half * 2, canvas_half * 2);
	rotate = (Math.floor(rotate / 90) * 90) % 360;
	const rotated = {
		//  ____|270
		// |_180|__|_
		//   |  |_0__|
		//   |90|
		"0":   {x: canvas_half           , y: canvas_half           , width: img.width, height: img.height},
		"180": {x: canvas_half-img.width , y: canvas_half-img.height, width: img.width, height: img.height},
		"90":  {x: canvas_half-img.height, y: canvas_half           , width: img.height, height: img.width},
		"270": {x: canvas_half           , y: canvas_half-img.width , width: img.height, height: img.width},
	}
	let r = rotated[rotate];
	texture_contex.translate(canvas_half, canvas_half);
	texture_contex.rotate(deg2rad(rotate));
	texture_contex.drawImage(img, 0, 0, img.width, img.height, 0, 0, img.width, img.height);
	let temp_canvas = document.createElement("canvas");
	temp_canvas.setAttribute("width", r.width);
	temp_canvas.setAttribute("height", r.height);
	let temp_contex = temp_canvas.getContext('2d');
	temp_contex.imageSmoothingEnabled  = false;
	temp_contex.clearRect(0, 0, r.width, r.height);
	temp_contex.drawImage(texture_canvas, r.x, r.y, r.width, r.height, 0, 0, r.width, r.height);
	texture_canvas.remove();

	return temp_canvas;
}

// Minecraftのテクスチャの各面に対応する座標を返す
function getUVFacePoints(x, y, z, hflip) {
	// box face: [+X, -X, +Y, -Y, +Z, -Z]
	// box face: [Right, Left, Upper, Bottom, Front, Back]
	return [ //Minecraft Texture mapping
		{ left: hflip ? 0 : z + x, top: z, width: z, height: y },
		{ left: hflip ? z + x : 0, top: z, width: z, height: y },
		{ left: z, top: 0, width: x, height: z },
		{ left: z + x, top: 0, width: x, height: z, vflip: true }, // THREE.js bottom textures are flipped upside down
		{ left: z, top: z, width: x, height: y },
		{ left: z * 2 + x, top: z, width: x, height: y }
	]
}

// bbmodelのfaceエレメントの負の数値から反転フラグを分解
function convUVFaceData(textures, uv_data){
	let texture = textures[uv_data.texture ? uv_data.texture: 0];
	let img = document.querySelector(texture);
	let hflip = uv_data.uv[0] > uv_data.uv[2];
	let vflip = uv_data.uv[1] > uv_data.uv[3];
	let uv_h = [
		hflip? uv_data.uv[2]: uv_data.uv[0],
		hflip? uv_data.uv[0]: uv_data.uv[2]
	];
	let uv_v = [
		vflip? uv_data.uv[3]: uv_data.uv[1],
		vflip? uv_data.uv[1]: uv_data.uv[3]
	];
	return {
		texture: texture,
		left: uv_h[0] % img.width,
		top: uv_v[0] % img.width,
		width: Math.abs(uv_h[1] - uv_h[0]),
		height: Math.abs(uv_v[1] - uv_v[0]),
		hflip: hflip,
		vflip: vflip,
		rotate: uv_data.rotation ? uv_data.rotation: 0,
	};
}

```

#使い方
コールバック関数の中で出来たObjectをSceneに追加してください。
それぞれのパーツは第二引数のpartsに入っていますので、回転などのアニメーションも簡単にできます。

Blockbenchのアニメーション形式には対応していません。


#なんでクラス化してないの？
こっちの方が使いやすそうだったので・・・(\*´ω`\*)

#なんでOBJ/MTLローダーを使わないの？
便利なんですが、回転や拡大縮小がちゃんと再現されないようなのです。
直立なのに微妙に斜めになったりする。
float型による弊害か、Blockbench側の問題？？

**This page images are copyrights Mojang AB.**