Beispiel #1
0
Vector<uint8_t> EditorSceneExportPlugin::custom_export(String& p_path,const Ref<EditorExportPlatform> &p_platform) {

	if (!EditorImportExport::get_singleton()->get_convert_text_scenes()) {
		return Vector<uint8_t>();
	}


	String extension = p_path.extension();

	//step 1 check if scene

	if (extension=="xml" || extension=="xres") {

		String type = ResourceLoader::get_resource_type(p_path);

		if (type!="PackedScene")
			return Vector<uint8_t>();

	} else if (extension!="tscn" && extension!="xscn") {
		return Vector<uint8_t>();
	}

	//step 2 check if cached

	uint64_t sd=0;
	String smd5;
	String gp = Globals::get_singleton()->globalize_path(p_path);
	String md5=gp.md5_text();
	String tmp_path = EditorSettings::get_singleton()->get_settings_path().plus_file("tmp/");

	bool valid=false;
	{
		//if existing, make sure it's valid
		FileAccessRef f = FileAccess::open(tmp_path+"scnexp-"+md5+".txt",FileAccess::READ);
		if (f) {

			uint64_t d = f->get_line().strip_edges().to_int64();
			sd = FileAccess::get_modified_time(p_path);

			if (d==sd) {
				valid=true;
			} else {
				String cmd5 = f->get_line().strip_edges();
				smd5 = FileAccess::get_md5(p_path);
				if (cmd5==smd5) {
					valid=true;
				}
			}


		}
	}

	if (!valid) {
		//cache failed, convert
		DirAccess *da = DirAccess::create(DirAccess::ACCESS_RESOURCES);

		String copy = p_path+".convert."+extension;

		// a copy will allow loading the internal resources without conflicting with opened scenes
		da->copy(p_path,copy);

		//@todo for tscn use something more efficient

		Ref<PackedScene> copyres =  ResourceLoader::load(copy,"PackedScene");

		da->remove(copy);

		memdelete(da);

		ERR_FAIL_COND_V(!copyres.is_valid(),Vector<uint8_t>());

		Error err = ResourceSaver::save(tmp_path+"scnexp-"+md5+".scn",copyres);

		copyres=Ref<PackedScene>();

		ERR_FAIL_COND_V(err!=OK,Vector<uint8_t>());

		FileAccessRef f = FileAccess::open(tmp_path+"scnexp-"+md5+".txt",FileAccess::WRITE);

		if (sd==0)
			sd = FileAccess::get_modified_time(p_path);
		if (smd5==String())
			smd5 = FileAccess::get_md5(p_path);

		f->store_line(String::num(sd));
		f->store_line(smd5);
		f->store_line(gp); //source path for reference
	}


	Vector<uint8_t> ret = FileAccess::get_file_as_array(tmp_path+"scnexp-"+md5+".scn");

	p_path+=".converted.scn";

	return ret;

}
Error ResourceImporterOBJ::import(const String &p_source_file, const String &p_save_path, const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files) {

	FileAccessRef f = FileAccess::open(p_source_file, FileAccess::READ);
	ERR_FAIL_COND_V(!f, ERR_CANT_OPEN);

	Ref<ArrayMesh> mesh = Ref<ArrayMesh>(memnew(ArrayMesh));
	Map<String, Ref<Material> > name_map;

	bool generate_normals = p_options["generate/normals"];
	bool generate_tangents = p_options["generate/tangents"];
	bool flip_faces = p_options["force/flip_faces"];
	bool force_smooth = p_options["force/smooth_shading"];
	bool weld_vertices = p_options["force/weld_vertices"];
	float weld_tolerance = p_options["force/weld_tolerance"];
	Vector<Vector3> vertices;
	Vector<Vector3> normals;
	Vector<Vector2> uvs;
	String name;

	Ref<SurfaceTool> surf_tool = memnew(SurfaceTool);
	surf_tool->begin(Mesh::PRIMITIVE_TRIANGLES);
	if (force_smooth)
		surf_tool->add_smooth_group(true);
	int has_index_data = false;

	while (true) {

		String l = f->get_line().strip_edges();

		if (l.begins_with("v ")) {
			//vertex
			Vector<String> v = l.split(" ", false);
			ERR_FAIL_COND_V(v.size() < 4, ERR_INVALID_DATA);
			Vector3 vtx;
			vtx.x = v[1].to_float();
			vtx.y = v[2].to_float();
			vtx.z = v[3].to_float();
			vertices.push_back(vtx);
		} else if (l.begins_with("vt ")) {
			//uv
			Vector<String> v = l.split(" ", false);
			ERR_FAIL_COND_V(v.size() < 3, ERR_INVALID_DATA);
			Vector2 uv;
			uv.x = v[1].to_float();
			uv.y = 1.0 - v[2].to_float();
			uvs.push_back(uv);

		} else if (l.begins_with("vn ")) {
			//normal
			Vector<String> v = l.split(" ", false);
			ERR_FAIL_COND_V(v.size() < 4, ERR_INVALID_DATA);
			Vector3 nrm;
			nrm.x = v[1].to_float();
			nrm.y = v[2].to_float();
			nrm.z = v[3].to_float();
			normals.push_back(nrm);
		} else if (l.begins_with("f ")) {
			//vertex

			has_index_data = true;
			Vector<String> v = l.split(" ", false);
			ERR_FAIL_COND_V(v.size() < 4, ERR_INVALID_DATA);

			//not very fast, could be sped up

			Vector<String> face[3];
			face[0] = v[1].split("/");
			face[1] = v[2].split("/");
			ERR_FAIL_COND_V(face[0].size() == 0, ERR_PARSE_ERROR);
			ERR_FAIL_COND_V(face[0].size() != face[1].size(), ERR_PARSE_ERROR);
			for (int i = 2; i < v.size() - 1; i++) {

				face[2] = v[i + 1].split("/");
				ERR_FAIL_COND_V(face[0].size() != face[2].size(), ERR_PARSE_ERROR);
				for (int j = 0; j < 3; j++) {

					int idx = j;

					if (!flip_faces && idx < 2) {
						idx = 1 ^ idx;
					}

					if (face[idx].size() == 3) {
						int norm = face[idx][2].to_int() - 1;
						if (norm < 0)
							norm += normals.size() + 1;
						ERR_FAIL_INDEX_V(norm, normals.size(), ERR_PARSE_ERROR);
						surf_tool->add_normal(normals[norm]);
					}

					if (face[idx].size() >= 2 && face[idx][1] != String()) {
						int uv = face[idx][1].to_int() - 1;
						if (uv < 0)
							uv += uvs.size() + 1;
						ERR_FAIL_INDEX_V(uv, uvs.size(), ERR_PARSE_ERROR);
						surf_tool->add_uv(uvs[uv]);
					}

					int vtx = face[idx][0].to_int() - 1;
					if (vtx < 0)
						vtx += vertices.size() + 1;
					ERR_FAIL_INDEX_V(vtx, vertices.size(), ERR_PARSE_ERROR);

					Vector3 vertex = vertices[vtx];
					if (weld_vertices)
						vertex.snap(Vector3(weld_tolerance, weld_tolerance, weld_tolerance));
					surf_tool->add_vertex(vertex);
				}

				face[1] = face[2];
			}
		} else if (l.begins_with("s ") && !force_smooth) { //smoothing
			String what = l.substr(2, l.length()).strip_edges();
			if (what == "off")
				surf_tool->add_smooth_group(false);
			else
				surf_tool->add_smooth_group(true);

		} else if (l.begins_with("o ") || f->eof_reached()) { //new surface or done

			if (has_index_data) {
				//new object/surface
				if (generate_normals || force_smooth)
					surf_tool->generate_normals();
				if (uvs.size() && (normals.size() || generate_normals) && generate_tangents)
					surf_tool->generate_tangents();

				surf_tool->index();
				mesh = surf_tool->commit(mesh);
				if (name == "")
					name = vformat(TTR("Surface %d"), mesh->get_surface_count() - 1);
				mesh->surface_set_name(mesh->get_surface_count() - 1, name);
				name = "";
				surf_tool->clear();
				surf_tool->begin(Mesh::PRIMITIVE_TRIANGLES);
				if (force_smooth)
					surf_tool->add_smooth_group(true);

				has_index_data = false;

				if (f->eof_reached())
					break;
			}

			if (l.begins_with("o ")) //name
				name = l.substr(2, l.length()).strip_edges();
		}
	}

	/*
	TODO, check existing materials and merge?
	//re-apply materials if exist
	for(int i=0;i<mesh->get_surface_count();i++) {

		String n = mesh->surface_get_name(i);
		if (name_map.has(n))
			mesh->surface_set_material(i,name_map[n]);
	}
*/

	Error err = ResourceSaver::save(p_save_path + ".mesh", mesh);

	return err;
}
Error EditorMeshImportPlugin::import(const String& p_path, const Ref<ResourceImportMetadata>& p_from){


	ERR_FAIL_COND_V(p_from->get_source_count()!=1,ERR_INVALID_PARAMETER);

	Ref<ResourceImportMetadata> from=p_from;

	String src_path=EditorImportPlugin::expand_source_path(from->get_source_path(0));
	FileAccessRef f = FileAccess::open(src_path,FileAccess::READ);
	ERR_FAIL_COND_V(!f,ERR_CANT_OPEN);

	Ref<Mesh> mesh;
	Map<String,Ref<Material> > name_map;

	if (FileAccess::exists(p_path)) {
		mesh=ResourceLoader::load(p_path,"Mesh");
		if (mesh.is_valid()) {
			for(int i=0;i<mesh->get_surface_count();i++) {

				if (!mesh->surface_get_material(i).is_valid())
					continue;
				String name;
				if (mesh->surface_get_name(i)!="")
					name=mesh->surface_get_name(i);
				else
					name=vformat(TTR("Surface %d"),i+1);

				name_map[name]=mesh->surface_get_material(i);
			}

			while(mesh->get_surface_count()) {
				mesh->surface_remove(0);
			}
		}
	}

	if (!mesh.is_valid())
		mesh = Ref<Mesh>( memnew( Mesh ) );


	bool generate_normals=from->get_option("generate/normals");
	bool generate_tangents=from->get_option("generate/tangents");
	bool flip_faces=from->get_option("force/flip_faces");
	bool force_smooth=from->get_option("force/smooth_shading");
	bool weld_vertices=from->get_option("force/weld_vertices");
	float weld_tolerance=from->get_option("force/weld_tolerance");
	Vector<Vector3> vertices;
	Vector<Vector3> normals;
	Vector<Vector2> uvs;
	String name;

	Ref<SurfaceTool> surf_tool = memnew( SurfaceTool) ;
	surf_tool->begin(Mesh::PRIMITIVE_TRIANGLES);
	if (force_smooth)
		surf_tool->add_smooth_group(true);
	int has_index_data=false;

	while(true) {


		String l = f->get_line().strip_edges();

		if (l.begins_with("v ")) {
			//vertex
			Vector<String> v = l.split(" ",false);
			ERR_FAIL_COND_V(v.size()<4,ERR_INVALID_DATA);
			Vector3 vtx;
			vtx.x=v[1].to_float();
			vtx.y=v[2].to_float();
			vtx.z=v[3].to_float();
			vertices.push_back(vtx);
		} else  if (l.begins_with("vt ")) {
			//uv
			Vector<String> v = l.split(" ",false);
			ERR_FAIL_COND_V(v.size()<3,ERR_INVALID_DATA);
			Vector2 uv;
			uv.x=v[1].to_float();
			uv.y=1.0-v[2].to_float();
			uvs.push_back(uv);

		} else if (l.begins_with("vn ")) {
			//normal
			Vector<String> v = l.split(" ",false);
			ERR_FAIL_COND_V(v.size()<4,ERR_INVALID_DATA);
			Vector3 nrm;
			nrm.x=v[1].to_float();
			nrm.y=v[2].to_float();
			nrm.z=v[3].to_float();
			normals.push_back(nrm);
		} if (l.begins_with("f ")) {
				//vertex

			has_index_data=true;
			Vector<String> v = l.split(" ",false);
			ERR_FAIL_COND_V(v.size()<4,ERR_INVALID_DATA);

			//not very fast, could be sped up


			Vector<String> face[3];
			face[0] = v[1].split("/");
			face[1] = v[2].split("/");
			ERR_FAIL_COND_V(face[0].size()==0,ERR_PARSE_ERROR);
			ERR_FAIL_COND_V(face[0].size()!=face[1].size(),ERR_PARSE_ERROR);
			for(int i=2;i<v.size()-1;i++) {

				face[2] = v[i+1].split("/");
				ERR_FAIL_COND_V(face[0].size()!=face[2].size(),ERR_PARSE_ERROR);
				for(int j=0;j<3;j++) {

					int idx=j;

					if (!flip_faces && idx<2) {
						idx=1^idx;
					}


					if (face[idx].size()==3) {
						int norm = face[idx][2].to_int()-1;
						ERR_FAIL_INDEX_V(norm,normals.size(),ERR_PARSE_ERROR);
						surf_tool->add_normal(normals[norm]);
					}

					if (face[idx].size()>=2 && face[idx][1]!=String()) {

						int uv = face[idx][1].to_int()-1;
						ERR_FAIL_INDEX_V(uv,uvs.size(),ERR_PARSE_ERROR);
						surf_tool->add_uv(uvs[uv]);
					}

					int vtx = face[idx][0].to_int()-1;
					ERR_FAIL_INDEX_V(vtx,vertices.size(),ERR_PARSE_ERROR);

					Vector3 vertex = vertices[vtx];
					if (weld_vertices)
						vertex=vertex.snapped(weld_tolerance);
					surf_tool->add_vertex(vertex);
				}

				face[1]=face[2];
			}
		} else if (l.begins_with("s ") && !force_smooth) { //smoothing
			String what = l.substr(2,l.length()).strip_edges();
			if (what=="off")
				surf_tool->add_smooth_group(false);
			else
				surf_tool->add_smooth_group(true);

		} else if (l.begins_with("o ") || f->eof_reached()) { //new surface or done

			if (has_index_data) {
				//new object/surface
				if (generate_normals || force_smooth)
					surf_tool->generate_normals();
				if (uvs.size() && (normals.size() || generate_normals) && generate_tangents)
					surf_tool->generate_tangents();

				surf_tool->index();
				mesh = surf_tool->commit(mesh);
				if (name=="")
					name=vformat(TTR("Surface %d"),mesh->get_surface_count()-1);
				mesh->surface_set_name(mesh->get_surface_count()-1,name);
				name="";
				surf_tool->clear();
				surf_tool->begin(Mesh::PRIMITIVE_TRIANGLES);
				if (force_smooth)
					surf_tool->add_smooth_group(true);

				has_index_data=false;

				if (f->eof_reached())
					break;
			}

			if (l.begins_with("o ")) //name
				name=l.substr(2,l.length()).strip_edges();
		}
	}


	from->set_source_md5(0,FileAccess::get_md5(src_path));
	from->set_editor(get_name());
	mesh->set_import_metadata(from);

	//re-apply materials if exist
	for(int i=0;i<mesh->get_surface_count();i++) {

		String n = mesh->surface_get_name(i);
		if (name_map.has(n))
			mesh->surface_set_material(i,name_map[n]);
	}

	Error err = ResourceSaver::save(p_path,mesh);

	return err;
}
Beispiel #4
0
void EditorFileSystem::_scan_filesystem() {

	ERR_FAIL_COND(!scanning || new_filesystem);

	//read .fscache
	String cpath;

	sources_changed.clear();
	file_cache.clear();

	String project = ProjectSettings::get_singleton()->get_resource_path();

	String fscache = EditorSettings::get_singleton()->get_project_settings_path().plus_file("filesystem_cache2");
	FileAccess *f = FileAccess::open(fscache, FileAccess::READ);

	if (f) {
		//read the disk cache
		while (!f->eof_reached()) {

			String l = f->get_line().strip_edges();
			if (l == String())
				continue;

			if (l.begins_with("::")) {
				Vector<String> split = l.split("::");
				ERR_CONTINUE(split.size() != 3);
				String name = split[1];

				cpath = name;

			} else {
				Vector<String> split = l.split("::");
				ERR_CONTINUE(split.size() != 5);
				String name = split[0];
				String file;

				file = name;
				name = cpath.plus_file(name);

				FileCache fc;
				fc.type = split[1];
				fc.modification_time = split[2].to_int64();
				fc.import_modification_time = split[3].to_int64();

				String deps = split[4].strip_edges();
				if (deps.length()) {
					Vector<String> dp = deps.split("<>");
					for (int i = 0; i < dp.size(); i++) {
						String path = dp[i];
						fc.deps.push_back(path);
					}
				}

				file_cache[name] = fc;
			}
		}

		f->close();
		memdelete(f);
	}

	String update_cache = EditorSettings::get_singleton()->get_project_settings_path().plus_file("filesystem_update2");

	print_line("try to see fs update2");
	if (FileAccess::exists(update_cache)) {

		print_line("it exists");

		{
			FileAccessRef f = FileAccess::open(update_cache, FileAccess::READ);
			String l = f->get_line().strip_edges();
			while (l != String()) {

				print_line("erased cache for: " + l + " " + itos(file_cache.has(l)));
				file_cache.erase(l); //erase cache for this, so it gets updated
				l = f->get_line().strip_edges();
			}
		}

		DirAccessRef d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
		d->remove(update_cache); //bye bye update cache
	}

	EditorProgressBG scan_progress("efs", "ScanFS", 1000);

	ScanProgress sp;
	sp.low = 0;
	sp.hi = 1;
	sp.progress = &scan_progress;

	new_filesystem = memnew(EditorFileSystemDirectory);
	new_filesystem->parent = NULL;

	DirAccess *d = DirAccess::create(DirAccess::ACCESS_RESOURCES);
	d->change_dir("res://");
	_scan_new_dir(new_filesystem, d, sp);

	file_cache.clear(); //clear caches, no longer needed

	memdelete(d);

	//save back the findings
	//String fscache = EditorSettings::get_singleton()->get_project_settings_path().plus_file("file_cache");

	f = FileAccess::open(fscache, FileAccess::WRITE);
	_save_filesystem_cache(new_filesystem, f);
	f->close();
	memdelete(f);

	scanning = false;
}
Error EditorOBJImporter::_parse_material_library(const String &p_path, Map<String, Ref<SpatialMaterial> > &material_map, List<String> *r_missing_deps) {

	FileAccessRef f = FileAccess::open(p_path, FileAccess::READ);
	ERR_FAIL_COND_V(!f, ERR_CANT_OPEN);

	Ref<SpatialMaterial> current;
	String current_name;
	String base_path = p_path.get_base_dir();
	while (true) {

		String l = f->get_line().strip_edges();

		if (l.begins_with("newmtl ")) {
			//vertex

			current_name = l.replace("newmtl", "").strip_edges();
			current.instance();
			material_map[current_name] = current;
		} else if (l.begins_with("Ka ")) {
			//uv
			print_line("Warning: Ambient light for material '" + current_name + "' is ignored in PBR");

		} else if (l.begins_with("Kd ")) {
			//normal
			ERR_FAIL_COND_V(current.is_null(), ERR_FILE_CORRUPT);
			Vector<String> v = l.split(" ", false);
			ERR_FAIL_COND_V(v.size() < 4, ERR_INVALID_DATA);
			Color c = current->get_albedo();
			c.r = v[1].to_float();
			c.g = v[2].to_float();
			c.b = v[3].to_float();
			current->set_albedo(c);
		} else if (l.begins_with("Ks ")) {
			//normal
			ERR_FAIL_COND_V(current.is_null(), ERR_FILE_CORRUPT);
			Vector<String> v = l.split(" ", false);
			ERR_FAIL_COND_V(v.size() < 4, ERR_INVALID_DATA);
			float r = v[1].to_float();
			float g = v[2].to_float();
			float b = v[3].to_float();
			float metalness = MAX(r, MAX(g, b));
			current->set_metallic(metalness);
		} else if (l.begins_with("Ns ")) {
			//normal
			ERR_FAIL_COND_V(current.is_null(), ERR_FILE_CORRUPT);
			Vector<String> v = l.split(" ", false);
			ERR_FAIL_COND_V(v.size() != 2, ERR_INVALID_DATA);
			float s = v[1].to_float();
			current->set_metallic((1000.0 - s) / 1000.0);
		} else if (l.begins_with("d ")) {
			//normal
			ERR_FAIL_COND_V(current.is_null(), ERR_FILE_CORRUPT);
			Vector<String> v = l.split(" ", false);
			ERR_FAIL_COND_V(v.size() != 2, ERR_INVALID_DATA);
			float d = v[1].to_float();
			Color c = current->get_albedo();
			c.a = d;
			current->set_albedo(c);
			if (c.a < 0.99) {
				current->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true);
			}
		} else if (l.begins_with("Tr ")) {
			//normal
			ERR_FAIL_COND_V(current.is_null(), ERR_FILE_CORRUPT);
			Vector<String> v = l.split(" ", false);
			ERR_FAIL_COND_V(v.size() != 2, ERR_INVALID_DATA);
			float d = v[1].to_float();
			Color c = current->get_albedo();
			c.a = 1.0 - d;
			current->set_albedo(c);
			if (c.a < 0.99) {
				current->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true);
			}

		} else if (l.begins_with("map_Ka ")) {
			//uv
			print_line("Warning: Ambient light texture for material '" + current_name + "' is ignored in PBR");

		} else if (l.begins_with("map_Kd ")) {
			//normal
			ERR_FAIL_COND_V(current.is_null(), ERR_FILE_CORRUPT);

			String p = l.replace("map_Kd", "").replace("\\", "/").strip_edges();
			String path = base_path.plus_file(p);

			Ref<Texture> texture = ResourceLoader::load(path);

			if (texture.is_valid()) {
				current->set_texture(SpatialMaterial::TEXTURE_ALBEDO, texture);
			} else {
				r_missing_deps->push_back(path);
			}

		} else if (l.begins_with("map_Ks ")) {
			//normal
			ERR_FAIL_COND_V(current.is_null(), ERR_FILE_CORRUPT);

			String p = l.replace("map_Ks", "").replace("\\", "/").strip_edges();
			String path = base_path.plus_file(p);

			Ref<Texture> texture = ResourceLoader::load(path);

			if (texture.is_valid()) {
				current->set_texture(SpatialMaterial::TEXTURE_METALLIC, texture);
			} else {
				r_missing_deps->push_back(path);
			}

		} else if (l.begins_with("map_Ns ")) {
			//normal
			ERR_FAIL_COND_V(current.is_null(), ERR_FILE_CORRUPT);

			String p = l.replace("map_Ns", "").replace("\\", "/").strip_edges();
			String path = base_path.plus_file(p);

			Ref<Texture> texture = ResourceLoader::load(path);

			if (texture.is_valid()) {
				current->set_texture(SpatialMaterial::TEXTURE_ROUGHNESS, texture);
			} else {
				r_missing_deps->push_back(path);
			}
		} else if (l.begins_with("map_bump ")) {
			//normal
			ERR_FAIL_COND_V(current.is_null(), ERR_FILE_CORRUPT);

			String p = l.replace("map_bump", "").replace("\\", "/").strip_edges();
			String path = base_path.plus_file(p);

			Ref<Texture> texture = ResourceLoader::load(path);

			if (texture.is_valid()) {
				current->set_feature(SpatialMaterial::FEATURE_NORMAL_MAPPING, true);
				current->set_texture(SpatialMaterial::TEXTURE_NORMAL, texture);
			} else {
				r_missing_deps->push_back(path);
			}
		} else if (f->eof_reached()) {
			break;
		}
	}

	return OK;
}
Node *EditorOBJImporter::import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List<String> *r_missing_deps, Error *r_err) {

	FileAccessRef f = FileAccess::open(p_path, FileAccess::READ);

	if (r_err) {
		*r_err = ERR_CANT_OPEN;
	}

	ERR_FAIL_COND_V(!f, NULL);

	if (r_err) {
		*r_err = OK;
	}

	Spatial *scene = memnew(Spatial);

	Ref<ArrayMesh> mesh;
	mesh.instance();

	Map<String, Ref<Material> > name_map;

	bool generate_tangents = p_flags & IMPORT_GENERATE_TANGENT_ARRAYS;
	bool flip_faces = false;
	//bool flip_faces = p_options["force/flip_faces"];
	//bool force_smooth = p_options["force/smooth_shading"];
	//bool weld_vertices = p_options["force/weld_vertices"];
	//float weld_tolerance = p_options["force/weld_tolerance"];

	Vector<Vector3> vertices;
	Vector<Vector3> normals;
	Vector<Vector2> uvs;
	String name;

	Map<String, Map<String, Ref<SpatialMaterial> > > material_map;

	Ref<SurfaceTool> surf_tool = memnew(SurfaceTool);
	surf_tool->begin(Mesh::PRIMITIVE_TRIANGLES);

	String current_material_library;
	String current_material;
	String current_group;

	while (true) {

		String l = f->get_line().strip_edges();

		if (l.begins_with("v ")) {
			//vertex
			Vector<String> v = l.split(" ", false);
			ERR_FAIL_COND_V(v.size() < 4, NULL);
			Vector3 vtx;
			vtx.x = v[1].to_float();
			vtx.y = v[2].to_float();
			vtx.z = v[3].to_float();
			vertices.push_back(vtx);
		} else if (l.begins_with("vt ")) {
			//uv
			Vector<String> v = l.split(" ", false);
			ERR_FAIL_COND_V(v.size() < 3, NULL);
			Vector2 uv;
			uv.x = v[1].to_float();
			uv.y = 1.0 - v[2].to_float();
			uvs.push_back(uv);

		} else if (l.begins_with("vn ")) {
			//normal
			Vector<String> v = l.split(" ", false);
			ERR_FAIL_COND_V(v.size() < 4, NULL);
			Vector3 nrm;
			nrm.x = v[1].to_float();
			nrm.y = v[2].to_float();
			nrm.z = v[3].to_float();
			normals.push_back(nrm);
		} else if (l.begins_with("f ")) {
			//vertex

			Vector<String> v = l.split(" ", false);
			ERR_FAIL_COND_V(v.size() < 4, NULL);

			//not very fast, could be sped up

			Vector<String> face[3];
			face[0] = v[1].split("/");
			face[1] = v[2].split("/");
			ERR_FAIL_COND_V(face[0].size() == 0, NULL);
			ERR_FAIL_COND_V(face[0].size() != face[1].size(), NULL);
			for (int i = 2; i < v.size() - 1; i++) {

				face[2] = v[i + 1].split("/");
				ERR_FAIL_COND_V(face[0].size() != face[2].size(), NULL);
				for (int j = 0; j < 3; j++) {

					int idx = j;

					if (!flip_faces && idx < 2) {
						idx = 1 ^ idx;
					}

					if (face[idx].size() == 3) {
						int norm = face[idx][2].to_int() - 1;
						if (norm < 0)
							norm += normals.size() + 1;
						ERR_FAIL_INDEX_V(norm, normals.size(), NULL);
						surf_tool->add_normal(normals[norm]);
					}

					if (face[idx].size() >= 2 && face[idx][1] != String()) {
						int uv = face[idx][1].to_int() - 1;
						if (uv < 0)
							uv += uvs.size() + 1;
						ERR_FAIL_INDEX_V(uv, uvs.size(), NULL);
						surf_tool->add_uv(uvs[uv]);
					}

					int vtx = face[idx][0].to_int() - 1;
					if (vtx < 0)
						vtx += vertices.size() + 1;
					ERR_FAIL_INDEX_V(vtx, vertices.size(), NULL);

					Vector3 vertex = vertices[vtx];
					//if (weld_vertices)
					//	vertex.snap(Vector3(weld_tolerance, weld_tolerance, weld_tolerance));
					surf_tool->add_vertex(vertex);
				}

				face[1] = face[2];
			}
		} else if (l.begins_with("s ")) { //smoothing
			String what = l.substr(2, l.length()).strip_edges();
			if (what == "off")
				surf_tool->add_smooth_group(false);
			else
				surf_tool->add_smooth_group(true);
		} else if (/*l.begins_with("g ") ||*/ l.begins_with("usemtl ") || (l.begins_with("o ") || f->eof_reached())) { //commit group to mesh
			//groups are too annoying
			if (surf_tool->get_vertex_array().size()) {
				//another group going on, commit it
				if (normals.size() == 0) {
					surf_tool->generate_normals();
				}

				if (generate_tangents && uvs.size()) {
					surf_tool->generate_tangents();
				}

				surf_tool->index();

				print_line("current material library " + current_material_library + " has " + itos(material_map.has(current_material_library)));
				print_line("current material " + current_material + " has " + itos(material_map.has(current_material_library) && material_map[current_material_library].has(current_material)));

				if (material_map.has(current_material_library) && material_map[current_material_library].has(current_material)) {
					surf_tool->set_material(material_map[current_material_library][current_material]);
				}

				mesh = surf_tool->commit(mesh);

				if (current_material != String()) {
					mesh->surface_set_name(mesh->get_surface_count() - 1, current_material.get_basename());
				} else if (current_group != String()) {
					mesh->surface_set_name(mesh->get_surface_count() - 1, current_group);
				}

				print_line("Added surface :" + mesh->surface_get_name(mesh->get_surface_count() - 1));
				surf_tool->clear();
				surf_tool->begin(Mesh::PRIMITIVE_TRIANGLES);
			}

			if (l.begins_with("o ") || f->eof_reached()) {

				MeshInstance *mi = memnew(MeshInstance);
				mi->set_name(name);
				mi->set_mesh(mesh);

				scene->add_child(mi);
				mi->set_owner(scene);

				mesh.instance();
				current_group = "";
				current_material = "";
			}

			if (f->eof_reached()) {
				break;
			}

			if (l.begins_with("o ")) {
				name = l.substr(2, l.length()).strip_edges();
			}

			if (l.begins_with("usemtl ")) {

				current_material = l.replace("usemtl", "").strip_edges();
			}

			if (l.begins_with("g ")) {

				current_group = l.substr(2, l.length()).strip_edges();
			}

		} else if (l.begins_with("mtllib ")) { //parse material

			current_material_library = l.replace("mtllib", "").strip_edges();
			if (!material_map.has(current_material_library)) {
				Map<String, Ref<SpatialMaterial> > lib;
				Error err = _parse_material_library(current_material_library, lib, r_missing_deps);
				if (err == ERR_CANT_OPEN) {
					String dir = p_path.get_base_dir();
					err = _parse_material_library(dir.plus_file(current_material_library), lib, r_missing_deps);
				}
				if (err == OK) {
					material_map[current_material_library] = lib;
				}
			}
		}
	}

	/*
	TODO, check existing materials and merge?
	//re-apply materials if exist
	for(int i=0;i<mesh->get_surface_count();i++) {

		String n = mesh->surface_get_name(i);
		if (name_map.has(n))
			mesh->surface_set_material(i,name_map[n]);
	}
*/

	return scene;
}
Vector<uint8_t> EditorTextureImportPlugin::custom_export(const String& p_path, const Ref<EditorExportPlatform> &p_platform) {


	Ref<ResourceImportMetadata> rimd = ResourceLoader::load_import_metadata(p_path);

	if (rimd.is_null()) {

		StringName group = EditorImportExport::get_singleton()->image_get_export_group(p_path);

		if (group!=StringName()) {
			//handled by export group
			rimd = Ref<ResourceImportMetadata>( memnew( ResourceImportMetadata ) );

			int group_format=0;
			float group_lossy_quality=EditorImportExport::get_singleton()->image_export_group_get_lossy_quality(group);
			int group_shrink=EditorImportExport::get_singleton()->image_export_group_get_shrink(group);
			group_shrink*=EditorImportExport::get_singleton()->get_export_image_shrink();

			switch(EditorImportExport::get_singleton()->image_export_group_get_image_action(group)) {
				case EditorImportExport::IMAGE_ACTION_NONE: {

					switch(EditorImportExport::get_singleton()->get_export_image_action()) {
						case EditorImportExport::IMAGE_ACTION_NONE: {

							group_format=EditorTextureImportPlugin::IMAGE_FORMAT_COMPRESS_DISK_LOSSLESS; //?

						} break; //use default
						case EditorImportExport::IMAGE_ACTION_COMPRESS_DISK: {
							group_format=EditorTextureImportPlugin::IMAGE_FORMAT_COMPRESS_DISK_LOSSY;
						} break; //use default
						case EditorImportExport::IMAGE_ACTION_COMPRESS_RAM: {
							group_format=EditorTextureImportPlugin::IMAGE_FORMAT_COMPRESS_RAM;
						} break; //use default
					}

					group_lossy_quality=EditorImportExport::get_singleton()->get_export_image_quality();

				} break; //use default
				case EditorImportExport::IMAGE_ACTION_COMPRESS_DISK: {
					group_format=EditorTextureImportPlugin::IMAGE_FORMAT_COMPRESS_DISK_LOSSY;
				} break; //use default
				case EditorImportExport::IMAGE_ACTION_COMPRESS_RAM: {
					group_format=EditorTextureImportPlugin::IMAGE_FORMAT_COMPRESS_RAM;
				} break; //use default
			}


			int flags=0;

			if (Globals::get_singleton()->get("texture_import/filter"))
				flags|=IMAGE_FLAG_FILTER;
			if (!Globals::get_singleton()->get("texture_import/gen_mipmaps"))
				flags|=IMAGE_FLAG_NO_MIPMAPS;
			if (!Globals::get_singleton()->get("texture_import/repeat"))
				flags|=IMAGE_FLAG_REPEAT;

			flags|=IMAGE_FLAG_FIX_BORDER_ALPHA;

			print_line("group format"+itos(group_format));
			rimd->set_option("format",group_format);
			rimd->set_option("flags",flags);
			rimd->set_option("quality",group_lossy_quality);
			rimd->set_option("atlas",false);
			rimd->set_option("shrink",group_shrink);
			rimd->add_source(EditorImportPlugin::validate_source_path(p_path));

		} else if (EditorImportExport::get_singleton()->get_image_formats().has(p_path.extension().to_lower()) && EditorImportExport::get_singleton()->get_export_image_action()!=EditorImportExport::IMAGE_ACTION_NONE) {
			//handled by general image export settings

			rimd = Ref<ResourceImportMetadata>( memnew( ResourceImportMetadata ) );

			switch(EditorImportExport::get_singleton()->get_export_image_action()) {
				case EditorImportExport::IMAGE_ACTION_COMPRESS_DISK: rimd->set_option("format",IMAGE_FORMAT_COMPRESS_DISK_LOSSY); break;
				case EditorImportExport::IMAGE_ACTION_COMPRESS_RAM: rimd->set_option("format",IMAGE_FORMAT_COMPRESS_RAM); break;
			}

			int flags=0;

			if (Globals::get_singleton()->get("texture_import/filter"))
				flags|=IMAGE_FLAG_FILTER;
			if (!Globals::get_singleton()->get("texture_import/gen_mipmaps"))
				flags|=IMAGE_FLAG_NO_MIPMAPS;
			if (!Globals::get_singleton()->get("texture_import/repeat"))
				flags|=IMAGE_FLAG_REPEAT;

			flags|=IMAGE_FLAG_FIX_BORDER_ALPHA;

			rimd->set_option("shrink",EditorImportExport::get_singleton()->get_export_image_shrink());
			rimd->set_option("flags",flags);
			rimd->set_option("quality",EditorImportExport::get_singleton()->get_export_image_quality());
			rimd->set_option("atlas",false);
			rimd->add_source(EditorImportPlugin::validate_source_path(p_path));

		} else {
			return Vector<uint8_t>();
		}
	}

	int fmt = rimd->get_option("format");

	if (fmt!=IMAGE_FORMAT_COMPRESS_RAM && fmt!=IMAGE_FORMAT_COMPRESS_DISK_LOSSY)  {
		print_line("no compress ram or lossy");
		return Vector<uint8_t>(); //pointless to do anything, since no need to reconvert
	}

	uint32_t flags = rimd->get_option("flags");
	uint8_t shrink = rimd->has_option("shrink") ? rimd->get_option("shrink"): Variant(1);
	uint8_t format = rimd->get_option("format");
	uint8_t comp = (format==EditorTextureImportPlugin::IMAGE_FORMAT_COMPRESS_RAM)?uint8_t(p_platform->get_image_compression()):uint8_t(255);

	MD5_CTX ctx;
	uint8_t f4[4];
	encode_uint32(flags,&f4[0]);
	MD5Init(&ctx);
	String gp = Globals::get_singleton()->globalize_path(p_path);
	CharString cs = gp.utf8();
	MD5Update(&ctx,(unsigned char*)cs.get_data(),cs.length());
	MD5Update(&ctx,f4,4);
	MD5Update(&ctx,&format,1);
	MD5Update(&ctx,&comp,1);
	MD5Update(&ctx,&shrink,1);
	MD5Final(&ctx);

	uint64_t sd=0;
	String smd5;

	String md5 = String::md5(ctx.digest);

	String tmp_path = EditorSettings::get_singleton()->get_settings_path().plus_file("tmp/");

	bool valid=false;
	{
		//if existing, make sure it's valid
		FileAccessRef f = FileAccess::open(tmp_path+"imgexp-"+md5+".txt",FileAccess::READ);
		if (f) {

			uint64_t d = f->get_line().strip_edges().to_int64();
			sd = FileAccess::get_modified_time(p_path);

			if (d==sd) {
				valid=true;
			} else {
				String cmd5 = f->get_line().strip_edges();
				smd5 = FileAccess::get_md5(p_path);
				if (cmd5==smd5) {
					valid=true;
				}
			}


		}
	}

	if (!valid) {
		//cache failed, convert
		Error err = import2(tmp_path+"imgexp-"+md5+".tex",rimd,p_platform->get_image_compression(),true);
		ERR_FAIL_COND_V(err!=OK,Vector<uint8_t>());
		FileAccessRef f = FileAccess::open(tmp_path+"imgexp-"+md5+".txt",FileAccess::WRITE);

		if (sd==0)
			sd = FileAccess::get_modified_time(p_path);
		if (smd5==String())
			smd5 = FileAccess::get_md5(p_path);

		f->store_line(String::num(sd));
		f->store_line(smd5);
		f->store_line(gp); //source path for reference
	}


	Vector<uint8_t> ret;
	FileAccessRef f = FileAccess::open(tmp_path+"imgexp-"+md5+".tex",FileAccess::READ);
	ERR_FAIL_COND_V(!f,ret);

	ret.resize(f->get_len());
	f->get_buffer(ret.ptr(),ret.size());

	return ret;
}