見出し画像

ティラノスクリプトの3D その7:3d_model_new

初めに

今回は、既存のタグ [3d_model_new] の機能拡張です。拡張項目は、three.js で標準的にサポートしている 3dモデルファイル形式をロードして表示させることと、モデルの親子関係指定機能の付加です。また、大本の [3d_model_new] は、モーションデータが内包している場合自動でモーションを開始しますが、ティラノスクリプトの new 系のタグでは、標準で作成時は非表示なので、この仕様は今一整合がとれてないようなので、モーションを開始しない切り口も用意しました。今回は、あくまでロードして表示できるまでの記述と認識願います。

タグ説明

[3d_model_new]

3Dモデルファイルを指定して、ティラノスクリプトに取り込みます。その時点では表示されません。3Dモデルファイルは、data/others/3d/model フォルダに存在しているとみなします。ただ、別の場所に保管する方法はあります。
取り込めるファイル形式を以下に拡張子毎に示します。
gltf/glb :gltfは、テキスト形式、glbはバイナリ形式のファイル形式です。リアルタイムレンダリング向きのメジャーなファイル形式です。モーション情報も内包出来ます。
obj :テキスト形式の割と古い形式です。モーションや、リグ構造は含みませんが、非常にポピュラーな型式です。
3dsAutodesk社の 3ds Max 用のファイル形式です。非常に古くからある形式ですので、割と有名です。
dae :Collada ファイル形式です。かなり新しく多機能なファイル形式です。モーション情報も内包出来ます。メジャーな3Dソフトは殆ど、インポート、エキスポートをサポートしています。
fbx :FBXファイルは、Collada形式と同レベルの情報を内包可能なファイル形式です。殆どの3Dソフトでサポートされてます。
pmd/pmx :これらのファイル形式は、MMD用のモデルファイルです。モーションは、別に vmd という拡張子で提供されています。この形式の特徴は、IK物理演算機構の情報をファイルに内包してますので、人の髪や服をなびかせたり、ダンスのモーションなどを比較的簡単に作れる点です。
vrmgltf 型式を人体モデルに特化させた、VR向けの3Dアバター向けのファイル形式です。gltf と異なり、モーション情報は内包できません。
以下にパラメータを列記します。
name [必須] このモデルの名前です。ティラノくスクリプト内でこの名前で参照しあいます。
storage [必須] 3dモデルファイル名を指定します。拡張子まで含めて記述します。ファイル名の前に model フォルダ以下のフォルダを "/" 付きで一緒に書くことも可能です。
folder [選択] これを指定すると、dataフォルダ下の任意の場所にファイルを保存できます。但しこの時は、storage には、フォルダを指定しないようにしてください。
motion [選択] このバラメータに "false" を指定すると、ファイル内にモーションデータが有ってもモーション動作をしません。また、無指定 "" の場合は、モーションが有った場合その最初のモーションを実行します。意図的に名前を指定すると、そのモーション名のモーションを実行します。因みに、ロード時モーションがある場合、ティラノスタジオのデバッグウインドウのconsole にその名前の一覧をコメントアウトするようにしています。
tonemap [選択] トーンマッピングに指定をします。デフォルトは、"true" です。
pos [選択] 3Dモデルの位置を指定します。これは、そのモデルにおける[0,0,0] の位置を、シーン内の何処に置くかを指定します。モデルの中心の位置ではありませんの注意が必要です。(モデルごとにポリシーが異なりますので、意図しない場所に現れる可能性はあります。)デフォルトは、[0,0,0] です。
rot [選択] 3Dモデルの回転を指定します。値は、ラジアンなので、面倒です。デフォルトは、無回転です。
scale [選択] 3Dモデルの拡大率を指定します。ティラノスクリプトのデフォルト値は、100 ですが、これはちょっと異常ですので、こちらでは、1 にしています。3Dモデル毎に癖がありますので、読み込む前に何らかのビュアーでチェックしておいた方がいいと思います。なお、-1 のように指定すると、反転します。アリシアの場合は、向こうを向いてしまいましたので、zに -1 を指定しました。
texture_folder [選択] 3Dファイルが、3DSの時のみ意味があります。3DS は、テクスチャーイメージをファイル内に持たずに、外部参照します。そのため、イメージファイルを置く場所をこれで指定します。3DSファイルと同じ場所なら、 "" を指定します。デフォルトでは、 "textures" です。

