1 | /**************************************************************************/ |
2 | /* gpu_particles_3d_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 "gpu_particles_3d_editor_plugin.h" |
32 | |
33 | #include "core/io/resource_loader.h" |
34 | #include "editor/editor_node.h" |
35 | #include "editor/editor_undo_redo_manager.h" |
36 | #include "editor/plugins/node_3d_editor_plugin.h" |
37 | #include "editor/scene_tree_dock.h" |
38 | #include "scene/3d/cpu_particles_3d.h" |
39 | #include "scene/3d/mesh_instance_3d.h" |
40 | #include "scene/gui/menu_button.h" |
41 | #include "scene/resources/image_texture.h" |
42 | #include "scene/resources/particle_process_material.h" |
43 | |
44 | bool GPUParticles3DEditorBase::_generate(Vector<Vector3> &points, Vector<Vector3> &normals) { |
45 | bool use_normals = emission_fill->get_selected() == 1; |
46 | |
47 | if (emission_fill->get_selected() < 2) { |
48 | float area_accum = 0; |
49 | RBMap<float, int> triangle_area_map; |
50 | |
51 | for (int i = 0; i < geometry.size(); i++) { |
52 | float area = geometry[i].get_area(); |
53 | if (area < CMP_EPSILON) { |
54 | continue; |
55 | } |
56 | triangle_area_map[area_accum] = i; |
57 | area_accum += area; |
58 | } |
59 | |
60 | if (!triangle_area_map.size() || area_accum == 0) { |
61 | EditorNode::get_singleton()->show_warning(TTR("The geometry's faces don't contain any area." )); |
62 | return false; |
63 | } |
64 | |
65 | int emissor_count = emission_amount->get_value(); |
66 | |
67 | for (int i = 0; i < emissor_count; i++) { |
68 | float areapos = Math::random(0.0f, area_accum); |
69 | |
70 | RBMap<float, int>::Iterator E = triangle_area_map.find_closest(areapos); |
71 | ERR_FAIL_COND_V(!E, false); |
72 | int index = E->value; |
73 | ERR_FAIL_INDEX_V(index, geometry.size(), false); |
74 | |
75 | // ok FINALLY get face |
76 | Face3 face = geometry[index]; |
77 | //now compute some position inside the face... |
78 | |
79 | Vector3 pos = face.get_random_point_inside(); |
80 | |
81 | points.push_back(pos); |
82 | |
83 | if (use_normals) { |
84 | Vector3 normal = face.get_plane().normal; |
85 | normals.push_back(normal); |
86 | } |
87 | } |
88 | } else { |
89 | int gcount = geometry.size(); |
90 | |
91 | if (gcount == 0) { |
92 | EditorNode::get_singleton()->show_warning(TTR("The geometry doesn't contain any faces." )); |
93 | return false; |
94 | } |
95 | |
96 | const Face3 *r = geometry.ptr(); |
97 | |
98 | AABB aabb; |
99 | |
100 | for (int i = 0; i < gcount; i++) { |
101 | for (int j = 0; j < 3; j++) { |
102 | if (i == 0 && j == 0) { |
103 | aabb.position = r[i].vertex[j]; |
104 | } else { |
105 | aabb.expand_to(r[i].vertex[j]); |
106 | } |
107 | } |
108 | } |
109 | |
110 | int emissor_count = emission_amount->get_value(); |
111 | |
112 | for (int i = 0; i < emissor_count; i++) { |
113 | int attempts = 5; |
114 | |
115 | for (int j = 0; j < attempts; j++) { |
116 | Vector3 dir; |
117 | dir[Math::rand() % 3] = 1.0; |
118 | Vector3 ofs = (Vector3(1, 1, 1) - dir) * Vector3(Math::randf(), Math::randf(), Math::randf()) * aabb.size + aabb.position; |
119 | |
120 | Vector3 ofsv = ofs + aabb.size * dir; |
121 | |
122 | //space it a little |
123 | ofs -= dir; |
124 | ofsv += dir; |
125 | |
126 | float max = -1e7, min = 1e7; |
127 | |
128 | for (int k = 0; k < gcount; k++) { |
129 | const Face3 &f3 = r[k]; |
130 | |
131 | Vector3 res; |
132 | if (f3.intersects_segment(ofs, ofsv, &res)) { |
133 | res -= ofs; |
134 | float d = dir.dot(res); |
135 | |
136 | if (d < min) { |
137 | min = d; |
138 | } |
139 | if (d > max) { |
140 | max = d; |
141 | } |
142 | } |
143 | } |
144 | |
145 | if (max < min) { |
146 | continue; //lost attempt |
147 | } |
148 | |
149 | float val = min + (max - min) * Math::randf(); |
150 | |
151 | Vector3 point = ofs + dir * val; |
152 | |
153 | points.push_back(point); |
154 | break; |
155 | } |
156 | } |
157 | } |
158 | |
159 | return true; |
160 | } |
161 | |
162 | void GPUParticles3DEditorBase::_node_selected(const NodePath &p_path) { |
163 | Node *sel = get_node(p_path); |
164 | if (!sel) { |
165 | return; |
166 | } |
167 | |
168 | if (!sel->is_class("Node3D" )) { |
169 | EditorNode::get_singleton()->show_warning(vformat(TTR("\"%s\" doesn't inherit from Node3D." ), sel->get_name())); |
170 | return; |
171 | } |
172 | |
173 | MeshInstance3D *mi = Object::cast_to<MeshInstance3D>(sel); |
174 | if (!mi || mi->get_mesh().is_null()) { |
175 | EditorNode::get_singleton()->show_warning(vformat(TTR("\"%s\" doesn't contain geometry." ), sel->get_name())); |
176 | return; |
177 | } |
178 | |
179 | geometry = mi->get_mesh()->get_faces(); |
180 | |
181 | if (geometry.size() == 0) { |
182 | EditorNode::get_singleton()->show_warning(vformat(TTR("\"%s\" doesn't contain face geometry." ), sel->get_name())); |
183 | return; |
184 | } |
185 | |
186 | Transform3D geom_xform = base_node->get_global_transform().affine_inverse() * mi->get_global_transform(); |
187 | |
188 | int gc = geometry.size(); |
189 | Face3 *w = geometry.ptrw(); |
190 | |
191 | for (int i = 0; i < gc; i++) { |
192 | for (int j = 0; j < 3; j++) { |
193 | w[i].vertex[j] = geom_xform.xform(w[i].vertex[j]); |
194 | } |
195 | } |
196 | |
197 | emission_dialog->popup_centered(Size2(300, 130)); |
198 | } |
199 | |
200 | void GPUParticles3DEditorBase::_bind_methods() { |
201 | } |
202 | |
203 | GPUParticles3DEditorBase::GPUParticles3DEditorBase() { |
204 | emission_dialog = memnew(ConfirmationDialog); |
205 | emission_dialog->set_title(TTR("Create Emitter" )); |
206 | add_child(emission_dialog); |
207 | VBoxContainer *emd_vb = memnew(VBoxContainer); |
208 | emission_dialog->add_child(emd_vb); |
209 | |
210 | emission_amount = memnew(SpinBox); |
211 | emission_amount->set_min(1); |
212 | emission_amount->set_max(100000); |
213 | emission_amount->set_value(512); |
214 | emd_vb->add_margin_child(TTR("Emission Points:" ), emission_amount); |
215 | |
216 | emission_fill = memnew(OptionButton); |
217 | emission_fill->add_item(TTR("Surface Points" )); |
218 | emission_fill->add_item(TTR("Surface Points+Normal (Directed)" )); |
219 | emission_fill->add_item(TTR("Volume" )); |
220 | emd_vb->add_margin_child(TTR("Emission Source:" ), emission_fill); |
221 | |
222 | emission_dialog->set_ok_button_text(TTR("Create" )); |
223 | emission_dialog->connect("confirmed" , callable_mp(this, &GPUParticles3DEditorBase::_generate_emission_points)); |
224 | |
225 | emission_tree_dialog = memnew(SceneTreeDialog); |
226 | add_child(emission_tree_dialog); |
227 | emission_tree_dialog->connect("selected" , callable_mp(this, &GPUParticles3DEditorBase::_node_selected)); |
228 | } |
229 | |
230 | void GPUParticles3DEditor::_node_removed(Node *p_node) { |
231 | if (p_node == node) { |
232 | node = nullptr; |
233 | hide(); |
234 | } |
235 | } |
236 | |
237 | void GPUParticles3DEditor::_notification(int p_notification) { |
238 | switch (p_notification) { |
239 | case NOTIFICATION_ENTER_TREE: { |
240 | options->set_icon(options->get_popup()->get_editor_theme_icon(SNAME("GPUParticles3D" ))); |
241 | get_tree()->connect("node_removed" , callable_mp(this, &GPUParticles3DEditor::_node_removed)); |
242 | } break; |
243 | } |
244 | } |
245 | |
246 | void GPUParticles3DEditor::_menu_option(int p_option) { |
247 | switch (p_option) { |
248 | case MENU_OPTION_GENERATE_AABB: { |
249 | // Add one second to the default generation lifetime, since the progress is updated every second. |
250 | generate_seconds->set_value(MAX(1.0, trunc(node->get_lifetime()) + 1.0)); |
251 | |
252 | if (generate_seconds->get_value() >= 11.0 + CMP_EPSILON) { |
253 | // Only pop up the time dialog if the particle's lifetime is long enough to warrant shortening it. |
254 | generate_aabb->popup_centered(); |
255 | } else { |
256 | // Generate the visibility AABB immediately. |
257 | _generate_aabb(); |
258 | } |
259 | } break; |
260 | case MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE: { |
261 | Ref<ParticleProcessMaterial> mat = node->get_process_material(); |
262 | if (mat.is_null()) { |
263 | EditorNode::get_singleton()->show_warning(TTR("A processor material of type 'ParticleProcessMaterial' is required." )); |
264 | return; |
265 | } |
266 | |
267 | emission_tree_dialog->popup_scenetree_dialog(); |
268 | |
269 | } break; |
270 | case MENU_OPTION_CONVERT_TO_CPU_PARTICLES: { |
271 | CPUParticles3D *cpu_particles = memnew(CPUParticles3D); |
272 | cpu_particles->convert_from_particles(node); |
273 | cpu_particles->set_name(node->get_name()); |
274 | cpu_particles->set_transform(node->get_transform()); |
275 | cpu_particles->set_visible(node->is_visible()); |
276 | cpu_particles->set_process_mode(node->get_process_mode()); |
277 | |
278 | EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); |
279 | ur->create_action(TTR("Convert to CPUParticles3D" )); |
280 | SceneTreeDock::get_singleton()->replace_node(node, cpu_particles); |
281 | ur->commit_action(false); |
282 | |
283 | } break; |
284 | case MENU_OPTION_RESTART: { |
285 | node->restart(); |
286 | |
287 | } break; |
288 | } |
289 | } |
290 | |
291 | void GPUParticles3DEditor::_generate_aabb() { |
292 | double time = generate_seconds->get_value(); |
293 | |
294 | double running = 0.0; |
295 | |
296 | EditorProgress ep("gen_aabb" , TTR("Generating Visibility AABB (Waiting for Particle Simulation)" ), int(time)); |
297 | |
298 | bool was_emitting = node->is_emitting(); |
299 | if (!was_emitting) { |
300 | node->set_emitting(true); |
301 | OS::get_singleton()->delay_usec(1000); |
302 | } |
303 | |
304 | AABB rect; |
305 | |
306 | while (running < time) { |
307 | uint64_t ticks = OS::get_singleton()->get_ticks_usec(); |
308 | ep.step("Generating..." , int(running), true); |
309 | OS::get_singleton()->delay_usec(1000); |
310 | |
311 | AABB capture = node->capture_aabb(); |
312 | if (rect == AABB()) { |
313 | rect = capture; |
314 | } else { |
315 | rect.merge_with(capture); |
316 | } |
317 | |
318 | running += (OS::get_singleton()->get_ticks_usec() - ticks) / 1000000.0; |
319 | } |
320 | |
321 | if (!was_emitting) { |
322 | node->set_emitting(false); |
323 | } |
324 | |
325 | EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); |
326 | ur->create_action(TTR("Generate Visibility AABB" )); |
327 | ur->add_do_method(node, "set_visibility_aabb" , rect); |
328 | ur->add_undo_method(node, "set_visibility_aabb" , node->get_visibility_aabb()); |
329 | ur->commit_action(); |
330 | } |
331 | |
332 | void GPUParticles3DEditor::edit(GPUParticles3D *p_particles) { |
333 | base_node = p_particles; |
334 | node = p_particles; |
335 | } |
336 | |
337 | void GPUParticles3DEditor::_generate_emission_points() { |
338 | /// hacer codigo aca |
339 | Vector<Vector3> points; |
340 | Vector<Vector3> normals; |
341 | |
342 | if (!_generate(points, normals)) { |
343 | return; |
344 | } |
345 | |
346 | int point_count = points.size(); |
347 | |
348 | int w = 2048; |
349 | int h = (point_count / 2048) + 1; |
350 | |
351 | Vector<uint8_t> point_img; |
352 | point_img.resize(w * h * 3 * sizeof(float)); |
353 | |
354 | { |
355 | uint8_t *iw = point_img.ptrw(); |
356 | memset(iw, 0, w * h * 3 * sizeof(float)); |
357 | const Vector3 *r = points.ptr(); |
358 | float *wf = reinterpret_cast<float *>(iw); |
359 | for (int i = 0; i < point_count; i++) { |
360 | wf[i * 3 + 0] = r[i].x; |
361 | wf[i * 3 + 1] = r[i].y; |
362 | wf[i * 3 + 2] = r[i].z; |
363 | } |
364 | } |
365 | |
366 | Ref<Image> image = memnew(Image(w, h, false, Image::FORMAT_RGBF, point_img)); |
367 | Ref<ImageTexture> tex = ImageTexture::create_from_image(image); |
368 | |
369 | Ref<ParticleProcessMaterial> mat = node->get_process_material(); |
370 | ERR_FAIL_COND(mat.is_null()); |
371 | |
372 | if (normals.size() > 0) { |
373 | mat->set_emission_shape(ParticleProcessMaterial::EMISSION_SHAPE_DIRECTED_POINTS); |
374 | mat->set_emission_point_count(point_count); |
375 | mat->set_emission_point_texture(tex); |
376 | |
377 | Vector<uint8_t> point_img2; |
378 | point_img2.resize(w * h * 3 * sizeof(float)); |
379 | |
380 | { |
381 | uint8_t *iw = point_img2.ptrw(); |
382 | memset(iw, 0, w * h * 3 * sizeof(float)); |
383 | const Vector3 *r = normals.ptr(); |
384 | float *wf = reinterpret_cast<float *>(iw); |
385 | for (int i = 0; i < point_count; i++) { |
386 | wf[i * 3 + 0] = r[i].x; |
387 | wf[i * 3 + 1] = r[i].y; |
388 | wf[i * 3 + 2] = r[i].z; |
389 | } |
390 | } |
391 | |
392 | Ref<Image> image2 = memnew(Image(w, h, false, Image::FORMAT_RGBF, point_img2)); |
393 | mat->set_emission_normal_texture(ImageTexture::create_from_image(image2)); |
394 | } else { |
395 | mat->set_emission_shape(ParticleProcessMaterial::EMISSION_SHAPE_POINTS); |
396 | mat->set_emission_point_count(point_count); |
397 | mat->set_emission_point_texture(tex); |
398 | } |
399 | } |
400 | |
401 | void GPUParticles3DEditor::_bind_methods() { |
402 | } |
403 | |
404 | GPUParticles3DEditor::GPUParticles3DEditor() { |
405 | node = nullptr; |
406 | particles_editor_hb = memnew(HBoxContainer); |
407 | Node3DEditor::get_singleton()->add_control_to_menu_panel(particles_editor_hb); |
408 | options = memnew(MenuButton); |
409 | options->set_switch_on_hover(true); |
410 | particles_editor_hb->add_child(options); |
411 | particles_editor_hb->hide(); |
412 | |
413 | options->set_text(TTR("GPUParticles3D" )); |
414 | options->get_popup()->add_item(TTR("Restart" ), MENU_OPTION_RESTART); |
415 | options->get_popup()->add_item(TTR("Generate AABB" ), MENU_OPTION_GENERATE_AABB); |
416 | options->get_popup()->add_item(TTR("Create Emission Points From Node" ), MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE); |
417 | options->get_popup()->add_item(TTR("Convert to CPUParticles3D" ), MENU_OPTION_CONVERT_TO_CPU_PARTICLES); |
418 | |
419 | options->get_popup()->connect("id_pressed" , callable_mp(this, &GPUParticles3DEditor::_menu_option)); |
420 | |
421 | generate_aabb = memnew(ConfirmationDialog); |
422 | generate_aabb->set_title(TTR("Generate Visibility AABB" )); |
423 | VBoxContainer *genvb = memnew(VBoxContainer); |
424 | generate_aabb->add_child(genvb); |
425 | generate_seconds = memnew(SpinBox); |
426 | genvb->add_margin_child(TTR("Generation Time (sec):" ), generate_seconds); |
427 | generate_seconds->set_min(0.1); |
428 | generate_seconds->set_max(25); |
429 | generate_seconds->set_value(2); |
430 | |
431 | add_child(generate_aabb); |
432 | |
433 | generate_aabb->connect("confirmed" , callable_mp(this, &GPUParticles3DEditor::_generate_aabb)); |
434 | } |
435 | |
436 | void GPUParticles3DEditorPlugin::edit(Object *p_object) { |
437 | particles_editor->edit(Object::cast_to<GPUParticles3D>(p_object)); |
438 | } |
439 | |
440 | bool GPUParticles3DEditorPlugin::handles(Object *p_object) const { |
441 | return p_object->is_class("GPUParticles3D" ); |
442 | } |
443 | |
444 | void GPUParticles3DEditorPlugin::make_visible(bool p_visible) { |
445 | if (p_visible) { |
446 | particles_editor->show(); |
447 | particles_editor->particles_editor_hb->show(); |
448 | } else { |
449 | particles_editor->particles_editor_hb->hide(); |
450 | particles_editor->hide(); |
451 | particles_editor->edit(nullptr); |
452 | } |
453 | } |
454 | |
455 | GPUParticles3DEditorPlugin::GPUParticles3DEditorPlugin() { |
456 | particles_editor = memnew(GPUParticles3DEditor); |
457 | EditorNode::get_singleton()->get_main_screen_control()->add_child(particles_editor); |
458 | |
459 | particles_editor->hide(); |
460 | } |
461 | |
462 | GPUParticles3DEditorPlugin::~GPUParticles3DEditorPlugin() { |
463 | } |
464 | |