Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/editor/scene/2d/particles_2d_editor_plugin.cpp
9904 views
1
/**************************************************************************/
2
/* particles_2d_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 "particles_2d_editor_plugin.h"
32
33
#include "core/io/image_loader.h"
34
#include "editor/editor_node.h"
35
#include "editor/editor_undo_redo_manager.h"
36
#include "editor/gui/editor_file_dialog.h"
37
#include "scene/2d/cpu_particles_2d.h"
38
#include "scene/2d/gpu_particles_2d.h"
39
#include "scene/gui/box_container.h"
40
#include "scene/gui/check_box.h"
41
#include "scene/gui/option_button.h"
42
#include "scene/gui/popup_menu.h"
43
#include "scene/gui/spin_box.h"
44
#include "scene/resources/image_texture.h"
45
#include "scene/resources/particle_process_material.h"
46
47
void GPUParticles2DEditorPlugin::_menu_callback(int p_idx) {
48
if (p_idx == MENU_GENERATE_VISIBILITY_RECT) {
49
if (need_show_lifetime_dialog(generate_seconds)) {
50
generate_visibility_rect->popup_centered();
51
} else {
52
_generate_visibility_rect();
53
}
54
} else {
55
Particles2DEditorPlugin::_menu_callback(p_idx);
56
}
57
}
58
59
void GPUParticles2DEditorPlugin::_add_menu_options(PopupMenu *p_menu) {
60
Particles2DEditorPlugin::_add_menu_options(p_menu);
61
p_menu->add_item(TTR("Generate Visibility Rect"), MENU_GENERATE_VISIBILITY_RECT);
62
}
63
64
void Particles2DEditorPlugin::_file_selected(const String &p_file) {
65
source_emission_file = p_file;
66
emission_mask->popup_centered();
67
}
68
69
void Particles2DEditorPlugin::_get_base_emission_mask(PackedVector2Array &r_valid_positions, PackedVector2Array &r_valid_normals, PackedByteArray &r_valid_colors, Vector2i &r_image_size) {
70
Ref<Image> img;
71
img.instantiate();
72
Error err = ImageLoader::load_image(source_emission_file, img);
73
ERR_FAIL_COND_MSG(err != OK, "Error loading image '" + source_emission_file + "'.");
74
75
if (img->is_compressed()) {
76
img->decompress();
77
}
78
img->convert(Image::FORMAT_RGBA8);
79
ERR_FAIL_COND(img->get_format() != Image::FORMAT_RGBA8);
80
Size2i s = img->get_size();
81
ERR_FAIL_COND(s.width == 0 || s.height == 0);
82
83
r_image_size = s;
84
85
r_valid_positions.resize(s.width * s.height);
86
87
EmissionMode emode = (EmissionMode)emission_mask_mode->get_selected();
88
89
if (emode == EMISSION_MODE_BORDER_DIRECTED) {
90
r_valid_normals.resize(s.width * s.height);
91
}
92
93
bool capture_colors = emission_colors->is_pressed();
94
95
if (capture_colors) {
96
r_valid_colors.resize(s.width * s.height * 4);
97
}
98
99
int vpc = 0;
100
101
{
102
Vector<uint8_t> img_data = img->get_data();
103
const uint8_t *r = img_data.ptr();
104
105
for (int i = 0; i < s.width; i++) {
106
for (int j = 0; j < s.height; j++) {
107
uint8_t a = r[(j * s.width + i) * 4 + 3];
108
109
if (a > 128) {
110
if (emode == EMISSION_MODE_SOLID) {
111
if (capture_colors) {
112
r_valid_colors.write[vpc * 4 + 0] = r[(j * s.width + i) * 4 + 0];
113
r_valid_colors.write[vpc * 4 + 1] = r[(j * s.width + i) * 4 + 1];
114
r_valid_colors.write[vpc * 4 + 2] = r[(j * s.width + i) * 4 + 2];
115
r_valid_colors.write[vpc * 4 + 3] = r[(j * s.width + i) * 4 + 3];
116
}
117
r_valid_positions.write[vpc++] = Point2(i, j);
118
119
} else {
120
bool on_border = false;
121
for (int x = i - 1; x <= i + 1; x++) {
122
for (int y = j - 1; y <= j + 1; y++) {
123
if (x < 0 || y < 0 || x >= s.width || y >= s.height || r[(y * s.width + x) * 4 + 3] <= 128) {
124
on_border = true;
125
break;
126
}
127
}
128
129
if (on_border) {
130
break;
131
}
132
}
133
134
if (on_border) {
135
r_valid_positions.write[vpc] = Point2(i, j);
136
137
if (emode == EMISSION_MODE_BORDER_DIRECTED) {
138
Vector2 normal;
139
for (int x = i - 2; x <= i + 2; x++) {
140
for (int y = j - 2; y <= j + 2; y++) {
141
if (x == i && y == j) {
142
continue;
143
}
144
145
if (x < 0 || y < 0 || x >= s.width || y >= s.height || r[(y * s.width + x) * 4 + 3] <= 128) {
146
normal += Vector2(x - i, y - j).normalized();
147
}
148
}
149
}
150
151
normal.normalize();
152
r_valid_normals.write[vpc] = normal;
153
}
154
155
if (capture_colors) {
156
r_valid_colors.write[vpc * 4 + 0] = r[(j * s.width + i) * 4 + 0];
157
r_valid_colors.write[vpc * 4 + 1] = r[(j * s.width + i) * 4 + 1];
158
r_valid_colors.write[vpc * 4 + 2] = r[(j * s.width + i) * 4 + 2];
159
r_valid_colors.write[vpc * 4 + 3] = r[(j * s.width + i) * 4 + 3];
160
}
161
162
vpc++;
163
}
164
}
165
}
166
}
167
}
168
}
169
170
r_valid_positions.resize(vpc);
171
if (!r_valid_normals.is_empty()) {
172
r_valid_normals.resize(vpc);
173
}
174
}
175
176
Particles2DEditorPlugin::Particles2DEditorPlugin() {
177
file = memnew(EditorFileDialog);
178
179
List<String> ext;
180
ImageLoader::get_recognized_extensions(&ext);
181
for (const String &E : ext) {
182
file->add_filter("*." + E, E.to_upper());
183
}
184
185
file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
186
EditorNode::get_singleton()->get_gui_base()->add_child(file);
187
file->connect("file_selected", callable_mp(this, &Particles2DEditorPlugin::_file_selected));
188
189
emission_mask = memnew(ConfirmationDialog);
190
emission_mask->set_title(TTR("Load Emission Mask"));
191
192
VBoxContainer *emvb = memnew(VBoxContainer);
193
emission_mask->add_child(emvb);
194
195
emission_mask_mode = memnew(OptionButton);
196
emission_mask_mode->add_item(TTR("Solid Pixels"), EMISSION_MODE_SOLID);
197
emission_mask_mode->add_item(TTR("Border Pixels"), EMISSION_MODE_BORDER);
198
emission_mask_mode->add_item(TTR("Directed Border Pixels"), EMISSION_MODE_BORDER_DIRECTED);
199
emvb->add_margin_child(TTR("Emission Mask"), emission_mask_mode);
200
201
VBoxContainer *optionsvb = memnew(VBoxContainer);
202
emvb->add_margin_child(TTR("Options"), optionsvb);
203
204
emission_mask_centered = memnew(CheckBox(TTR("Centered")));
205
optionsvb->add_child(emission_mask_centered);
206
emission_colors = memnew(CheckBox(TTR("Capture Colors from Pixel")));
207
optionsvb->add_child(emission_colors);
208
209
EditorNode::get_singleton()->get_gui_base()->add_child(emission_mask);
210
211
emission_mask->connect(SceneStringName(confirmed), callable_mp(this, &Particles2DEditorPlugin::_generate_emission_mask));
212
}
213
214
void Particles2DEditorPlugin::_set_show_gizmos(Node *p_node, bool p_show) {
215
GPUParticles2D *gpu_particles = Object::cast_to<GPUParticles2D>(p_node);
216
if (gpu_particles) {
217
gpu_particles->set_show_gizmos(p_show);
218
}
219
CPUParticles2D *cpu_particles = Object::cast_to<CPUParticles2D>(p_node);
220
if (cpu_particles) {
221
cpu_particles->set_show_gizmos(p_show);
222
}
223
224
// The `selection_changed` signal is deferred. A node could be deleted before the signal is emitted.
225
if (p_show) {
226
p_node->connect(SceneStringName(tree_exiting), callable_mp(this, &Particles2DEditorPlugin::_node_removed).bind(p_node));
227
} else {
228
p_node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Particles2DEditorPlugin::_node_removed));
229
}
230
}
231
232
void Particles2DEditorPlugin::_selection_changed() {
233
List<Node *> current_selection = EditorNode::get_singleton()->get_editor_selection()->get_top_selected_node_list();
234
if (selected_particles.is_empty() && current_selection.is_empty()) {
235
return;
236
}
237
238
// Turn gizmos off for nodes that are no longer selected.
239
for (List<Node *>::Element *E = selected_particles.front(); E;) {
240
Node *node = E->get();
241
List<Node *>::Element *N = E->next();
242
if (current_selection.find(node) == nullptr) {
243
_set_show_gizmos(node, false);
244
selected_particles.erase(E);
245
}
246
E = N;
247
}
248
249
// Turn gizmos on for nodes that are newly selected.
250
for (Node *node : current_selection) {
251
if (selected_particles.find(node) == nullptr) {
252
_set_show_gizmos(node, true);
253
selected_particles.push_back(node);
254
}
255
}
256
}
257
258
void Particles2DEditorPlugin::_node_removed(Node *p_node) {
259
List<Node *>::Element *E = selected_particles.find(p_node);
260
if (E) {
261
_set_show_gizmos(E->get(), false);
262
selected_particles.erase(E);
263
}
264
}
265
266
void GPUParticles2DEditorPlugin::_generate_visibility_rect() {
267
GPUParticles2D *particles = Object::cast_to<GPUParticles2D>(edited_node);
268
269
double time = generate_seconds->get_value();
270
271
float running = 0.0;
272
273
EditorProgress ep("gen_vrect", TTR("Generating Visibility Rect (Waiting for Particle Simulation)"), int(time));
274
275
bool was_emitting = particles->is_emitting();
276
if (!was_emitting) {
277
particles->set_emitting(true);
278
OS::get_singleton()->delay_usec(1000);
279
}
280
281
Rect2 rect;
282
while (running < time) {
283
uint64_t ticks = OS::get_singleton()->get_ticks_usec();
284
ep.step(TTR("Generating..."), int(running), true);
285
OS::get_singleton()->delay_usec(1000);
286
287
Rect2 capture = particles->capture_rect();
288
if (rect == Rect2()) {
289
rect = capture;
290
} else {
291
rect = rect.merge(capture);
292
}
293
294
running += (OS::get_singleton()->get_ticks_usec() - ticks) / 1000000.0;
295
}
296
297
if (!was_emitting) {
298
particles->set_emitting(false);
299
}
300
301
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
302
undo_redo->create_action(TTR("Generate Visibility Rect"));
303
undo_redo->add_do_method(particles, "set_visibility_rect", rect);
304
undo_redo->add_undo_method(particles, "set_visibility_rect", particles->get_visibility_rect());
305
undo_redo->commit_action();
306
}
307
308
void Particles2DEditorPlugin::_notification(int p_what) {
309
switch (p_what) {
310
case NOTIFICATION_ENTER_TREE: {
311
EditorNode::get_singleton()->get_editor_selection()->connect("selection_changed", callable_mp(this, &Particles2DEditorPlugin::_selection_changed));
312
} break;
313
}
314
}
315
316
void Particles2DEditorPlugin::_menu_callback(int p_idx) {
317
if (p_idx == MENU_LOAD_EMISSION_MASK) {
318
GPUParticles2D *particles = Object::cast_to<GPUParticles2D>(edited_node);
319
if (particles && particles->get_process_material().is_null()) {
320
EditorNode::get_singleton()->show_warning(TTR("Loading emission mask requires ParticleProcessMaterial."));
321
return;
322
}
323
324
file->popup_file_dialog();
325
} else {
326
ParticlesEditorPlugin::_menu_callback(p_idx);
327
}
328
}
329
330
void Particles2DEditorPlugin::_add_menu_options(PopupMenu *p_menu) {
331
p_menu->add_item(TTR("Load Emission Mask"), MENU_LOAD_EMISSION_MASK);
332
}
333
334
Node *GPUParticles2DEditorPlugin::_convert_particles() {
335
GPUParticles2D *particles = Object::cast_to<GPUParticles2D>(edited_node);
336
337
CPUParticles2D *cpu_particles = memnew(CPUParticles2D);
338
cpu_particles->convert_from_particles(particles);
339
cpu_particles->set_name(particles->get_name());
340
cpu_particles->set_transform(particles->get_transform());
341
cpu_particles->set_visible(particles->is_visible());
342
cpu_particles->set_process_mode(particles->get_process_mode());
343
cpu_particles->set_z_index(particles->get_z_index());
344
return cpu_particles;
345
}
346
347
void GPUParticles2DEditorPlugin::_generate_emission_mask() {
348
GPUParticles2D *particles = Object::cast_to<GPUParticles2D>(edited_node);
349
Ref<ParticleProcessMaterial> pm = particles->get_process_material();
350
ERR_FAIL_COND(pm.is_null());
351
352
PackedVector2Array valid_positions;
353
PackedVector2Array valid_normals;
354
PackedByteArray valid_colors;
355
Vector2i image_size;
356
_get_base_emission_mask(valid_positions, valid_normals, valid_colors, image_size);
357
358
ERR_FAIL_COND_MSG(valid_positions.is_empty(), "No pixels with transparency > 128 in image...");
359
360
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
361
undo_redo->create_action(TTR("Load Emission Mask"));
362
ParticleProcessMaterial *pmptr = pm.ptr();
363
364
Vector<uint8_t> texdata;
365
366
int vpc = valid_positions.size();
367
int w = 2048;
368
int h = (vpc / 2048) + 1;
369
370
texdata.resize(w * h * 2 * sizeof(float));
371
372
{
373
Vector2 offset;
374
if (emission_mask_centered->is_pressed()) {
375
offset = Vector2(-image_size.width * 0.5, -image_size.height * 0.5);
376
}
377
378
uint8_t *tw = texdata.ptrw();
379
float *twf = reinterpret_cast<float *>(tw);
380
for (int i = 0; i < vpc; i++) {
381
twf[i * 2 + 0] = valid_positions[i].x + offset.x;
382
twf[i * 2 + 1] = valid_positions[i].y + offset.y;
383
}
384
}
385
386
Ref<Image> img;
387
img.instantiate();
388
img->set_data(w, h, false, Image::FORMAT_RGF, texdata);
389
undo_redo->add_do_property(pmptr, "emission_point_texture", ImageTexture::create_from_image(img));
390
undo_redo->add_undo_property(pmptr, "emission_point_texture", pm->get_emission_point_texture());
391
undo_redo->add_do_property(pmptr, "emission_point_count", vpc);
392
undo_redo->add_undo_property(pmptr, "emission_point_count", pm->get_emission_point_count());
393
394
if (emission_colors->is_pressed()) {
395
Vector<uint8_t> colordata;
396
colordata.resize(w * h * 4); //use RG texture
397
398
{
399
uint8_t *tw = colordata.ptrw();
400
for (int i = 0; i < vpc * 4; i++) {
401
tw[i] = valid_colors[i];
402
}
403
}
404
405
img.instantiate();
406
img->set_data(w, h, false, Image::FORMAT_RGBA8, colordata);
407
undo_redo->add_do_property(pmptr, "emission_color_texture", ImageTexture::create_from_image(img));
408
undo_redo->add_undo_property(pmptr, "emission_color_texture", pm->get_emission_color_texture());
409
}
410
411
if (!valid_normals.is_empty()) {
412
undo_redo->add_do_property(pmptr, "emission_shape", ParticleProcessMaterial::EMISSION_SHAPE_DIRECTED_POINTS);
413
undo_redo->add_undo_property(pmptr, "emission_shape", pm->get_emission_shape());
414
pm->set_emission_shape(ParticleProcessMaterial::EMISSION_SHAPE_DIRECTED_POINTS);
415
416
Vector<uint8_t> normdata;
417
normdata.resize(w * h * 2 * sizeof(float)); //use RG texture
418
419
{
420
uint8_t *tw = normdata.ptrw();
421
float *twf = reinterpret_cast<float *>(tw);
422
for (int i = 0; i < vpc; i++) {
423
twf[i * 2 + 0] = valid_normals[i].x;
424
twf[i * 2 + 1] = valid_normals[i].y;
425
}
426
}
427
428
img.instantiate();
429
img->set_data(w, h, false, Image::FORMAT_RGF, normdata);
430
undo_redo->add_do_property(pmptr, "emission_normal_texture", ImageTexture::create_from_image(img));
431
undo_redo->add_undo_property(pmptr, "emission_normal_texture", pm->get_emission_normal_texture());
432
} else {
433
undo_redo->add_do_property(pmptr, "emission_shape", ParticleProcessMaterial::EMISSION_SHAPE_POINTS);
434
undo_redo->add_undo_property(pmptr, "emission_shape", pm->get_emission_shape());
435
}
436
undo_redo->commit_action();
437
}
438
439
GPUParticles2DEditorPlugin::GPUParticles2DEditorPlugin() {
440
handled_type = TTRC("GPUParticles2D");
441
conversion_option_name = TTR("Convert to CPUParticles2D");
442
443
generate_visibility_rect = memnew(ConfirmationDialog);
444
generate_visibility_rect->set_title(TTR("Generate Visibility Rect"));
445
446
VBoxContainer *genvb = memnew(VBoxContainer);
447
generate_visibility_rect->add_child(genvb);
448
449
generate_seconds = memnew(SpinBox);
450
generate_seconds->set_min(0.1);
451
generate_seconds->set_max(25);
452
generate_seconds->set_value(2);
453
genvb->add_margin_child(TTR("Generation Time (sec):"), generate_seconds);
454
455
EditorNode::get_singleton()->get_gui_base()->add_child(generate_visibility_rect);
456
457
generate_visibility_rect->connect(SceneStringName(confirmed), callable_mp(this, &GPUParticles2DEditorPlugin::_generate_visibility_rect));
458
}
459
460
Node *CPUParticles2DEditorPlugin::_convert_particles() {
461
CPUParticles2D *particles = Object::cast_to<CPUParticles2D>(edited_node);
462
463
GPUParticles2D *gpu_particles = memnew(GPUParticles2D);
464
gpu_particles->convert_from_particles(particles);
465
gpu_particles->set_name(particles->get_name());
466
gpu_particles->set_transform(particles->get_transform());
467
gpu_particles->set_visible(particles->is_visible());
468
gpu_particles->set_process_mode(particles->get_process_mode());
469
return gpu_particles;
470
}
471
472
void CPUParticles2DEditorPlugin::_generate_emission_mask() {
473
CPUParticles2D *particles = Object::cast_to<CPUParticles2D>(edited_node);
474
475
PackedVector2Array valid_positions;
476
PackedVector2Array valid_normals;
477
PackedByteArray valid_colors;
478
Vector2i image_size;
479
_get_base_emission_mask(valid_positions, valid_normals, valid_colors, image_size);
480
481
ERR_FAIL_COND_MSG(valid_positions.is_empty(), "No pixels with transparency > 128 in image...");
482
483
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
484
undo_redo->create_action(TTR("Load Emission Mask"));
485
486
int vpc = valid_positions.size();
487
if (emission_colors->is_pressed()) {
488
PackedColorArray pca;
489
pca.resize(vpc);
490
Color *pcaw = pca.ptrw();
491
for (int i = 0; i < vpc; i += 1) {
492
Color color;
493
color.r = valid_colors[i * 4 + 0] / 255.0f;
494
color.g = valid_colors[i * 4 + 1] / 255.0f;
495
color.b = valid_colors[i * 4 + 2] / 255.0f;
496
color.a = valid_colors[i * 4 + 3] / 255.0f;
497
pcaw[i] = color;
498
}
499
undo_redo->add_do_property(particles, "emission_colors", pca);
500
undo_redo->add_undo_property(particles, "emission_colors", particles->get_emission_colors());
501
}
502
503
if (!valid_normals.is_empty()) {
504
undo_redo->add_do_property(particles, "emission_shape", CPUParticles2D::EMISSION_SHAPE_DIRECTED_POINTS);
505
undo_redo->add_undo_property(particles, "emission_shape", particles->get_emission_shape());
506
PackedVector2Array norms;
507
norms.resize(valid_normals.size());
508
Vector2 *normsw = norms.ptrw();
509
for (int i = 0; i < valid_normals.size(); i += 1) {
510
normsw[i] = valid_normals[i];
511
}
512
undo_redo->add_do_property(particles, "emission_normals", norms);
513
undo_redo->add_undo_property(particles, "emission_normals", particles->get_emission_normals());
514
} else {
515
undo_redo->add_do_property(particles, "emission_shape", CPUParticles2D::EMISSION_SHAPE_POINTS);
516
undo_redo->add_undo_property(particles, "emission_shape", particles->get_emission_shape());
517
}
518
519
{
520
Vector2 offset;
521
if (emission_mask_centered->is_pressed()) {
522
offset = Vector2(-image_size.width * 0.5, -image_size.height * 0.5);
523
}
524
525
PackedVector2Array points;
526
points.resize(valid_positions.size());
527
Vector2 *pointsw = points.ptrw();
528
for (int i = 0; i < valid_positions.size(); i += 1) {
529
pointsw[i] = valid_positions[i] + offset;
530
}
531
undo_redo->add_do_property(particles, "emission_points", points);
532
undo_redo->add_undo_property(particles, "emission_shape", particles->get_emission_points());
533
}
534
undo_redo->commit_action();
535
}
536
537
CPUParticles2DEditorPlugin::CPUParticles2DEditorPlugin() {
538
handled_type = TTRC("CPUParticles2D");
539
conversion_option_name = TTR("Convert to GPUParticles2D");
540
}
541
542