コード

VRM は、GLTFLoder だけで済ませるつもりだったのですが、vroid のサイトで、VRMの three.js 向けのライブラリが公開されてましたので、急遽ライブラリの追加をおこないました。ライブラリ自体は、

です。これをライブラリ保存場所に追加で置きました。
そして、init.ks を以下のように変更しました。

[iscript]
	array_scripts = [
		//"./data/others/plugin/extend3D/three.js",
		//"./data/others/plugin/extend3D/loaders/GLTFLoader.js",
		//"./data/others/plugin/extend3D/loaders/OBJLoader.js",
		//"./data/others/plugin/extend3D/loaders/MTLLoader.js",
		//"./tyrano/libs/three/controls/TransformControls.js",
		"./data/others/plugin/extend3D/ammo.js",
		"./data/others/plugin/extend3D/loaders/AmmoPhysics.js",
		"./data/others/plugin/extend3D/animation/CCDIKSolver.js",
		"./data/others/plugin/extend3D/loaders/MMDLoader.js",
		"./data/others/plugin/extend3D/libs/mmdparser.js",
		"./data/others/plugin/extend3D/animation/MMDAnimationHelper.js",
		"./data/others/plugin/extend3D/animation/MMDPhysics.js",
		"./data/others/plugin/extend3D/shaders/MMDToonShader.js",
		"./data/others/plugin/extend3D/loaders/TGALoader.js",
		//"./data/others/plugin/extend3D/controls/OrbitControls.js",
		"./data/others/plugin/extend3D/effects/OutlineEffect.js",
		"./data/others/plugin/extend3D/loaders/TDSLoader.js",
		"./data/others/plugin/extend3D/loaders/ColladaLoader.js",
		"./data/others/plugin/extend3D/loaders/FBXLoader.js",
		"./data/others/plugin/extend3D/libs/fflate.min.js",
		"./data/others/plugin/extend3D/vrm/three-vrm.min.js",
	]
	//debugger;
	//複数のスクリプトを一括して読み込み、但し前のスクリプトの読み込みが終わるまで次の読み込みは行わない。
	var array_size = array_scripts.length;
	var arr_id = 0;
	if( 0 < array_size ){
		function get_Script(src) {
			$.getScript(src, function() {
				if( 0 < src.indexOf('ammo',20) ){
					Ammo().then( function( AmmoLib ) {
						Ammo = AmmoLib;
					});
				}
				arr_id++;
				if( arr_id < array_size ){
					get_Script(array_scripts[arr_id]);
				}
			});
		};
		get_Script(array_scripts[arr_id]);
	}
//https://unpkg.com/three@0.147.0/build/three.js';
[endscript]
[loadjs storage="/plugin/extend3d/extend3D.js"]
[macro name="3d_dome_new"]
[3d_dome *]
[endmacro]
[return]

array_scripts の最後に three-vrm.min.js を指定しています。
最終的には、さらに追加される可能性があります。
そして、[3d_model_new] は、以下の通りです。

