#include "editor_scene_importer_fbxconv.h" #include "os/file_access.h" #include "os/os.h" #include "tools/editor/editor_settings.h" #include "scene/3d/mesh_instance.h" #include "scene/animation/animation_player.h" String EditorSceneImporterFBXConv::_id(const String& p_id) const { return p_id.replace(":","_").replace("/","_"); } uint32_t EditorSceneImporterFBXConv::get_import_flags() const { return IMPORT_SCENE|IMPORT_ANIMATION; } void EditorSceneImporterFBXConv::get_extensions(List *r_extensions) const{ r_extensions->push_back("fbx"); r_extensions->push_back("g3dj"); } Color EditorSceneImporterFBXConv::_get_color(const Array& a) { if (a.size()<3) return Color(); Color c; c.r=a[0]; c.g=a[1]; c.b=a[2]; if (a.size()>=4) c.a=a[3]; return c; } Transform EditorSceneImporterFBXConv::_get_transform_mixed(const Dictionary& d,const Dictionary& dbase) { Array translation; if (d.has("translation")) translation=d["translation"]; else if (dbase.has("translation")) translation=dbase["translation"]; Array rotation; if (d.has("rotation")) rotation=d["rotation"]; else if (dbase.has("rotation")) rotation=dbase["rotation"]; Array scale; if (d.has("scale")) scale=d["scale"]; else if (dbase.has("scale")) scale=dbase["scale"]; Transform t; if (translation.size()) { Array tr = translation; if (tr.size()>=3) { t.origin.x=tr[0]; t.origin.y=tr[1]; t.origin.z=tr[2]; } } if (rotation.size()) { Array r = rotation; if (r.size()>=4) { Quat q; q.x = r[0]; q.y = r[1]; q.z = r[2]; q.w = r[3]; t.basis=Matrix3(q); } } if (scale.size()) { Array sc = scale; if (sc.size()>=3) { Vector3 s; s.x=sc[0]; s.y=sc[1]; s.z=sc[2]; t.basis.scale(s); } } return t; } Transform EditorSceneImporterFBXConv::_get_transform(const Dictionary& d) { Transform t; if (d.has("translation")) { Array tr = d["translation"]; if (tr.size()>=3) { t.origin.x=tr[0]; t.origin.y=tr[1]; t.origin.z=tr[2]; } } if (d.has("rotation")) { Array r = d["rotation"]; if (r.size()>=4) { Quat q; q.x = r[0]; q.y = r[1]; q.z = r[2]; q.w = r[3]; t.basis=Matrix3(q); } } if (d.has("scale")) { Array sc = d["scale"]; if (sc.size()>=3) { Vector3 s; s.x=sc[0]; s.y=sc[1]; s.z=sc[2]; t.basis.scale(s); } } return t; } void EditorSceneImporterFBXConv::_detect_bones_in_nodes(State& state,const Array& p_nodes) { for(int i=0;iget_bone_count(); skeleton->add_bone(id); skeleton->set_bone_parent(bone_idx,p_parent); skeleton->set_bone_rest(bone_idx,state.bones[id].rest); state.bones[id].skeleton=skeleton; } } if (d.has("children")) { _parse_skeletons(id,state,d["children"],skeleton,bone_idx); } } } void EditorSceneImporterFBXConv::_detect_bones(State& state) { //This format should mark when a node is a bone, //which is the only thing that Collada does right. //think about others implementing a parser. //Just _one_ string and you avoid loads of lines of code to other people. for(int i=0;i& m,const Dictionary &part) { if (part.has("meshpartid")) { String id = part["meshpartid"]; ERR_FAIL_COND(!state.surface_cache.has(id)); Ref mat; if (part.has("materialid")) { String matid=part["materialid"]; if (state.material_cache.has(matid)) { mat=state.material_cache[matid]; } } int idx = m->get_surface_count(); Array array = state.surface_cache[id].array; DVector indices = array[Mesh::ARRAY_BONES]; if (indices.size() && part.has("bones")) { Map index_map; Array bones=part["bones"]; for(int i=0;ifind_bone(name); if (idx==-1) idx=0; index_map[i]=idx; } } int ilen=indices.size(); { DVector::Write iw=indices.write(); for(int j=0;jadd_surface(state.surface_cache[id].primitive,array); m->surface_set_material(idx,mat); m->surface_set_name(idx,id); } } Error EditorSceneImporterFBXConv::_parse_nodes(State& state,const Array &p_nodes,Node* p_base) { for(int i=0;ilocalize_rests(); print_line("IS SKELETON! "); } else if (state.bones.has(id)) { if (p_base) node=p_base->cast_to(); if (!state.bones[id].has_anim_chan) { print_line("no has anim "+id); } skip=true; } else if (n.has("parts")) { //is a mesh MeshInstance *mesh = memnew( MeshInstance ); node=mesh; Array parts=n["parts"]; String long_identifier; for(int j=0;j m; if (state.mesh_cache.has(long_identifier)) { m=state.mesh_cache[long_identifier]; } else { m = Ref( memnew( Mesh ) ); //and parts are surfaces for(int j=0;jset_mesh(m); } if (!skip) { if (!node) { node = memnew( Spatial ); } node->set_name(id); node->set_transform(_get_transform(n)); p_base->add_child(node); node->set_owner(state.scene); } if (n.has("children")) { Error err = _parse_nodes(state,n["children"],node); if (err) return err; } } return OK; } void EditorSceneImporterFBXConv::_parse_materials(State& state) { for(int i=0;i mat = memnew( FixedMaterial ); if (material.has("diffuse")) { mat->set_parameter(FixedMaterial::PARAM_DIFFUSE,_get_color(material["diffuse"])); } if (material.has("specular")) { mat->set_parameter(FixedMaterial::PARAM_SPECULAR,_get_color(material["specular"])); } if (material.has("emissive")) { mat->set_parameter(FixedMaterial::PARAM_EMISSION,_get_color(material["emissive"])); } if (material.has("shininess")) { float exp = material["shininess"]; mat->set_parameter(FixedMaterial::PARAM_SPECULAR_EXP,exp); } if (material.has("opacity")) { Color c = mat->get_parameter(FixedMaterial::PARAM_DIFFUSE); c.a=material["opacity"]; mat->set_parameter(FixedMaterial::PARAM_DIFFUSE,c); } if (material.has("textures")) { Array textures = material["textures"]; for(int j=0;j tex; if (texture.has("filename")) { String filename=texture["filename"]; String path=state.base_path+"/"+filename.replace("\\","/"); if (state.texture_cache.has(path)) { tex=state.texture_cache[path]; } else { tex = ResourceLoader::load(path,"ImageTexture"); if (tex.is_null()) { if (state.missing_deps) state.missing_deps->push_back(path); } state.texture_cache[path]=tex; //add anyway } } if (tex.is_valid() && texture.has("type")) { String type=texture["type"]; if (type=="DIFFUSE") mat->set_texture(FixedMaterial::PARAM_DIFFUSE,tex); else if (type=="SPECULAR") mat->set_texture(FixedMaterial::PARAM_SPECULAR,tex); else if (type=="SHININESS") mat->set_texture(FixedMaterial::PARAM_SPECULAR_EXP,tex); else if (type=="NORMAL") mat->set_texture(FixedMaterial::PARAM_NORMAL,tex); else if (type=="EMISSIVE") mat->set_texture(FixedMaterial::PARAM_EMISSION,tex); } } } state.material_cache[id]=mat; } } void EditorSceneImporterFBXConv::_parse_surfaces(State& state) { for(int i=0;i iarray; Map array; for(int k=0;k vtx; vtx.resize(array.size()); { int len=array.size(); DVector::Write w = vtx.write(); for(int l=0;l tangents; tangents.resize(array.size()*4); { int len=array.size(); DVector::Write w = tangents.write(); for(int l=0;l cols; cols.resize(array.size()); { int len=array.size(); DVector::Write w = cols.write(); for(int l=0;l uvs; uvs.resize(array.size()); { int len=array.size(); DVector::Write w = uvs.write(); for(int l=0;l arr; arr.resize(array.size()*4); int po=k==Mesh::ARRAY_WEIGHTS?1:0; lofs=ofs[Mesh::ARRAY_BONES]; { int len=array.size(); DVector::Write w = arr.write(); for(int l=0;l arr; arr.resize(indices.size()); { int len=indices.size(); DVector::Write w = arr.write(); for(int l=0;l ia=arrays[Mesh::ARRAY_INDEX]; int len=ia.size(); { DVector::Write w=ia.write(); for(int l=0;ladd_child(ap); ap->set_owner(state.scene); for(int i=0;i an = memnew( Animation ); an->set_name(_id(anim["id"])); if (anim.has("bones")) { Array bone_tracks = anim["bones"]; for(int j=0;jfind_bone(bone); if (bone_idx==-1) continue; String path = state.scene->get_path_to(sk); path+=":"+bone; an->add_track(Animation::TYPE_TRANSFORM); int tidx = an->get_track_count()-1; an->track_set_path(tidx,path); Dictionary parent_xform_dict; Dictionary xform_dict; if (state.bones.has(bone)) { xform_dict=state.bones[bone].node; } Array parent_keyframes; if (sk->get_bone_parent(bone_idx)!=-1) { String parent_name = sk->get_bone_name(sk->get_bone_parent(bone_idx)); if (state.bones.has(parent_name)) { parent_xform_dict=state.bones[parent_name].node; } print_line("parent for "+bone+"? "+parent_name+" XFD: "+String(Variant(parent_xform_dict))); for(int k=0;ktime) break; prev_kt=kt; idx++; } Transform t; if (idx==0) { t=_get_transform_mixed(parent_keyframes[0],parent_xform_dict); } else if (idx==parent_keyframes.size()){ t=_get_transform_mixed(parent_keyframes[idx-1],parent_xform_dict); } else { t=_get_transform_mixed(parent_keyframes[idx-1],parent_xform_dict); float d = (time-prev_kt)/(kt-prev_kt); if (d>0) { Transform t2=_get_transform_mixed(parent_keyframes[idx],parent_xform_dict); t=t.interpolate_with(t2,d); } else { print_line("exact: "+rtos(kt)); } } xform = t.affine_inverse() * xform; //localize } else if (!parent_xform_dict.empty()) { Transform t = _get_transform(parent_xform_dict); xform = t.affine_inverse() * xform; //localize } #endif xform = sk->get_bone_rest(bone_idx).affine_inverse() * xform; Quat q = xform.basis; q.normalize(); Vector3 s = xform.basis.get_scale(); Vector3 l = xform.origin; an->transform_track_insert_key(tidx,time,l,q,s); } } } ap->add_animation(_id(anim["id"]),an); } return OK; } Error EditorSceneImporterFBXConv::_parse_json(State& state, const String &p_path) { //not the happiest.... Vector data = FileAccess::get_file_as_array(p_path); ERR_FAIL_COND_V(!data.size(),ERR_FILE_CANT_OPEN); String str; bool utferr = str.parse_utf8((const char*)data.ptr(),data.size()); ERR_FAIL_COND_V(utferr,ERR_PARSE_ERROR); Dictionary dict; Error err = dict.parse_json(str); str=String(); //free mem immediately ERR_FAIL_COND_V(err,err); if (dict.has("meshes")) state.meshes=dict["meshes"]; if (dict.has("materials")) state.materials=dict["materials"]; if (dict.has("nodes")) state.nodes=dict["nodes"]; if (dict.has("animations")) state.animations=dict["animations"]; state.scene = memnew( Spatial ); _detect_bones(state); _parse_surfaces(state); _parse_materials(state); err = _parse_nodes(state,state.nodes,state.scene); if (err) return err; if (state.import_animations) { err = _parse_animations(state); if (err) return err; } print_line("JSON PARSED O-K!"); return OK; } Error EditorSceneImporterFBXConv::_parse_fbx(State& state,const String& p_path) { state.base_path=p_path.get_base_dir(); if (p_path.to_lower().ends_with("g3dj")) { return _parse_json(state,p_path.basename()+".g3dj"); } String tool = EDITOR_DEF("fbxconv/path",""); ERR_FAIL_COND_V( !FileAccess::exists(tool),ERR_UNCONFIGURED); String wine = EDITOR_DEF("fbxconv/use_wine",""); List args; String path=p_path; if (wine!="") { List wpargs; wpargs.push_back("-w"); wpargs.push_back(p_path); String pipe; //winepath to convert to windows path int wpres; Error wperr = OS::get_singleton()->execute(wine+"path",wpargs,true,NULL,&pipe,&wpres); ERR_FAIL_COND_V(wperr!=OK,ERR_CANT_CREATE); ERR_FAIL_COND_V(wpres!=0,ERR_CANT_CREATE); path=pipe.strip_edges(); args.push_back(tool); tool=wine; } args.push_back("-o"); args.push_back("G3DJ"); args.push_back(path); int res; Error err = OS::get_singleton()->execute(tool,args,true,NULL,NULL,&res); ERR_FAIL_COND_V(err!=OK,ERR_CANT_CREATE); ERR_FAIL_COND_V(res!=0,ERR_CANT_CREATE); return _parse_json(state,p_path.basename()+".g3dj"); } Node* EditorSceneImporterFBXConv::import_scene(const String& p_path,uint32_t p_flags,List *r_missing_deps,Error* r_err){ State state; state.scene=NULL; state.missing_deps=r_missing_deps; state.import_animations=p_flags&IMPORT_ANIMATION; Error err = _parse_fbx(state,p_path); if (err!=OK) { if (r_err) *r_err=err; return NULL; } return state.scene; } Ref EditorSceneImporterFBXConv::import_animation(const String& p_path,uint32_t p_flags){ return Ref(); } EditorSceneImporterFBXConv::EditorSceneImporterFBXConv() { EDITOR_DEF("fbxconv/path",""); #ifndef WINDOWS_ENABLED EDITOR_DEF("fbxconv/use_wine",""); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING,"fbxconv/use_wine",PROPERTY_HINT_GLOBAL_FILE)); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING,"fbxconv/path",PROPERTY_HINT_GLOBAL_FILE)); #else EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING,"fbxconv/path",PROPERTY_HINT_GLOBAL_FILE,"exe")); #endif }