1/**************************************************************************/
2/* gpu_particles_2d.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_2d.h"
32
33#include "scene/2d/cpu_particles_2d.h"
34#include "scene/resources/atlas_texture.h"
35#include "scene/resources/curve_texture.h"
36#include "scene/resources/gradient_texture.h"
37#include "scene/resources/particle_process_material.h"
38#include "scene/scene_string_names.h"
39
40#ifdef TOOLS_ENABLED
41#include "core/config/engine.h"
42#endif
43
44void GPUParticles2D::set_emitting(bool p_emitting) {
45 // Do not return even if `p_emitting == emitting` because `emitting` is just an approximation.
46
47 if (p_emitting && one_shot) {
48 if (!active && !emitting) {
49 // Last cycle ended.
50 active = true;
51 time = 0;
52 signal_canceled = false;
53 emission_time = lifetime;
54 active_time = lifetime * (2 - explosiveness_ratio);
55 } else {
56 signal_canceled = true;
57 }
58 set_process_internal(true);
59 } else if (!p_emitting) {
60 if (one_shot) {
61 set_process_internal(true);
62 } else {
63 set_process_internal(false);
64 }
65 }
66
67 emitting = p_emitting;
68 RS::get_singleton()->particles_set_emitting(particles, p_emitting);
69}
70
71void GPUParticles2D::set_amount(int p_amount) {
72 ERR_FAIL_COND_MSG(p_amount < 1, "Amount of particles cannot be smaller than 1.");
73 amount = p_amount;
74 RS::get_singleton()->particles_set_amount(particles, amount);
75}
76
77void GPUParticles2D::set_lifetime(double p_lifetime) {
78 ERR_FAIL_COND_MSG(p_lifetime <= 0, "Particles lifetime must be greater than 0.");
79 lifetime = p_lifetime;
80 RS::get_singleton()->particles_set_lifetime(particles, lifetime);
81}
82
83void GPUParticles2D::set_one_shot(bool p_enable) {
84 one_shot = p_enable;
85 RS::get_singleton()->particles_set_one_shot(particles, one_shot);
86
87 if (is_emitting()) {
88 set_process_internal(true);
89 if (!one_shot) {
90 RenderingServer::get_singleton()->particles_restart(particles);
91 }
92 }
93
94 if (!one_shot) {
95 set_process_internal(false);
96 }
97}
98
99void GPUParticles2D::set_pre_process_time(double p_time) {
100 pre_process_time = p_time;
101 RS::get_singleton()->particles_set_pre_process_time(particles, pre_process_time);
102}
103
104void GPUParticles2D::set_explosiveness_ratio(real_t p_ratio) {
105 explosiveness_ratio = p_ratio;
106 RS::get_singleton()->particles_set_explosiveness_ratio(particles, explosiveness_ratio);
107}
108
109void GPUParticles2D::set_randomness_ratio(real_t p_ratio) {
110 randomness_ratio = p_ratio;
111 RS::get_singleton()->particles_set_randomness_ratio(particles, randomness_ratio);
112}
113
114void GPUParticles2D::set_visibility_rect(const Rect2 &p_visibility_rect) {
115 visibility_rect = p_visibility_rect;
116 AABB aabb;
117 aabb.position.x = p_visibility_rect.position.x;
118 aabb.position.y = p_visibility_rect.position.y;
119 aabb.size.x = p_visibility_rect.size.x;
120 aabb.size.y = p_visibility_rect.size.y;
121
122 RS::get_singleton()->particles_set_custom_aabb(particles, aabb);
123
124 queue_redraw();
125}
126
127void GPUParticles2D::set_use_local_coordinates(bool p_enable) {
128 local_coords = p_enable;
129 RS::get_singleton()->particles_set_use_local_coordinates(particles, local_coords);
130 set_notify_transform(!p_enable);
131 if (!p_enable && is_inside_tree()) {
132 _update_particle_emission_transform();
133 }
134}
135
136void GPUParticles2D::_update_particle_emission_transform() {
137 Transform2D xf2d = get_global_transform();
138 Transform3D xf;
139 xf.basis.set_column(0, Vector3(xf2d.columns[0].x, xf2d.columns[0].y, 0));
140 xf.basis.set_column(1, Vector3(xf2d.columns[1].x, xf2d.columns[1].y, 0));
141 xf.set_origin(Vector3(xf2d.get_origin().x, xf2d.get_origin().y, 0));
142
143 RS::get_singleton()->particles_set_emission_transform(particles, xf);
144}
145
146void GPUParticles2D::set_process_material(const Ref<Material> &p_material) {
147 process_material = p_material;
148 Ref<ParticleProcessMaterial> pm = p_material;
149 if (pm.is_valid() && !pm->get_particle_flag(ParticleProcessMaterial::PARTICLE_FLAG_DISABLE_Z) && pm->get_gravity() == Vector3(0, -9.8, 0)) {
150 // Likely a new (3D) material, modify it to match 2D space
151 pm->set_particle_flag(ParticleProcessMaterial::PARTICLE_FLAG_DISABLE_Z, true);
152 pm->set_gravity(Vector3(0, 98, 0));
153 }
154 RID material_rid;
155 if (process_material.is_valid()) {
156 material_rid = process_material->get_rid();
157 }
158 RS::get_singleton()->particles_set_process_material(particles, material_rid);
159
160 update_configuration_warnings();
161}
162
163void GPUParticles2D::set_trail_enabled(bool p_enabled) {
164 trail_enabled = p_enabled;
165 RS::get_singleton()->particles_set_trails(particles, trail_enabled, trail_lifetime);
166 queue_redraw();
167 update_configuration_warnings();
168
169 RS::get_singleton()->particles_set_transform_align(particles, p_enabled ? RS::PARTICLES_TRANSFORM_ALIGN_Y_TO_VELOCITY : RS::PARTICLES_TRANSFORM_ALIGN_DISABLED);
170}
171
172void GPUParticles2D::set_trail_lifetime(double p_seconds) {
173 ERR_FAIL_COND(p_seconds < 0.01);
174 trail_lifetime = p_seconds;
175 RS::get_singleton()->particles_set_trails(particles, trail_enabled, trail_lifetime);
176 queue_redraw();
177}
178
179void GPUParticles2D::set_trail_sections(int p_sections) {
180 ERR_FAIL_COND(p_sections < 2);
181 ERR_FAIL_COND(p_sections > 128);
182
183 trail_sections = p_sections;
184 queue_redraw();
185}
186
187void GPUParticles2D::set_trail_section_subdivisions(int p_subdivisions) {
188 ERR_FAIL_COND(p_subdivisions < 1);
189 ERR_FAIL_COND(p_subdivisions > 1024);
190
191 trail_section_subdivisions = p_subdivisions;
192 queue_redraw();
193}
194
195#ifdef TOOLS_ENABLED
196void GPUParticles2D::set_show_visibility_rect(bool p_show_visibility_rect) {
197 show_visibility_rect = p_show_visibility_rect;
198 queue_redraw();
199}
200#endif
201
202bool GPUParticles2D::is_trail_enabled() const {
203 return trail_enabled;
204}
205
206double GPUParticles2D::get_trail_lifetime() const {
207 return trail_lifetime;
208}
209
210void GPUParticles2D::_update_collision_size() {
211 real_t csize = collision_base_size;
212
213 if (texture.is_valid()) {
214 csize *= (texture->get_width() + texture->get_height()) / 4.0; //half size since its a radius
215 }
216
217 RS::get_singleton()->particles_set_collision_base_size(particles, csize);
218}
219
220void GPUParticles2D::set_collision_base_size(real_t p_size) {
221 collision_base_size = p_size;
222 _update_collision_size();
223}
224
225real_t GPUParticles2D::get_collision_base_size() const {
226 return collision_base_size;
227}
228
229void GPUParticles2D::set_speed_scale(double p_scale) {
230 speed_scale = p_scale;
231 RS::get_singleton()->particles_set_speed_scale(particles, p_scale);
232}
233
234bool GPUParticles2D::is_emitting() const {
235 return emitting;
236}
237
238int GPUParticles2D::get_amount() const {
239 return amount;
240}
241
242double GPUParticles2D::get_lifetime() const {
243 return lifetime;
244}
245
246int GPUParticles2D::get_trail_sections() const {
247 return trail_sections;
248}
249int GPUParticles2D::get_trail_section_subdivisions() const {
250 return trail_section_subdivisions;
251}
252
253bool GPUParticles2D::get_one_shot() const {
254 return one_shot;
255}
256
257double GPUParticles2D::get_pre_process_time() const {
258 return pre_process_time;
259}
260
261real_t GPUParticles2D::get_explosiveness_ratio() const {
262 return explosiveness_ratio;
263}
264
265real_t GPUParticles2D::get_randomness_ratio() const {
266 return randomness_ratio;
267}
268
269Rect2 GPUParticles2D::get_visibility_rect() const {
270 return visibility_rect;
271}
272
273bool GPUParticles2D::get_use_local_coordinates() const {
274 return local_coords;
275}
276
277Ref<Material> GPUParticles2D::get_process_material() const {
278 return process_material;
279}
280
281double GPUParticles2D::get_speed_scale() const {
282 return speed_scale;
283}
284
285void GPUParticles2D::set_draw_order(DrawOrder p_order) {
286 draw_order = p_order;
287 RS::get_singleton()->particles_set_draw_order(particles, RS::ParticlesDrawOrder(p_order));
288}
289
290GPUParticles2D::DrawOrder GPUParticles2D::get_draw_order() const {
291 return draw_order;
292}
293
294void GPUParticles2D::set_fixed_fps(int p_count) {
295 fixed_fps = p_count;
296 RS::get_singleton()->particles_set_fixed_fps(particles, p_count);
297}
298
299int GPUParticles2D::get_fixed_fps() const {
300 return fixed_fps;
301}
302
303void GPUParticles2D::set_fractional_delta(bool p_enable) {
304 fractional_delta = p_enable;
305 RS::get_singleton()->particles_set_fractional_delta(particles, p_enable);
306}
307
308bool GPUParticles2D::get_fractional_delta() const {
309 return fractional_delta;
310}
311
312void GPUParticles2D::set_interpolate(bool p_enable) {
313 interpolate = p_enable;
314 RS::get_singleton()->particles_set_interpolate(particles, p_enable);
315}
316
317bool GPUParticles2D::get_interpolate() const {
318 return interpolate;
319}
320
321PackedStringArray GPUParticles2D::get_configuration_warnings() const {
322 PackedStringArray warnings = Node2D::get_configuration_warnings();
323
324 if (process_material.is_null()) {
325 warnings.push_back(RTR("A material to process the particles is not assigned, so no behavior is imprinted."));
326 } else {
327 CanvasItemMaterial *mat = Object::cast_to<CanvasItemMaterial>(get_material().ptr());
328
329 if (get_material().is_null() || (mat && !mat->get_particles_animation())) {
330 const ParticleProcessMaterial *process = Object::cast_to<ParticleProcessMaterial>(process_material.ptr());
331 if (process &&
332 (process->get_param_max(ParticleProcessMaterial::PARAM_ANIM_SPEED) != 0.0 || process->get_param_max(ParticleProcessMaterial::PARAM_ANIM_OFFSET) != 0.0 ||
333 process->get_param_texture(ParticleProcessMaterial::PARAM_ANIM_SPEED).is_valid() || process->get_param_texture(ParticleProcessMaterial::PARAM_ANIM_OFFSET).is_valid())) {
334 warnings.push_back(RTR("Particles2D animation requires the usage of a CanvasItemMaterial with \"Particles Animation\" enabled."));
335 }
336 }
337 }
338
339 if (trail_enabled && OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") {
340 warnings.push_back(RTR("Particle trails are only available when using the Forward+ or Mobile rendering backends."));
341 }
342
343 if (sub_emitter != NodePath() && OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") {
344 warnings.push_back(RTR("Particle sub-emitters are not available when using the GL Compatibility rendering backend."));
345 }
346
347 return warnings;
348}
349
350Rect2 GPUParticles2D::capture_rect() const {
351 AABB aabb = RS::get_singleton()->particles_get_current_aabb(particles);
352 Rect2 r;
353 r.position.x = aabb.position.x;
354 r.position.y = aabb.position.y;
355 r.size.x = aabb.size.x;
356 r.size.y = aabb.size.y;
357 return r;
358}
359
360void GPUParticles2D::set_texture(const Ref<Texture2D> &p_texture) {
361 if (texture.is_valid()) {
362 texture->disconnect_changed(callable_mp(this, &GPUParticles2D::_texture_changed));
363 }
364
365 texture = p_texture;
366
367 if (texture.is_valid()) {
368 texture->connect_changed(callable_mp(this, &GPUParticles2D::_texture_changed));
369 }
370 _update_collision_size();
371 queue_redraw();
372}
373
374Ref<Texture2D> GPUParticles2D::get_texture() const {
375 return texture;
376}
377
378void GPUParticles2D::_validate_property(PropertyInfo &p_property) const {
379}
380
381void GPUParticles2D::emit_particle(const Transform2D &p_transform2d, const Vector2 &p_velocity2d, const Color &p_color, const Color &p_custom, uint32_t p_emit_flags) {
382 Transform3D emit_transform;
383 emit_transform.basis.set_column(0, Vector3(p_transform2d.columns[0].x, p_transform2d.columns[0].y, 0));
384 emit_transform.basis.set_column(1, Vector3(p_transform2d.columns[1].x, p_transform2d.columns[1].y, 0));
385 emit_transform.set_origin(Vector3(p_transform2d.get_origin().x, p_transform2d.get_origin().y, 0));
386 Vector3 velocity = Vector3(p_velocity2d.x, p_velocity2d.y, 0);
387
388 RS::get_singleton()->particles_emit(particles, emit_transform, velocity, p_color, p_custom, p_emit_flags);
389}
390
391void GPUParticles2D::_attach_sub_emitter() {
392 Node *n = get_node_or_null(sub_emitter);
393 if (n) {
394 GPUParticles2D *sen = Object::cast_to<GPUParticles2D>(n);
395 if (sen && sen != this) {
396 RS::get_singleton()->particles_set_subemitter(particles, sen->particles);
397 }
398 }
399}
400
401void GPUParticles2D::_texture_changed() {
402 // Changes to the texture need to trigger an update to make
403 // the editor redraw the sprite with the updated texture.
404 if (texture.is_valid()) {
405 queue_redraw();
406 }
407}
408
409void GPUParticles2D::set_sub_emitter(const NodePath &p_path) {
410 if (is_inside_tree()) {
411 RS::get_singleton()->particles_set_subemitter(particles, RID());
412 }
413
414 sub_emitter = p_path;
415
416 if (is_inside_tree() && sub_emitter != NodePath()) {
417 _attach_sub_emitter();
418 }
419 update_configuration_warnings();
420}
421
422NodePath GPUParticles2D::get_sub_emitter() const {
423 return sub_emitter;
424}
425
426void GPUParticles2D::restart() {
427 RS::get_singleton()->particles_restart(particles);
428 RS::get_singleton()->particles_set_emitting(particles, true);
429
430 emitting = true;
431 active = true;
432 signal_canceled = false;
433 time = 0;
434 emission_time = lifetime;
435 active_time = lifetime * (2 - explosiveness_ratio);
436 if (one_shot) {
437 set_process_internal(true);
438 }
439}
440
441void GPUParticles2D::convert_from_particles(Node *p_particles) {
442 CPUParticles2D *cpu_particles = Object::cast_to<CPUParticles2D>(p_particles);
443 ERR_FAIL_NULL_MSG(cpu_particles, "Only CPUParticles2D nodes can be converted to GPUParticles2D.");
444
445 set_emitting(cpu_particles->is_emitting());
446 set_amount(cpu_particles->get_amount());
447 set_lifetime(cpu_particles->get_lifetime());
448 set_one_shot(cpu_particles->get_one_shot());
449 set_pre_process_time(cpu_particles->get_pre_process_time());
450 set_explosiveness_ratio(cpu_particles->get_explosiveness_ratio());
451 set_randomness_ratio(cpu_particles->get_randomness_ratio());
452 set_use_local_coordinates(cpu_particles->get_use_local_coordinates());
453 set_fixed_fps(cpu_particles->get_fixed_fps());
454 set_fractional_delta(cpu_particles->get_fractional_delta());
455 set_speed_scale(cpu_particles->get_speed_scale());
456 set_draw_order(DrawOrder(cpu_particles->get_draw_order()));
457 set_texture(cpu_particles->get_texture());
458
459 Ref<Material> mat = cpu_particles->get_material();
460 if (mat.is_valid()) {
461 set_material(mat);
462 }
463
464 Ref<ParticleProcessMaterial> proc_mat = memnew(ParticleProcessMaterial);
465 set_process_material(proc_mat);
466 Vector2 dir = cpu_particles->get_direction();
467 proc_mat->set_direction(Vector3(dir.x, dir.y, 0));
468 proc_mat->set_spread(cpu_particles->get_spread());
469 proc_mat->set_color(cpu_particles->get_color());
470
471 Ref<Gradient> color_grad = cpu_particles->get_color_ramp();
472 if (color_grad.is_valid()) {
473 Ref<GradientTexture1D> tex = memnew(GradientTexture1D);
474 tex->set_gradient(color_grad);
475 proc_mat->set_color_ramp(tex);
476 }
477
478 Ref<Gradient> color_init_grad = cpu_particles->get_color_initial_ramp();
479 if (color_init_grad.is_valid()) {
480 Ref<GradientTexture1D> tex = memnew(GradientTexture1D);
481 tex->set_gradient(color_init_grad);
482 proc_mat->set_color_initial_ramp(tex);
483 }
484
485 proc_mat->set_particle_flag(ParticleProcessMaterial::PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY, cpu_particles->get_particle_flag(CPUParticles2D::PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY));
486
487 proc_mat->set_emission_shape(ParticleProcessMaterial::EmissionShape(cpu_particles->get_emission_shape()));
488 proc_mat->set_emission_sphere_radius(cpu_particles->get_emission_sphere_radius());
489
490 Vector2 rect_extents = cpu_particles->get_emission_rect_extents();
491 proc_mat->set_emission_box_extents(Vector3(rect_extents.x, rect_extents.y, 0));
492
493 if (cpu_particles->get_split_scale()) {
494 Ref<CurveXYZTexture> scale3D = memnew(CurveXYZTexture);
495 scale3D->set_curve_x(cpu_particles->get_scale_curve_x());
496 scale3D->set_curve_y(cpu_particles->get_scale_curve_y());
497 proc_mat->set_param_texture(ParticleProcessMaterial::PARAM_SCALE, scale3D);
498 }
499
500 Vector2 gravity = cpu_particles->get_gravity();
501 proc_mat->set_gravity(Vector3(gravity.x, gravity.y, 0));
502 proc_mat->set_lifetime_randomness(cpu_particles->get_lifetime_randomness());
503
504#define CONVERT_PARAM(m_param) \
505 proc_mat->set_param_min(ParticleProcessMaterial::m_param, cpu_particles->get_param_min(CPUParticles2D::m_param)); \
506 { \
507 Ref<Curve> curve = cpu_particles->get_param_curve(CPUParticles2D::m_param); \
508 if (curve.is_valid()) { \
509 Ref<CurveTexture> tex = memnew(CurveTexture); \
510 tex->set_curve(curve); \
511 proc_mat->set_param_texture(ParticleProcessMaterial::m_param, tex); \
512 } \
513 } \
514 proc_mat->set_param_max(ParticleProcessMaterial::m_param, cpu_particles->get_param_max(CPUParticles2D::m_param));
515
516 CONVERT_PARAM(PARAM_INITIAL_LINEAR_VELOCITY);
517 CONVERT_PARAM(PARAM_ANGULAR_VELOCITY);
518 CONVERT_PARAM(PARAM_ORBIT_VELOCITY);
519 CONVERT_PARAM(PARAM_LINEAR_ACCEL);
520 CONVERT_PARAM(PARAM_RADIAL_ACCEL);
521 CONVERT_PARAM(PARAM_TANGENTIAL_ACCEL);
522 CONVERT_PARAM(PARAM_DAMPING);
523 CONVERT_PARAM(PARAM_ANGLE);
524 CONVERT_PARAM(PARAM_SCALE);
525 CONVERT_PARAM(PARAM_HUE_VARIATION);
526 CONVERT_PARAM(PARAM_ANIM_SPEED);
527 CONVERT_PARAM(PARAM_ANIM_OFFSET);
528
529#undef CONVERT_PARAM
530}
531
532void GPUParticles2D::_notification(int p_what) {
533 switch (p_what) {
534 case NOTIFICATION_DRAW: {
535 RID texture_rid;
536 Size2 size;
537 if (texture.is_valid()) {
538 texture_rid = texture->get_rid();
539 size = texture->get_size();
540 } else {
541 size = Size2(1, 1);
542 }
543
544 if (trail_enabled) {
545 RS::get_singleton()->mesh_clear(mesh);
546 PackedVector2Array points;
547 PackedVector2Array uvs;
548 PackedInt32Array bone_indices;
549 PackedFloat32Array bone_weights;
550 PackedInt32Array indices;
551
552 int total_segments = trail_sections * trail_section_subdivisions;
553 real_t depth = size.height * trail_sections;
554
555 for (int j = 0; j <= total_segments; j++) {
556 real_t v = j;
557 v /= total_segments;
558
559 real_t y = depth * v;
560 y = (depth * 0.5) - y;
561
562 int bone = j / trail_section_subdivisions;
563 real_t blend = 1.0 - real_t(j % trail_section_subdivisions) / real_t(trail_section_subdivisions);
564
565 real_t s = size.width;
566
567 points.push_back(Vector2(-s * 0.5, 0));
568 points.push_back(Vector2(+s * 0.5, 0));
569
570 uvs.push_back(Vector2(0, v));
571 uvs.push_back(Vector2(1, v));
572
573 for (int i = 0; i < 2; i++) {
574 bone_indices.push_back(bone);
575 bone_indices.push_back(MIN(trail_sections, bone + 1));
576 bone_indices.push_back(0);
577 bone_indices.push_back(0);
578
579 bone_weights.push_back(blend);
580 bone_weights.push_back(1.0 - blend);
581 bone_weights.push_back(0);
582 bone_weights.push_back(0);
583 }
584
585 if (j > 0) {
586 int base = j * 2 - 2;
587 indices.push_back(base + 0);
588 indices.push_back(base + 1);
589 indices.push_back(base + 2);
590
591 indices.push_back(base + 1);
592 indices.push_back(base + 3);
593 indices.push_back(base + 2);
594 }
595 }
596
597 Array arr;
598 arr.resize(RS::ARRAY_MAX);
599 arr[RS::ARRAY_VERTEX] = points;
600 arr[RS::ARRAY_TEX_UV] = uvs;
601 arr[RS::ARRAY_BONES] = bone_indices;
602 arr[RS::ARRAY_WEIGHTS] = bone_weights;
603 arr[RS::ARRAY_INDEX] = indices;
604
605 RS::get_singleton()->mesh_add_surface_from_arrays(mesh, RS::PRIMITIVE_TRIANGLES, arr, Array(), Dictionary(), RS::ARRAY_FLAG_USE_2D_VERTICES);
606
607 Vector<Transform3D> xforms;
608 for (int i = 0; i <= trail_sections; i++) {
609 Transform3D xform;
610 /*
611 xform.origin.y = depth / 2.0 - size.height * real_t(i);
612 xform.origin.y = -xform.origin.y; //bind is an inverse transform, so negate y */
613 xforms.push_back(xform);
614 }
615
616 RS::get_singleton()->particles_set_trail_bind_poses(particles, xforms);
617
618 } else {
619 RS::get_singleton()->mesh_clear(mesh);
620
621 Vector<Vector2> points = {
622 Vector2(-size.x / 2.0, -size.y / 2.0),
623 Vector2(size.x / 2.0, -size.y / 2.0),
624 Vector2(size.x / 2.0, size.y / 2.0),
625 Vector2(-size.x / 2.0, size.y / 2.0)
626 };
627
628 Vector<Vector2> uvs;
629 AtlasTexture *atlas_texure = Object::cast_to<AtlasTexture>(*texture);
630 if (atlas_texure && atlas_texure->get_atlas().is_valid()) {
631 Rect2 region_rect = atlas_texure->get_region();
632 Size2 atlas_size = atlas_texure->get_atlas()->get_size();
633 uvs.push_back(Vector2(region_rect.position.x / atlas_size.x, region_rect.position.y / atlas_size.y));
634 uvs.push_back(Vector2((region_rect.position.x + region_rect.size.x) / atlas_size.x, region_rect.position.y / atlas_size.y));
635 uvs.push_back(Vector2((region_rect.position.x + region_rect.size.x) / atlas_size.x, (region_rect.position.y + region_rect.size.y) / atlas_size.y));
636 uvs.push_back(Vector2(region_rect.position.x / atlas_size.x, (region_rect.position.y + region_rect.size.y) / atlas_size.y));
637 } else {
638 uvs.push_back(Vector2(0, 0));
639 uvs.push_back(Vector2(1, 0));
640 uvs.push_back(Vector2(1, 1));
641 uvs.push_back(Vector2(0, 1));
642 }
643
644 Vector<int> indices = { 0, 1, 2, 0, 2, 3 };
645
646 Array arr;
647 arr.resize(RS::ARRAY_MAX);
648 arr[RS::ARRAY_VERTEX] = points;
649 arr[RS::ARRAY_TEX_UV] = uvs;
650 arr[RS::ARRAY_INDEX] = indices;
651
652 RS::get_singleton()->mesh_add_surface_from_arrays(mesh, RS::PRIMITIVE_TRIANGLES, arr, Array(), Dictionary(), RS::ARRAY_FLAG_USE_2D_VERTICES);
653 RS::get_singleton()->particles_set_trail_bind_poses(particles, Vector<Transform3D>());
654 }
655 RS::get_singleton()->canvas_item_add_particles(get_canvas_item(), particles, texture_rid);
656
657#ifdef TOOLS_ENABLED
658 if (show_visibility_rect) {
659 draw_rect(visibility_rect, Color(0, 0.7, 0.9, 0.4), false);
660 }
661#endif
662 } break;
663
664 case NOTIFICATION_ENTER_TREE: {
665 if (sub_emitter != NodePath()) {
666 _attach_sub_emitter();
667 }
668 if (can_process()) {
669 RS::get_singleton()->particles_set_speed_scale(particles, speed_scale);
670 } else {
671 RS::get_singleton()->particles_set_speed_scale(particles, 0);
672 }
673 } break;
674
675 case NOTIFICATION_EXIT_TREE: {
676 RS::get_singleton()->particles_set_subemitter(particles, RID());
677 } break;
678
679 case NOTIFICATION_PAUSED:
680 case NOTIFICATION_UNPAUSED: {
681 if (is_inside_tree()) {
682 if (can_process()) {
683 RS::get_singleton()->particles_set_speed_scale(particles, speed_scale);
684 } else {
685 RS::get_singleton()->particles_set_speed_scale(particles, 0);
686 }
687 }
688 } break;
689
690 case NOTIFICATION_TRANSFORM_CHANGED: {
691 _update_particle_emission_transform();
692 } break;
693
694 case NOTIFICATION_INTERNAL_PROCESS: {
695 if (one_shot) {
696 time += get_process_delta_time();
697 if (time > emission_time) {
698 emitting = false;
699 if (!active) {
700 set_process_internal(false);
701 }
702 }
703 if (time > active_time) {
704 if (active && !signal_canceled) {
705 emit_signal(SceneStringNames::get_singleton()->finished);
706 }
707 active = false;
708 if (!emitting) {
709 set_process_internal(false);
710 }
711 }
712 }
713 } break;
714 }
715}
716
717void GPUParticles2D::_bind_methods() {
718 ClassDB::bind_method(D_METHOD("set_emitting", "emitting"), &GPUParticles2D::set_emitting);
719 ClassDB::bind_method(D_METHOD("set_amount", "amount"), &GPUParticles2D::set_amount);
720 ClassDB::bind_method(D_METHOD("set_lifetime", "secs"), &GPUParticles2D::set_lifetime);
721 ClassDB::bind_method(D_METHOD("set_one_shot", "secs"), &GPUParticles2D::set_one_shot);
722 ClassDB::bind_method(D_METHOD("set_pre_process_time", "secs"), &GPUParticles2D::set_pre_process_time);
723 ClassDB::bind_method(D_METHOD("set_explosiveness_ratio", "ratio"), &GPUParticles2D::set_explosiveness_ratio);
724 ClassDB::bind_method(D_METHOD("set_randomness_ratio", "ratio"), &GPUParticles2D::set_randomness_ratio);
725 ClassDB::bind_method(D_METHOD("set_visibility_rect", "visibility_rect"), &GPUParticles2D::set_visibility_rect);
726 ClassDB::bind_method(D_METHOD("set_use_local_coordinates", "enable"), &GPUParticles2D::set_use_local_coordinates);
727 ClassDB::bind_method(D_METHOD("set_fixed_fps", "fps"), &GPUParticles2D::set_fixed_fps);
728 ClassDB::bind_method(D_METHOD("set_fractional_delta", "enable"), &GPUParticles2D::set_fractional_delta);
729 ClassDB::bind_method(D_METHOD("set_interpolate", "enable"), &GPUParticles2D::set_interpolate);
730 ClassDB::bind_method(D_METHOD("set_process_material", "material"), &GPUParticles2D::set_process_material);
731 ClassDB::bind_method(D_METHOD("set_speed_scale", "scale"), &GPUParticles2D::set_speed_scale);
732 ClassDB::bind_method(D_METHOD("set_collision_base_size", "size"), &GPUParticles2D::set_collision_base_size);
733
734 ClassDB::bind_method(D_METHOD("is_emitting"), &GPUParticles2D::is_emitting);
735 ClassDB::bind_method(D_METHOD("get_amount"), &GPUParticles2D::get_amount);
736 ClassDB::bind_method(D_METHOD("get_lifetime"), &GPUParticles2D::get_lifetime);
737 ClassDB::bind_method(D_METHOD("get_one_shot"), &GPUParticles2D::get_one_shot);
738 ClassDB::bind_method(D_METHOD("get_pre_process_time"), &GPUParticles2D::get_pre_process_time);
739 ClassDB::bind_method(D_METHOD("get_explosiveness_ratio"), &GPUParticles2D::get_explosiveness_ratio);
740 ClassDB::bind_method(D_METHOD("get_randomness_ratio"), &GPUParticles2D::get_randomness_ratio);
741 ClassDB::bind_method(D_METHOD("get_visibility_rect"), &GPUParticles2D::get_visibility_rect);
742 ClassDB::bind_method(D_METHOD("get_use_local_coordinates"), &GPUParticles2D::get_use_local_coordinates);
743 ClassDB::bind_method(D_METHOD("get_fixed_fps"), &GPUParticles2D::get_fixed_fps);
744 ClassDB::bind_method(D_METHOD("get_fractional_delta"), &GPUParticles2D::get_fractional_delta);
745 ClassDB::bind_method(D_METHOD("get_interpolate"), &GPUParticles2D::get_interpolate);
746 ClassDB::bind_method(D_METHOD("get_process_material"), &GPUParticles2D::get_process_material);
747 ClassDB::bind_method(D_METHOD("get_speed_scale"), &GPUParticles2D::get_speed_scale);
748 ClassDB::bind_method(D_METHOD("get_collision_base_size"), &GPUParticles2D::get_collision_base_size);
749
750 ClassDB::bind_method(D_METHOD("set_draw_order", "order"), &GPUParticles2D::set_draw_order);
751 ClassDB::bind_method(D_METHOD("get_draw_order"), &GPUParticles2D::get_draw_order);
752
753 ClassDB::bind_method(D_METHOD("set_texture", "texture"), &GPUParticles2D::set_texture);
754 ClassDB::bind_method(D_METHOD("get_texture"), &GPUParticles2D::get_texture);
755
756 ClassDB::bind_method(D_METHOD("capture_rect"), &GPUParticles2D::capture_rect);
757
758 ClassDB::bind_method(D_METHOD("restart"), &GPUParticles2D::restart);
759
760 ClassDB::bind_method(D_METHOD("set_sub_emitter", "path"), &GPUParticles2D::set_sub_emitter);
761 ClassDB::bind_method(D_METHOD("get_sub_emitter"), &GPUParticles2D::get_sub_emitter);
762
763 ClassDB::bind_method(D_METHOD("emit_particle", "xform", "velocity", "color", "custom", "flags"), &GPUParticles2D::emit_particle);
764
765 ClassDB::bind_method(D_METHOD("set_trail_enabled", "enabled"), &GPUParticles2D::set_trail_enabled);
766 ClassDB::bind_method(D_METHOD("set_trail_lifetime", "secs"), &GPUParticles2D::set_trail_lifetime);
767
768 ClassDB::bind_method(D_METHOD("is_trail_enabled"), &GPUParticles2D::is_trail_enabled);
769 ClassDB::bind_method(D_METHOD("get_trail_lifetime"), &GPUParticles2D::get_trail_lifetime);
770
771 ClassDB::bind_method(D_METHOD("set_trail_sections", "sections"), &GPUParticles2D::set_trail_sections);
772 ClassDB::bind_method(D_METHOD("get_trail_sections"), &GPUParticles2D::get_trail_sections);
773
774 ClassDB::bind_method(D_METHOD("set_trail_section_subdivisions", "subdivisions"), &GPUParticles2D::set_trail_section_subdivisions);
775 ClassDB::bind_method(D_METHOD("get_trail_section_subdivisions"), &GPUParticles2D::get_trail_section_subdivisions);
776
777 ClassDB::bind_method(D_METHOD("convert_from_particles", "particles"), &GPUParticles2D::convert_from_particles);
778
779 ADD_SIGNAL(MethodInfo("finished"));
780
781 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting"), "set_emitting", "is_emitting");
782 ADD_PROPERTY_DEFAULT("emitting", true); // Workaround for doctool in headless mode, as dummy rasterizer always returns false.
783 ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_RANGE, "1,1000000,1,exp"), "set_amount", "get_amount");
784 ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "sub_emitter", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "GPUParticles2D"), "set_sub_emitter", "get_sub_emitter");
785 ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "process_material", PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial,ParticleProcessMaterial"), "set_process_material", "get_process_material");
786 ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture");
787 ADD_GROUP("Time", "");
788 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lifetime", PROPERTY_HINT_RANGE, "0.01,600.0,0.01,or_greater,suffix:s"), "set_lifetime", "get_lifetime");
789 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "one_shot"), "set_one_shot", "get_one_shot");
790 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "preprocess", PROPERTY_HINT_RANGE, "0.00,600.0,0.01,suffix:s"), "set_pre_process_time", "get_pre_process_time");
791 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed_scale", PROPERTY_HINT_RANGE, "0,64,0.01"), "set_speed_scale", "get_speed_scale");
792 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "explosiveness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_explosiveness_ratio", "get_explosiveness_ratio");
793 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "randomness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_randomness_ratio", "get_randomness_ratio");
794 ADD_PROPERTY(PropertyInfo(Variant::INT, "fixed_fps", PROPERTY_HINT_RANGE, "0,1000,1,suffix:FPS"), "set_fixed_fps", "get_fixed_fps");
795 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "interpolate"), "set_interpolate", "get_interpolate");
796 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fract_delta"), "set_fractional_delta", "get_fractional_delta");
797 ADD_GROUP("Collision", "collision_");
798 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision_base_size", PROPERTY_HINT_RANGE, "0,128,0.01,or_greater"), "set_collision_base_size", "get_collision_base_size");
799 ADD_GROUP("Drawing", "");
800 ADD_PROPERTY(PropertyInfo(Variant::RECT2, "visibility_rect", PROPERTY_HINT_NONE, "suffix:px"), "set_visibility_rect", "get_visibility_rect");
801 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "local_coords"), "set_use_local_coordinates", "get_use_local_coordinates");
802 ADD_PROPERTY(PropertyInfo(Variant::INT, "draw_order", PROPERTY_HINT_ENUM, "Index,Lifetime,Reverse Lifetime"), "set_draw_order", "get_draw_order");
803 ADD_GROUP("Trails", "trail_");
804 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "trail_enabled"), "set_trail_enabled", "is_trail_enabled");
805 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "trail_lifetime", PROPERTY_HINT_RANGE, "0.01,10,0.01,or_greater,suffix:s"), "set_trail_lifetime", "get_trail_lifetime");
806 ADD_PROPERTY(PropertyInfo(Variant::INT, "trail_sections", PROPERTY_HINT_RANGE, "2,128,1"), "set_trail_sections", "get_trail_sections");
807 ADD_PROPERTY(PropertyInfo(Variant::INT, "trail_section_subdivisions", PROPERTY_HINT_RANGE, "1,1024,1"), "set_trail_section_subdivisions", "get_trail_section_subdivisions");
808
809 BIND_ENUM_CONSTANT(DRAW_ORDER_INDEX);
810 BIND_ENUM_CONSTANT(DRAW_ORDER_LIFETIME);
811 BIND_ENUM_CONSTANT(DRAW_ORDER_REVERSE_LIFETIME);
812
813 BIND_ENUM_CONSTANT(EMIT_FLAG_POSITION);
814 BIND_ENUM_CONSTANT(EMIT_FLAG_ROTATION_SCALE);
815 BIND_ENUM_CONSTANT(EMIT_FLAG_VELOCITY);
816 BIND_ENUM_CONSTANT(EMIT_FLAG_COLOR);
817 BIND_ENUM_CONSTANT(EMIT_FLAG_CUSTOM);
818}
819
820GPUParticles2D::GPUParticles2D() {
821 particles = RS::get_singleton()->particles_create();
822 RS::get_singleton()->particles_set_mode(particles, RS::PARTICLES_MODE_2D);
823
824 mesh = RS::get_singleton()->mesh_create();
825 RS::get_singleton()->particles_set_draw_passes(particles, 1);
826 RS::get_singleton()->particles_set_draw_pass_mesh(particles, 0, mesh);
827
828 one_shot = false; // Needed so that set_emitting doesn't access uninitialized values
829 set_emitting(true);
830 set_one_shot(false);
831 set_amount(8);
832 set_lifetime(1);
833 set_fixed_fps(0);
834 set_fractional_delta(true);
835 set_interpolate(true);
836 set_pre_process_time(0);
837 set_explosiveness_ratio(0);
838 set_randomness_ratio(0);
839 set_visibility_rect(Rect2(Vector2(-100, -100), Vector2(200, 200)));
840 set_use_local_coordinates(false);
841 set_draw_order(DRAW_ORDER_LIFETIME);
842 set_speed_scale(1);
843 set_fixed_fps(30);
844 set_collision_base_size(collision_base_size);
845}
846
847GPUParticles2D::~GPUParticles2D() {
848 ERR_FAIL_NULL(RenderingServer::get_singleton());
849 RS::get_singleton()->free(particles);
850 RS::get_singleton()->free(mesh);
851}
852