tyrano.plugin.kag.tag["3d_model_new"] = {
	vital: ["name", "storage"],
	pm: {
		name: "",
		storage: "",
		pos: "0",
		rot: "0",
		scale: "1",
		tonemap: "true",
		motion: "",
		next: "true",
		folder: "",
		update: "",
		visible:"false",
		texture_folder:"textures",
		parent:"",
	},
	start : function (pm) {
		var three = this.kag.tmp.three;
		let storage_url = $.get_fullpath( pm.storage, pm.folder, "model");
		function set_datas( model, mixer, vrm=null ){
			let pos = $.three_pos(pm.pos),
			scale = $.three_pos(pm.scale),
			rot = $.three_pos(pm.rot);
			model.position.set(pos.x, pos.y, pos.z);
			model.scale.set(scale.x, scale.y, scale.z);
			model.rotation.set(rot.x, rot.y, rot.z);			
			let flag = ("true" == pm.tone )? true : false ;
			model.traverse(function (node) {
				if (node.isMesh) {
					node.material.toneMapped = flag;
				}
			});
			let visible = pm.visible ;
			if (0 != $.checkThreeModel(pm.parent)) {
				let parent = TYRANO.kag.tmp.three.models[pm.parent];
				parent.model.add( model );
				if (pm.visible == true ) pm.visible = false;
			}
			three.models[pm.name] = new ThreeModel(
			{ name: pm.name, model: model, mixer: mixer, pm: pm, vrm:vrm },three);
			if( pm.visible != visible ) pm.visible = visible;
		}
		function print_animations( animations ){
			animations.forEach( function(element) { console.log( "*** animation name:" + element.name )});
		}
		function play_animation( mixer, animations){
			if (animations.length > 0) {
				print_animations( animations);
				if( pm.motion != "false"){
					let anim = animations[0];
					if ("" != pm.motion){
						for (var i = 0; i < animations.length; i++) {
							if (animations[i].name == pm.motion) {
								anim = animations[i];
								break;
							}
						}
					}					
					mixer.clipAction(anim).play();
				}
			}
		}
		var ext = $.getExt(pm.storage);
		if ("gltf" == ext || "glb" == ext ) {
			new THREE.GLTFLoader().load(storage_url, (data) => {
				var gltf = data,
				model = gltf.scene;
				model.userData["type"]="gltf";
				const animations = gltf.animations;
				model.animations.push(...animations);
				let mixer = new THREE.AnimationMixer(model);
				set_datas( model, mixer );
				play_animation( mixer, animations );
				"true" == pm.next && this.kag.ftag.nextOrder();
			},
			function(xhr){},
			function(error){
				alert( storage_url + "のロードに失敗しました。" + error );
				pm.next == "true" && this.kag.ftag.nextOrder();
			});
		}else if ("vrm" == ext ) {
			const loader = new THREE.GLTFLoader();
			loader.register((parser) =>{ return new THREE.VRMLoaderPlugin(parser); });
			loader.load(storage_url, (data) => {
				const vrm = data.userData.vrm;
				const model = vrm.scene;
				let mixer = void 0;
				model.userData["type"] ="vrm";
				set_datas( model, mixer, vrm );
				"true" == pm.next && this.kag.ftag.nextOrder();
			},
			function(xhr){},
			function(error){
				alert( storage_url + "のロードに失敗しました。" + error );
				pm.next == "true" && this.kag.ftag.nextOrder();
			});
		}else if ("obj" == ext) {
			var mtl_url = (obj_url = storage_url).replace(".obj", ".mtl");
			new THREE.MTLLoader().load(mtl_url, (materials) => {
				materials.preload();
				var objLoader = new THREE.OBJLoader();
				objLoader.setMaterials(materials);
				materials.toneMaped = !1;
				objLoader.load(obj_url, (obj) => {
					var model = obj;
					model.userData["type"] = "obj";
					let mixer = void 0;
					set_datas( model, mixer );
					"true" == pm.next && this.kag.ftag.nextOrder();
				},
				function(xhr){},
				function(error){
					alert( storage_url + "のロードに失敗しました。" + error );
					pm.next == "true" && this.kag.ftag.nextOrder();
				});
			});
		}else if(ext=="3ds"){
			// 3dsファイルロード
			texture_path = $.get_fullpath( pm.texture_folder+"/", pm.folder, "model");
			var loader = new THREE.TDSLoader();
			loader.setResourcePath( texture_path );
			loader.load(storage_url,(data)=>{
				var model = data;
				data.userData["type"]="3ds";
				let mixer = void 0;
				set_datas( model, mixer );
				"true" == pm.next && this.kag.ftag.nextOrder();
			},
			function(xhr){},
			function(error){
				alert( storage_url + "のロードに失敗しました。" + error );
				pm.next == "true" && this.kag.ftag.nextOrder();
			});
		}else if(ext=="dae"){
			// Colladaファイルロード
			var loader = new THREE.ColladaLoader();
			loader.load(storage_url,(data)=>{
				let model = data.scene;
				model.userData["type"]="dae";
				const animations = model.animations;
				let mixer = new THREE.AnimationMixer(model);
				set_datas( model, mixer );
				play_animation( mixer, animations );
				"true" == pm.next && this.kag.ftag.nextOrder();
				},
				function(xhr){},
				function(error){
					alert( storage_url + "のロードに失敗しました。" + error );
					pm.next == "true" && this.kag.ftag.nextOrder();
				}
			);
		}else if(ext=="fbx"){
			// fbxファイルロード
			var loader = new THREE.FBXLoader();
			loader.load(storage_url,(data)=>{
				let model = data;
				model.userData["type"]="fbx";
				const animations = model.animations;
				let mixer = new THREE.AnimationMixer(model);
				set_datas( model, mixer );
				play_animation( mixer, animations );
				"true" == pm.next && this.kag.ftag.nextOrder();
			},
			function(xhr){},
			function(error){
				alert( storage_url + "のロードに失敗しました。" + error );
				pm.next == "true" && this.kag.ftag.nextOrder();
			}
			);
		}else if(ext=="pmd" || ext=="pmx"){
			var loader = new THREE.MMDLoader();
			loader.load(storage_url,(data)=>{
				let model = data;
				model.userData["type"]="mmd";
				let mixer = void 0;
				set_datas( model, mixer );
				"true" == pm.next && this.kag.ftag.nextOrder();
				},
				function(xhr){},
				function(error){
					alert( storage_url + "のロードに失敗しました。" + error );
					pm.next == "true" && this.kag.ftag.nextOrder();
				}
			);
		}else{
			alert("エラー:"+ext+"はサポートしていないファイル形式です");
		}
	}
};
TYRANO.kag.ftag.master_tag["3d_model_new"] = TYRANO.kag.tag["3d_model_new"];
TYRANO.kag.ftag.master_tag["3d_model_new"].kag = TYRANO.kag;

