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 | |
44 | void 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 | |
71 | void 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 | |
77 | void 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 | |
83 | void 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 | |
99 | void 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 | |
104 | void 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 | |
109 | void 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 | |
114 | void 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 | |
127 | void 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 | |
136 | void 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 | |
146 | void 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 | |
163 | void 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 | |
172 | void 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 | |
179 | void 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 | |
187 | void 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 |
196 | void 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 | |
202 | bool GPUParticles2D::is_trail_enabled() const { |
203 | return trail_enabled; |
204 | } |
205 | |
206 | double GPUParticles2D::get_trail_lifetime() const { |
207 | return trail_lifetime; |
208 | } |
209 | |
210 | void 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 | |
220 | void GPUParticles2D::set_collision_base_size(real_t p_size) { |
221 | collision_base_size = p_size; |
222 | _update_collision_size(); |
223 | } |
224 | |
225 | real_t GPUParticles2D::get_collision_base_size() const { |
226 | return collision_base_size; |
227 | } |
228 | |
229 | void 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 | |
234 | bool GPUParticles2D::is_emitting() const { |
235 | return emitting; |
236 | } |
237 | |
238 | int GPUParticles2D::get_amount() const { |
239 | return amount; |
240 | } |
241 | |
242 | double GPUParticles2D::get_lifetime() const { |
243 | return lifetime; |
244 | } |
245 | |
246 | int GPUParticles2D::get_trail_sections() const { |
247 | return trail_sections; |
248 | } |
249 | int GPUParticles2D::get_trail_section_subdivisions() const { |
250 | return trail_section_subdivisions; |
251 | } |
252 | |
253 | bool GPUParticles2D::get_one_shot() const { |
254 | return one_shot; |
255 | } |
256 | |
257 | double GPUParticles2D::get_pre_process_time() const { |
258 | return pre_process_time; |
259 | } |
260 | |
261 | real_t GPUParticles2D::get_explosiveness_ratio() const { |
262 | return explosiveness_ratio; |
263 | } |
264 | |
265 | real_t GPUParticles2D::get_randomness_ratio() const { |
266 | return randomness_ratio; |
267 | } |
268 | |
269 | Rect2 GPUParticles2D::get_visibility_rect() const { |
270 | return visibility_rect; |
271 | } |
272 | |
273 | bool GPUParticles2D::get_use_local_coordinates() const { |
274 | return local_coords; |
275 | } |
276 | |
277 | Ref<Material> GPUParticles2D::get_process_material() const { |
278 | return process_material; |
279 | } |
280 | |
281 | double GPUParticles2D::get_speed_scale() const { |
282 | return speed_scale; |
283 | } |
284 | |
285 | void 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 | |
290 | GPUParticles2D::DrawOrder GPUParticles2D::get_draw_order() const { |
291 | return draw_order; |
292 | } |
293 | |
294 | void 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 | |
299 | int GPUParticles2D::get_fixed_fps() const { |
300 | return fixed_fps; |
301 | } |
302 | |
303 | void 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 | |
308 | bool GPUParticles2D::get_fractional_delta() const { |
309 | return fractional_delta; |
310 | } |
311 | |
312 | void GPUParticles2D::set_interpolate(bool p_enable) { |
313 | interpolate = p_enable; |
314 | RS::get_singleton()->particles_set_interpolate(particles, p_enable); |
315 | } |
316 | |
317 | bool GPUParticles2D::get_interpolate() const { |
318 | return interpolate; |
319 | } |
320 | |
321 | PackedStringArray 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 | |
350 | Rect2 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 | |
360 | void 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 | |
374 | Ref<Texture2D> GPUParticles2D::get_texture() const { |
375 | return texture; |
376 | } |
377 | |
378 | void GPUParticles2D::_validate_property(PropertyInfo &p_property) const { |
379 | } |
380 | |
381 | void 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 | |
391 | void 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 | |
401 | void 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 | |
409 | void 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 | |
422 | NodePath GPUParticles2D::get_sub_emitter() const { |
423 | return sub_emitter; |
424 | } |
425 | |
426 | void 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 | |
441 | void 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 | |
532 | void 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 | |
717 | void 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 | |
820 | GPUParticles2D::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 | |
847 | GPUParticles2D::~GPUParticles2D() { |
848 | ERR_FAIL_NULL(RenderingServer::get_singleton()); |
849 | RS::get_singleton()->free(particles); |
850 | RS::get_singleton()->free(mesh); |
851 | } |
852 | |