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