基本、各ファイル拡張子用のローダーを生成して読み込み、そのローダー毎に、シーンに追加する Object3D を選んで登録するだけです。ただ、ロード後の処理を、全てコールバック部に書かないといけないので、function 化して、コンパクトにはしてますが、やはり鬱陶しい感じです。尚、全ての Object3D の userData に type 属性を付加し、モデルの種類を登録してますが、これは、次のモーション操作時に分別するためです。  
VRM の場合は、ドキュメントを参考に記述しました。ちょっとロード直後の中身をのぞいてみました。

ロードしたデータ構造

一応、指示では、data.userData.vrm.scene がモデルの実体となってますが、これって、data.scene と同じみたいですね。それ自体は、three.jsgroup で、その中に Mesh が4個と、何か意味ありげな r が、17個入ってます。vrm の中には、scene 以外にいろいろ格納されてますが、モーション時に必要な場合は、今の記述だけでは、後からモーションさせることが出来ません。そうなったら、この部分の修正が必要になりそうです。
3月21日修正
 やはり、以前のコードでは、vrmのモーションがうまくいかなかったので修正しました。vrmの部分で、付加的データを保存する必要がありました。
ここまで修正
基本、この時点では、表示する操作はしないはずですが、visible パラメータ がありますので、それに合った動作は、するように記述しています。この未公開パラメーターは、ティラノスクリプトでシーンのロードが有った時にその直前の状態を再現するための切り口のようです。

この記事が気に入ったらサポートをしてみませんか?