1 /*************************************************************************/
2 /*  multimesh_editor_plugin.cpp                                          */
3 /*************************************************************************/
4 /*                       This file is part of:                           */
5 /*                           GODOT ENGINE                                */
6 /*                      https://godotengine.org                          */
7 /*************************************************************************/
8 /* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
9 /* Copyright (c) 2014-2020 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 
31 #include "multimesh_editor_plugin.h"
32 
33 #include "scene/3d/mesh_instance.h"
34 #include "scene/gui/box_container.h"
35 #include "spatial_editor_plugin.h"
36 
_node_removed(Node * p_node)37 void MultiMeshEditor::_node_removed(Node *p_node) {
38 
39 	if (p_node == node) {
40 		node = NULL;
41 		hide();
42 	}
43 }
44 
_populate()45 void MultiMeshEditor::_populate() {
46 
47 	if (!node)
48 		return;
49 
50 	Ref<Mesh> mesh;
51 
52 	if (mesh_source->get_text() == "") {
53 
54 		Ref<MultiMesh> multimesh;
55 		multimesh = node->get_multimesh();
56 		if (multimesh.is_null()) {
57 
58 			err_dialog->set_text(TTR("No mesh source specified (and no MultiMesh set in node)."));
59 			err_dialog->popup_centered_minsize();
60 			return;
61 		}
62 		if (multimesh->get_mesh().is_null()) {
63 
64 			err_dialog->set_text(TTR("No mesh source specified (and MultiMesh contains no Mesh)."));
65 			err_dialog->popup_centered_minsize();
66 			return;
67 		}
68 
69 		mesh = multimesh->get_mesh();
70 	} else {
71 
72 		Node *ms_node = node->get_node(mesh_source->get_text());
73 
74 		if (!ms_node) {
75 
76 			err_dialog->set_text(TTR("Mesh source is invalid (invalid path)."));
77 			err_dialog->popup_centered_minsize();
78 			return;
79 		}
80 
81 		MeshInstance *ms_instance = Object::cast_to<MeshInstance>(ms_node);
82 
83 		if (!ms_instance) {
84 
85 			err_dialog->set_text(TTR("Mesh source is invalid (not a MeshInstance)."));
86 			err_dialog->popup_centered_minsize();
87 			return;
88 		}
89 
90 		mesh = ms_instance->get_mesh();
91 
92 		if (mesh.is_null()) {
93 
94 			err_dialog->set_text(TTR("Mesh source is invalid (contains no Mesh resource)."));
95 			err_dialog->popup_centered_minsize();
96 			return;
97 		}
98 	}
99 
100 	if (surface_source->get_text() == "") {
101 
102 		err_dialog->set_text(TTR("No surface source specified."));
103 		err_dialog->popup_centered_minsize();
104 		return;
105 	}
106 
107 	Node *ss_node = node->get_node(surface_source->get_text());
108 
109 	if (!ss_node) {
110 
111 		err_dialog->set_text(TTR("Surface source is invalid (invalid path)."));
112 		err_dialog->popup_centered_minsize();
113 		return;
114 	}
115 
116 	GeometryInstance *ss_instance = Object::cast_to<MeshInstance>(ss_node);
117 
118 	if (!ss_instance) {
119 
120 		err_dialog->set_text(TTR("Surface source is invalid (no geometry)."));
121 		err_dialog->popup_centered_minsize();
122 		return;
123 	}
124 
125 	Transform geom_xform = node->get_global_transform().affine_inverse() * ss_instance->get_global_transform();
126 
127 	PoolVector<Face3> geometry = ss_instance->get_faces(VisualInstance::FACES_SOLID);
128 
129 	if (geometry.size() == 0) {
130 
131 		err_dialog->set_text(TTR("Surface source is invalid (no faces)."));
132 		err_dialog->popup_centered_minsize();
133 		return;
134 	}
135 
136 	//make all faces local
137 
138 	int gc = geometry.size();
139 	PoolVector<Face3>::Write w = geometry.write();
140 
141 	for (int i = 0; i < gc; i++) {
142 		for (int j = 0; j < 3; j++) {
143 			w[i].vertex[j] = geom_xform.xform(w[i].vertex[j]);
144 		}
145 	}
146 
147 	w.release();
148 
149 	PoolVector<Face3> faces = geometry;
150 	int facecount = faces.size();
151 	ERR_FAIL_COND_MSG(!facecount, "Parent has no solid faces to populate.");
152 
153 	PoolVector<Face3>::Read r = faces.read();
154 
155 	float area_accum = 0;
156 	Map<float, int> triangle_area_map;
157 	for (int i = 0; i < facecount; i++) {
158 
159 		float area = r[i].get_area();
160 		if (area < CMP_EPSILON)
161 			continue;
162 		triangle_area_map[area_accum] = i;
163 		area_accum += area;
164 	}
165 
166 	ERR_FAIL_COND_MSG(triangle_area_map.size() == 0, "Couldn't map area.");
167 	ERR_FAIL_COND_MSG(area_accum == 0, "Couldn't map area.");
168 
169 	Ref<MultiMesh> multimesh = memnew(MultiMesh);
170 	multimesh->set_mesh(mesh);
171 
172 	int instance_count = populate_amount->get_value();
173 
174 	multimesh->set_transform_format(MultiMesh::TRANSFORM_3D);
175 	multimesh->set_color_format(MultiMesh::COLOR_NONE);
176 	multimesh->set_instance_count(instance_count);
177 
178 	float _tilt_random = populate_tilt_random->get_value();
179 	float _rotate_random = populate_rotate_random->get_value();
180 	float _scale_random = populate_scale_random->get_value();
181 	float _scale = populate_scale->get_value();
182 	int axis = populate_axis->get_selected();
183 
184 	Transform axis_xform;
185 	if (axis == Vector3::AXIS_Z) {
186 		axis_xform.rotate(Vector3(1, 0, 0), -Math_PI * 0.5);
187 	}
188 	if (axis == Vector3::AXIS_X) {
189 		axis_xform.rotate(Vector3(0, 0, 1), -Math_PI * 0.5);
190 	}
191 
192 	for (int i = 0; i < instance_count; i++) {
193 
194 		float areapos = Math::random(0.0f, area_accum);
195 
196 		Map<float, int>::Element *E = triangle_area_map.find_closest(areapos);
197 		ERR_FAIL_COND(!E);
198 		int index = E->get();
199 		ERR_FAIL_INDEX(index, facecount);
200 
201 		// ok FINALLY get face
202 		Face3 face = r[index];
203 		//now compute some position inside the face...
204 
205 		Vector3 pos = face.get_random_point_inside();
206 		Vector3 normal = face.get_plane().normal;
207 		Vector3 op_axis = (face.vertex[0] - face.vertex[1]).normalized();
208 
209 		Transform xform;
210 
211 		xform.set_look_at(pos, pos + op_axis, normal);
212 		xform = xform * axis_xform;
213 
214 		Basis post_xform;
215 
216 		post_xform.rotate(xform.basis.get_axis(1), -Math::random(-_rotate_random, _rotate_random) * Math_PI);
217 		post_xform.rotate(xform.basis.get_axis(2), -Math::random(-_tilt_random, _tilt_random) * Math_PI);
218 		post_xform.rotate(xform.basis.get_axis(0), -Math::random(-_tilt_random, _tilt_random) * Math_PI);
219 
220 		xform.basis = post_xform * xform.basis;
221 		//xform.basis.orthonormalize();
222 
223 		xform.basis.scale(Vector3(1, 1, 1) * (_scale + Math::random(-_scale_random, _scale_random)));
224 
225 		multimesh->set_instance_transform(i, xform);
226 	}
227 
228 	node->set_multimesh(multimesh);
229 }
230 
_browsed(const NodePath & p_path)231 void MultiMeshEditor::_browsed(const NodePath &p_path) {
232 
233 	NodePath path = node->get_path_to(get_node(p_path));
234 
235 	if (browsing_source)
236 		mesh_source->set_text(path);
237 	else
238 		surface_source->set_text(path);
239 }
240 
_menu_option(int p_option)241 void MultiMeshEditor::_menu_option(int p_option) {
242 
243 	switch (p_option) {
244 
245 		case MENU_OPTION_POPULATE: {
246 
247 			if (_last_pp_node != node) {
248 
249 				surface_source->set_text("..");
250 				mesh_source->set_text("..");
251 				populate_axis->select(1);
252 				populate_rotate_random->set_value(0);
253 				populate_tilt_random->set_value(0);
254 				populate_scale_random->set_value(0);
255 				populate_scale->set_value(1);
256 				populate_amount->set_value(128);
257 
258 				_last_pp_node = node;
259 			}
260 			populate_dialog->popup_centered(Size2(250, 380));
261 
262 		} break;
263 	}
264 }
265 
edit(MultiMeshInstance * p_multimesh)266 void MultiMeshEditor::edit(MultiMeshInstance *p_multimesh) {
267 
268 	node = p_multimesh;
269 }
270 
_browse(bool p_source)271 void MultiMeshEditor::_browse(bool p_source) {
272 
273 	browsing_source = p_source;
274 	std->get_scene_tree()->set_marked(node, false);
275 	std->popup_centered_ratio();
276 	if (p_source)
277 		std->set_title(TTR("Select a Source Mesh:"));
278 	else
279 		std->set_title(TTR("Select a Target Surface:"));
280 }
281 
_bind_methods()282 void MultiMeshEditor::_bind_methods() {
283 
284 	ClassDB::bind_method("_menu_option", &MultiMeshEditor::_menu_option);
285 	ClassDB::bind_method("_populate", &MultiMeshEditor::_populate);
286 	ClassDB::bind_method("_browsed", &MultiMeshEditor::_browsed);
287 	ClassDB::bind_method("_browse", &MultiMeshEditor::_browse);
288 }
289 
MultiMeshEditor()290 MultiMeshEditor::MultiMeshEditor() {
291 
292 	options = memnew(MenuButton);
293 	options->set_switch_on_hover(true);
294 	SpatialEditor::get_singleton()->add_control_to_menu_panel(options);
295 
296 	options->set_text("MultiMesh");
297 	options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_icon("MultiMeshInstance", "EditorIcons"));
298 
299 	options->get_popup()->add_item(TTR("Populate Surface"));
300 	options->get_popup()->connect("id_pressed", this, "_menu_option");
301 
302 	populate_dialog = memnew(ConfirmationDialog);
303 	populate_dialog->set_title(TTR("Populate MultiMesh"));
304 	add_child(populate_dialog);
305 
306 	VBoxContainer *vbc = memnew(VBoxContainer);
307 	populate_dialog->add_child(vbc);
308 	//populate_dialog->set_child_rect(vbc);
309 
310 	HBoxContainer *hbc = memnew(HBoxContainer);
311 
312 	surface_source = memnew(LineEdit);
313 	hbc->add_child(surface_source);
314 	surface_source->set_h_size_flags(SIZE_EXPAND_FILL);
315 	Button *b = memnew(Button);
316 	hbc->add_child(b);
317 	b->set_text("..");
318 	b->connect("pressed", this, "_browse", make_binds(false));
319 
320 	vbc->add_margin_child(TTR("Target Surface:"), hbc);
321 
322 	hbc = memnew(HBoxContainer);
323 	mesh_source = memnew(LineEdit);
324 	hbc->add_child(mesh_source);
325 	mesh_source->set_h_size_flags(SIZE_EXPAND_FILL);
326 	b = memnew(Button);
327 	hbc->add_child(b);
328 	b->set_text("..");
329 	vbc->add_margin_child(TTR("Source Mesh:"), hbc);
330 	b->connect("pressed", this, "_browse", make_binds(true));
331 
332 	populate_axis = memnew(OptionButton);
333 	populate_axis->add_item(TTR("X-Axis"));
334 	populate_axis->add_item(TTR("Y-Axis"));
335 	populate_axis->add_item(TTR("Z-Axis"));
336 	populate_axis->select(2);
337 	vbc->add_margin_child(TTR("Mesh Up Axis:"), populate_axis);
338 
339 	populate_rotate_random = memnew(HSlider);
340 	populate_rotate_random->set_max(1);
341 	populate_rotate_random->set_step(0.01);
342 	vbc->add_margin_child(TTR("Random Rotation:"), populate_rotate_random);
343 
344 	populate_tilt_random = memnew(HSlider);
345 	populate_tilt_random->set_max(1);
346 	populate_tilt_random->set_step(0.01);
347 	vbc->add_margin_child(TTR("Random Tilt:"), populate_tilt_random);
348 
349 	populate_scale_random = memnew(SpinBox);
350 	populate_scale_random->set_min(0);
351 	populate_scale_random->set_max(1);
352 	populate_scale_random->set_value(0);
353 	populate_scale_random->set_step(0.01);
354 
355 	vbc->add_margin_child(TTR("Random Scale:"), populate_scale_random);
356 
357 	populate_scale = memnew(SpinBox);
358 	populate_scale->set_min(0.001);
359 	populate_scale->set_max(4096);
360 	populate_scale->set_value(1);
361 	populate_scale->set_step(0.01);
362 
363 	vbc->add_margin_child(TTR("Scale:"), populate_scale);
364 
365 	populate_amount = memnew(SpinBox);
366 	populate_amount->set_anchor(MARGIN_RIGHT, ANCHOR_END);
367 	populate_amount->set_begin(Point2(20, 232));
368 	populate_amount->set_end(Point2(-5, 237));
369 	populate_amount->set_min(1);
370 	populate_amount->set_max(65536);
371 	populate_amount->set_value(128);
372 	vbc->add_margin_child(TTR("Amount:"), populate_amount);
373 
374 	populate_dialog->get_ok()->set_text(TTR("Populate"));
375 
376 	populate_dialog->get_ok()->connect("pressed", this, "_populate");
377 	std = memnew(SceneTreeDialog);
378 	populate_dialog->add_child(std);
379 	std->connect("selected", this, "_browsed");
380 
381 	_last_pp_node = NULL;
382 
383 	err_dialog = memnew(AcceptDialog);
384 	add_child(err_dialog);
385 }
386 
edit(Object * p_object)387 void MultiMeshEditorPlugin::edit(Object *p_object) {
388 
389 	multimesh_editor->edit(Object::cast_to<MultiMeshInstance>(p_object));
390 }
391 
handles(Object * p_object) const392 bool MultiMeshEditorPlugin::handles(Object *p_object) const {
393 
394 	return p_object->is_class("MultiMeshInstance");
395 }
396 
make_visible(bool p_visible)397 void MultiMeshEditorPlugin::make_visible(bool p_visible) {
398 
399 	if (p_visible) {
400 		multimesh_editor->options->show();
401 	} else {
402 
403 		multimesh_editor->options->hide();
404 		multimesh_editor->edit(NULL);
405 	}
406 }
407 
MultiMeshEditorPlugin(EditorNode * p_node)408 MultiMeshEditorPlugin::MultiMeshEditorPlugin(EditorNode *p_node) {
409 
410 	editor = p_node;
411 	multimesh_editor = memnew(MultiMeshEditor);
412 	editor->get_viewport()->add_child(multimesh_editor);
413 
414 	multimesh_editor->options->hide();
415 }
416 
~MultiMeshEditorPlugin()417 MultiMeshEditorPlugin::~MultiMeshEditorPlugin() {
418 }
419