1 /*************************************************************************/
2 /*  editor_mesh_import_plugin.cpp                                        */
3 /*************************************************************************/
4 /*                       This file is part of:                           */
5 /*                           GODOT ENGINE                                */
6 /*                      https://godotengine.org                          */
7 /*************************************************************************/
8 /* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur.                 */
9 /* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md)    */
10 /*                                                                       */
11 /* Permission is hereby granted, free of charge, to any person obtaining */
12 /* a copy of this software and associated documentation files (the       */
13 /* "Software"), to deal in the Software without restriction, including   */
14 /* without limitation the rights to use, copy, modify, merge, publish,   */
15 /* distribute, sublicense, and/or sell copies of the Software, and to    */
16 /* permit persons to whom the Software is furnished to do so, subject to */
17 /* the following conditions:                                             */
18 /*                                                                       */
19 /* The above copyright notice and this permission notice shall be        */
20 /* included in all copies or substantial portions of the Software.       */
21 /*                                                                       */
22 /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
23 /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
24 /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
25 /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
26 /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
27 /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
28 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
29 /*************************************************************************/
30 #include "editor_mesh_import_plugin.h"
31 
32 #include "editor/editor_dir_dialog.h"
33 #include "editor/editor_file_dialog.h"
34 #include "editor/editor_node.h"
35 #include "editor/property_editor.h"
36 #include "io/marshalls.h"
37 #include "io/resource_saver.h"
38 #include "os/file_access.h"
39 #include "scene/resources/sample.h"
40 #include "scene/resources/surface_tool.h"
41 
42 class _EditorMeshImportOptions : public Object {
43 
44 	OBJ_TYPE(_EditorMeshImportOptions, Object);
45 
46 public:
47 	bool generate_tangents;
48 	bool generate_normals;
49 	bool flip_faces;
50 	bool smooth_shading;
51 	bool weld_vertices;
52 	bool import_material;
53 	bool import_textures;
54 	float weld_tolerance;
55 
_set(const StringName & p_name,const Variant & p_value)56 	bool _set(const StringName &p_name, const Variant &p_value) {
57 
58 		String n = p_name;
59 		if (n == "generate/tangents")
60 			generate_tangents = p_value;
61 		else if (n == "generate/normals")
62 			generate_normals = p_value;
63 		else if (n == "import/materials")
64 			import_material = p_value;
65 		else if (n == "import/textures")
66 			import_textures = p_value;
67 		else if (n == "force/flip_faces")
68 			flip_faces = p_value;
69 		else if (n == "force/smooth_shading")
70 			smooth_shading = p_value;
71 		else if (n == "force/weld_vertices")
72 			weld_vertices = p_value;
73 		else if (n == "force/weld_tolerance")
74 			weld_tolerance = p_value;
75 		else
76 			return false;
77 
78 		return true;
79 	}
80 
_get(const StringName & p_name,Variant & r_ret) const81 	bool _get(const StringName &p_name, Variant &r_ret) const {
82 
83 		String n = p_name;
84 		if (n == "generate/tangents")
85 			r_ret = generate_tangents;
86 		else if (n == "generate/normals")
87 			r_ret = generate_normals;
88 		else if (n == "import/materials")
89 			r_ret = import_material;
90 		else if (n == "import/textures")
91 			r_ret = import_textures;
92 		else if (n == "force/flip_faces")
93 			r_ret = flip_faces;
94 		else if (n == "force/smooth_shading")
95 			r_ret = smooth_shading;
96 		else if (n == "force/weld_vertices")
97 			r_ret = weld_vertices;
98 		else if (n == "force/weld_tolerance")
99 			r_ret = weld_tolerance;
100 		else
101 			return false;
102 
103 		return true;
104 	}
_get_property_list(List<PropertyInfo> * p_list) const105 	void _get_property_list(List<PropertyInfo> *p_list) const {
106 
107 		p_list->push_back(PropertyInfo(Variant::BOOL, "generate/tangents"));
108 		p_list->push_back(PropertyInfo(Variant::BOOL, "generate/normals"));
109 		//not for nowp
110 		//p_list->push_back(PropertyInfo(Variant::BOOL,"import/materials"));
111 		//p_list->push_back(PropertyInfo(Variant::BOOL,"import/textures"));
112 		p_list->push_back(PropertyInfo(Variant::BOOL, "force/flip_faces"));
113 		p_list->push_back(PropertyInfo(Variant::BOOL, "force/smooth_shading"));
114 		p_list->push_back(PropertyInfo(Variant::BOOL, "force/weld_vertices"));
115 		p_list->push_back(PropertyInfo(Variant::REAL, "force/weld_tolerance", PROPERTY_HINT_RANGE, "0.00001,16,0.00001"));
116 		//p_list->push_back(PropertyInfo(Variant::BOOL,"compress/enable"));
117 		//p_list->push_back(PropertyInfo(Variant::INT,"compress/bitrate",PROPERTY_HINT_ENUM,"64,96,128,192"));
118 	}
119 
_bind_methods()120 	static void _bind_methods() {
121 
122 		ADD_SIGNAL(MethodInfo("changed"));
123 	}
124 
_EditorMeshImportOptions()125 	_EditorMeshImportOptions() {
126 
127 		generate_tangents = true;
128 		generate_normals = false;
129 		flip_faces = false;
130 		smooth_shading = false;
131 		weld_vertices = true;
132 		weld_tolerance = 0.0001;
133 		import_material = false;
134 		import_textures = false;
135 	}
136 };
137 
138 class EditorMeshImportDialog : public ConfirmationDialog {
139 
140 	OBJ_TYPE(EditorMeshImportDialog, ConfirmationDialog);
141 
142 	EditorMeshImportPlugin *plugin;
143 
144 	LineEdit *import_path;
145 	LineEdit *save_path;
146 	EditorFileDialog *file_select;
147 	EditorDirDialog *save_select;
148 	AcceptDialog *error_dialog;
149 	PropertyEditor *option_editor;
150 
151 	_EditorMeshImportOptions *options;
152 
153 public:
_choose_files(const Vector<String> & p_path)154 	void _choose_files(const Vector<String> &p_path) {
155 
156 		String files;
157 		for (int i = 0; i < p_path.size(); i++) {
158 
159 			if (i > 0)
160 				files += ",";
161 			files += p_path[i];
162 		}
163 		/*
164 		if (p_path.size()) {
165 			String srctex=p_path[0];
166 			String ipath = EditorImportDB::get_singleton()->find_source_path(srctex);
167 
168 			if (ipath!="")
169 				save_path->set_text(ipath.get_base_dir());
170 		}*/
171 		import_path->set_text(files);
172 	}
_choose_save_dir(const String & p_path)173 	void _choose_save_dir(const String &p_path) {
174 
175 		save_path->set_text(p_path);
176 	}
177 
_browse()178 	void _browse() {
179 
180 		file_select->popup_centered_ratio();
181 	}
182 
_browse_target()183 	void _browse_target() {
184 
185 		save_select->popup_centered_ratio();
186 	}
187 
popup_import(const String & p_path)188 	void popup_import(const String &p_path) {
189 
190 		popup_centered(Size2(400, 400) * EDSCALE);
191 
192 		if (p_path != "") {
193 
194 			Ref<ResourceImportMetadata> rimd = ResourceLoader::load_import_metadata(p_path);
195 			ERR_FAIL_COND(!rimd.is_valid());
196 
197 			save_path->set_text(p_path.get_base_dir());
198 			List<String> opts;
199 			rimd->get_options(&opts);
200 			for (List<String>::Element *E = opts.front(); E; E = E->next()) {
201 
202 				options->_set(E->get(), rimd->get_option(E->get()));
203 			}
204 
205 			String src = "";
206 			for (int i = 0; i < rimd->get_source_count(); i++) {
207 				if (i > 0)
208 					src += ",";
209 				src += EditorImportPlugin::expand_source_path(rimd->get_source_path(i));
210 			}
211 			import_path->set_text(src);
212 		}
213 	}
214 
_import()215 	void _import() {
216 
217 		Vector<String> meshes = import_path->get_text().split(",");
218 		if (meshes.size() == 0) {
219 			error_dialog->set_text(TTR("No meshes to import!"));
220 			error_dialog->popup_centered_minsize();
221 			return;
222 		}
223 
224 		String dst = save_path->get_text();
225 		if (dst == "") {
226 			error_dialog->set_text(TTR("Save path is empty!"));
227 			error_dialog->popup_centered_minsize();
228 			return;
229 		}
230 
231 		for (int i = 0; i < meshes.size(); i++) {
232 
233 			Ref<ResourceImportMetadata> imd = memnew(ResourceImportMetadata);
234 
235 			List<PropertyInfo> pl;
236 			options->_get_property_list(&pl);
237 			for (List<PropertyInfo>::Element *E = pl.front(); E; E = E->next()) {
238 
239 				Variant v;
240 				String opt = E->get().name;
241 				options->_get(opt, v);
242 				imd->set_option(opt, v);
243 			}
244 
245 			imd->add_source(EditorImportPlugin::validate_source_path(meshes[i]));
246 
247 			String file_path = dst.plus_file(meshes[i].get_file().basename() + ".msh");
248 
249 			plugin->import(file_path, imd);
250 		}
251 
252 		hide();
253 	}
254 
_notification(int p_what)255 	void _notification(int p_what) {
256 
257 		if (p_what == NOTIFICATION_ENTER_TREE) {
258 
259 			option_editor->edit(options);
260 		}
261 	}
262 
_bind_methods()263 	static void _bind_methods() {
264 
265 		ObjectTypeDB::bind_method("_choose_files", &EditorMeshImportDialog::_choose_files);
266 		ObjectTypeDB::bind_method("_choose_save_dir", &EditorMeshImportDialog::_choose_save_dir);
267 		ObjectTypeDB::bind_method("_import", &EditorMeshImportDialog::_import);
268 		ObjectTypeDB::bind_method("_browse", &EditorMeshImportDialog::_browse);
269 		ObjectTypeDB::bind_method("_browse_target", &EditorMeshImportDialog::_browse_target);
270 	}
271 
EditorMeshImportDialog(EditorMeshImportPlugin * p_plugin)272 	EditorMeshImportDialog(EditorMeshImportPlugin *p_plugin) {
273 
274 		plugin = p_plugin;
275 
276 		set_title(TTR("Single Mesh Import"));
277 		set_hide_on_ok(false);
278 
279 		VBoxContainer *vbc = memnew(VBoxContainer);
280 		add_child(vbc);
281 		set_child_rect(vbc);
282 
283 		HBoxContainer *hbc = memnew(HBoxContainer);
284 		vbc->add_margin_child(TTR("Source Mesh(es):"), hbc);
285 
286 		import_path = memnew(LineEdit);
287 		import_path->set_h_size_flags(SIZE_EXPAND_FILL);
288 		hbc->add_child(import_path);
289 
290 		Button *import_choose = memnew(Button);
291 		import_choose->set_text(" .. ");
292 		hbc->add_child(import_choose);
293 
294 		import_choose->connect("pressed", this, "_browse");
295 
296 		hbc = memnew(HBoxContainer);
297 		vbc->add_margin_child(TTR("Target Path:"), hbc);
298 
299 		save_path = memnew(LineEdit);
300 		save_path->set_h_size_flags(SIZE_EXPAND_FILL);
301 		hbc->add_child(save_path);
302 
303 		Button *save_choose = memnew(Button);
304 		save_choose->set_text(" .. ");
305 		hbc->add_child(save_choose);
306 
307 		save_choose->connect("pressed", this, "_browse_target");
308 
309 		file_select = memnew(EditorFileDialog);
310 		file_select->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
311 		file_select->set_mode(EditorFileDialog::MODE_OPEN_FILES);
312 		file_select->add_filter("*.obj ; Wavefront OBJ");
313 		add_child(file_select);
314 		file_select->connect("files_selected", this, "_choose_files");
315 
316 		save_select = memnew(EditorDirDialog);
317 		add_child(save_select);
318 		save_select->connect("dir_selected", this, "_choose_save_dir");
319 
320 		get_ok()->connect("pressed", this, "_import");
321 		get_ok()->set_text(TTR("Import"));
322 
323 		error_dialog = memnew(AcceptDialog);
324 		add_child(error_dialog);
325 
326 		options = memnew(_EditorMeshImportOptions);
327 
328 		option_editor = memnew(PropertyEditor);
329 		option_editor->hide_top_label();
330 		vbc->add_margin_child(TTR("Options:"), option_editor, true);
331 	}
332 
~EditorMeshImportDialog()333 	~EditorMeshImportDialog() {
334 		memdelete(options);
335 	}
336 };
337 
get_name() const338 String EditorMeshImportPlugin::get_name() const {
339 
340 	return "mesh";
341 }
get_visible_name() const342 String EditorMeshImportPlugin::get_visible_name() const {
343 
344 	return TTR("Mesh");
345 }
import_dialog(const String & p_from)346 void EditorMeshImportPlugin::import_dialog(const String &p_from) {
347 
348 	dialog->popup_import(p_from);
349 }
import(const String & p_path,const Ref<ResourceImportMetadata> & p_from)350 Error EditorMeshImportPlugin::import(const String &p_path, const Ref<ResourceImportMetadata> &p_from) {
351 
352 	ERR_FAIL_COND_V(p_from->get_source_count() != 1, ERR_INVALID_PARAMETER);
353 
354 	Ref<ResourceImportMetadata> from = p_from;
355 
356 	String src_path = EditorImportPlugin::expand_source_path(from->get_source_path(0));
357 	FileAccessRef f = FileAccess::open(src_path, FileAccess::READ);
358 	ERR_FAIL_COND_V(!f, ERR_CANT_OPEN);
359 
360 	Ref<Mesh> mesh;
361 	Map<String, Ref<Material> > name_map;
362 
363 	if (FileAccess::exists(p_path)) {
364 		mesh = ResourceLoader::load(p_path, "Mesh");
365 		if (mesh.is_valid()) {
366 			for (int i = 0; i < mesh->get_surface_count(); i++) {
367 
368 				if (!mesh->surface_get_material(i).is_valid())
369 					continue;
370 				String name;
371 				if (mesh->surface_get_name(i) != "")
372 					name = mesh->surface_get_name(i);
373 				else
374 					name = vformat(TTR("Surface %d"), i + 1);
375 
376 				name_map[name] = mesh->surface_get_material(i);
377 			}
378 
379 			while (mesh->get_surface_count()) {
380 				mesh->surface_remove(0);
381 			}
382 		}
383 	}
384 
385 	if (!mesh.is_valid())
386 		mesh = Ref<Mesh>(memnew(Mesh));
387 
388 	bool generate_normals = from->get_option("generate/normals");
389 	bool generate_tangents = from->get_option("generate/tangents");
390 	bool flip_faces = from->get_option("force/flip_faces");
391 	bool force_smooth = from->get_option("force/smooth_shading");
392 	bool weld_vertices = from->get_option("force/weld_vertices");
393 	float weld_tolerance = from->get_option("force/weld_tolerance");
394 	Vector<Vector3> vertices;
395 	Vector<Vector3> normals;
396 	Vector<Vector2> uvs;
397 	String name;
398 
399 	Ref<SurfaceTool> surf_tool = memnew(SurfaceTool);
400 	surf_tool->begin(Mesh::PRIMITIVE_TRIANGLES);
401 	if (force_smooth)
402 		surf_tool->add_smooth_group(true);
403 	int has_index_data = false;
404 
405 	while (true) {
406 
407 		String l = f->get_line().strip_edges();
408 
409 		if (l.begins_with("v ")) {
410 			//vertex
411 			Vector<String> v = l.split(" ", false);
412 			ERR_FAIL_COND_V(v.size() < 4, ERR_INVALID_DATA);
413 			Vector3 vtx;
414 			vtx.x = v[1].to_float();
415 			vtx.y = v[2].to_float();
416 			vtx.z = v[3].to_float();
417 			vertices.push_back(vtx);
418 		} else if (l.begins_with("vt ")) {
419 			//uv
420 			Vector<String> v = l.split(" ", false);
421 			ERR_FAIL_COND_V(v.size() < 3, ERR_INVALID_DATA);
422 			Vector2 uv;
423 			uv.x = v[1].to_float();
424 			uv.y = 1.0 - v[2].to_float();
425 			uvs.push_back(uv);
426 
427 		} else if (l.begins_with("vn ")) {
428 			//normal
429 			Vector<String> v = l.split(" ", false);
430 			ERR_FAIL_COND_V(v.size() < 4, ERR_INVALID_DATA);
431 			Vector3 nrm;
432 			nrm.x = v[1].to_float();
433 			nrm.y = v[2].to_float();
434 			nrm.z = v[3].to_float();
435 			normals.push_back(nrm);
436 		}
437 		if (l.begins_with("f ")) {
438 			//vertex
439 
440 			has_index_data = true;
441 			Vector<String> v = l.split(" ", false);
442 			ERR_FAIL_COND_V(v.size() < 4, ERR_INVALID_DATA);
443 
444 			//not very fast, could be sped up
445 
446 			Vector<String> face[3];
447 			face[0] = v[1].split("/");
448 			face[1] = v[2].split("/");
449 			ERR_FAIL_COND_V(face[0].size() == 0, ERR_PARSE_ERROR);
450 			ERR_FAIL_COND_V(face[0].size() != face[1].size(), ERR_PARSE_ERROR);
451 			for (int i = 2; i < v.size() - 1; i++) {
452 
453 				face[2] = v[i + 1].split("/");
454 				ERR_FAIL_COND_V(face[0].size() != face[2].size(), ERR_PARSE_ERROR);
455 				for (int j = 0; j < 3; j++) {
456 
457 					int idx = j;
458 
459 					if (!flip_faces && idx < 2) {
460 						idx = 1 ^ idx;
461 					}
462 
463 					if (face[idx].size() == 3) {
464 						int norm = face[idx][2].to_int() - 1;
465 						ERR_FAIL_INDEX_V(norm, normals.size(), ERR_PARSE_ERROR);
466 						surf_tool->add_normal(normals[norm]);
467 					}
468 
469 					if (face[idx].size() >= 2 && face[idx][1] != String()) {
470 
471 						int uv = face[idx][1].to_int() - 1;
472 						ERR_FAIL_INDEX_V(uv, uvs.size(), ERR_PARSE_ERROR);
473 						surf_tool->add_uv(uvs[uv]);
474 					}
475 
476 					int vtx = face[idx][0].to_int() - 1;
477 					ERR_FAIL_INDEX_V(vtx, vertices.size(), ERR_PARSE_ERROR);
478 
479 					Vector3 vertex = vertices[vtx];
480 					if (weld_vertices)
481 						vertex = vertex.snapped(weld_tolerance);
482 					surf_tool->add_vertex(vertex);
483 				}
484 
485 				face[1] = face[2];
486 			}
487 		} else if (l.begins_with("s ") && !force_smooth) { //smoothing
488 			String what = l.substr(2, l.length()).strip_edges();
489 			if (what == "off")
490 				surf_tool->add_smooth_group(false);
491 			else
492 				surf_tool->add_smooth_group(true);
493 
494 		} else if (l.begins_with("o ") || f->eof_reached()) { //new surface or done
495 
496 			if (has_index_data) {
497 				//new object/surface
498 				if (generate_normals || force_smooth)
499 					surf_tool->generate_normals();
500 				if (uvs.size() && (normals.size() || generate_normals) && generate_tangents)
501 					surf_tool->generate_tangents();
502 
503 				surf_tool->index();
504 				mesh = surf_tool->commit(mesh);
505 				if (name == "")
506 					name = vformat(TTR("Surface %d"), mesh->get_surface_count() - 1);
507 				mesh->surface_set_name(mesh->get_surface_count() - 1, name);
508 				name = "";
509 				surf_tool->clear();
510 				surf_tool->begin(Mesh::PRIMITIVE_TRIANGLES);
511 				if (force_smooth)
512 					surf_tool->add_smooth_group(true);
513 
514 				has_index_data = false;
515 			}
516 
517 			if (l.begins_with("o ")) //name
518 				name = l.substr(2, l.length()).strip_edges();
519 
520 			if (f->eof_reached())
521 				break;
522 		}
523 	}
524 
525 	from->set_source_md5(0, FileAccess::get_md5(src_path));
526 	from->set_editor(get_name());
527 	mesh->set_import_metadata(from);
528 
529 	//re-apply materials if exist
530 	for (int i = 0; i < mesh->get_surface_count(); i++) {
531 
532 		String n = mesh->surface_get_name(i);
533 		if (name_map.has(n))
534 			mesh->surface_set_material(i, name_map[n]);
535 	}
536 
537 	Error err = ResourceSaver::save(p_path, mesh);
538 
539 	return err;
540 }
541 
import_from_drop(const Vector<String> & p_drop,const String & p_dest_path)542 void EditorMeshImportPlugin::import_from_drop(const Vector<String> &p_drop, const String &p_dest_path) {
543 
544 	Vector<String> files;
545 	for (int i = 0; i < p_drop.size(); i++) {
546 		String ext = p_drop[i].extension().to_lower();
547 		String file = p_drop[i].get_file();
548 		if (ext == "obj") {
549 
550 			files.push_back(p_drop[i]);
551 		}
552 	}
553 
554 	if (files.size()) {
555 		import_dialog();
556 		dialog->_choose_files(files);
557 		dialog->_choose_save_dir(p_dest_path);
558 	}
559 }
560 
EditorMeshImportPlugin(EditorNode * p_editor)561 EditorMeshImportPlugin::EditorMeshImportPlugin(EditorNode *p_editor) {
562 
563 	dialog = memnew(EditorMeshImportDialog(this));
564 	p_editor->get_gui_base()->add_child(dialog);
565 }
566