1 | /**************************************************************************/ |
2 | /* gpu_particles_3d.cpp */ |
3 | /**************************************************************************/ |
4 | /* This file is part of: */ |
5 | /* GODOT ENGINE */ |
6 | /* https://godotengine.org */ |
7 | /**************************************************************************/ |
8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
10 | /* */ |
11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
12 | /* a copy of this software and associated documentation files (the */ |
13 | /* "Software"), to deal in the Software without restriction, including */ |
14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
17 | /* the following conditions: */ |
18 | /* */ |
19 | /* The above copyright notice and this permission notice shall be */ |
20 | /* included in all copies or substantial portions of the Software. */ |
21 | /* */ |
22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
29 | /**************************************************************************/ |
30 | |
31 | #include "gpu_particles_3d.h" |
32 | |
33 | #include "scene/3d/cpu_particles_3d.h" |
34 | #include "scene/resources/curve_texture.h" |
35 | #include "scene/resources/gradient_texture.h" |
36 | #include "scene/resources/particle_process_material.h" |
37 | #include "scene/scene_string_names.h" |
38 | |
39 | AABB GPUParticles3D::get_aabb() const { |
40 | return AABB(); |
41 | } |
42 | |
43 | void GPUParticles3D::set_emitting(bool p_emitting) { |
44 | // Do not return even if `p_emitting == emitting` because `emitting` is just an approximation. |
45 | |
46 | if (p_emitting && one_shot) { |
47 | if (!active && !emitting) { |
48 | // Last cycle ended. |
49 | active = true; |
50 | time = 0; |
51 | signal_canceled = false; |
52 | emission_time = lifetime; |
53 | active_time = lifetime * (2 - explosiveness_ratio); |
54 | } else { |
55 | signal_canceled = true; |
56 | } |
57 | set_process_internal(true); |
58 | } else if (!p_emitting) { |
59 | if (one_shot) { |
60 | set_process_internal(true); |
61 | } else { |
62 | set_process_internal(false); |
63 | } |
64 | } |
65 | |
66 | emitting = p_emitting; |
67 | RS::get_singleton()->particles_set_emitting(particles, p_emitting); |
68 | } |
69 | |
70 | void GPUParticles3D::set_amount(int p_amount) { |
71 | ERR_FAIL_COND_MSG(p_amount < 1, "Amount of particles cannot be smaller than 1." ); |
72 | amount = p_amount; |
73 | RS::get_singleton()->particles_set_amount(particles, amount); |
74 | } |
75 | |
76 | void GPUParticles3D::set_lifetime(double p_lifetime) { |
77 | ERR_FAIL_COND_MSG(p_lifetime <= 0, "Particles lifetime must be greater than 0." ); |
78 | lifetime = p_lifetime; |
79 | RS::get_singleton()->particles_set_lifetime(particles, lifetime); |
80 | } |
81 | |
82 | void GPUParticles3D::set_one_shot(bool p_one_shot) { |
83 | one_shot = p_one_shot; |
84 | RS::get_singleton()->particles_set_one_shot(particles, one_shot); |
85 | |
86 | if (is_emitting()) { |
87 | set_process_internal(true); |
88 | if (!one_shot) { |
89 | RenderingServer::get_singleton()->particles_restart(particles); |
90 | } |
91 | } |
92 | |
93 | if (!one_shot) { |
94 | set_process_internal(false); |
95 | } |
96 | } |
97 | |
98 | void GPUParticles3D::set_pre_process_time(double p_time) { |
99 | pre_process_time = p_time; |
100 | RS::get_singleton()->particles_set_pre_process_time(particles, pre_process_time); |
101 | } |
102 | |
103 | void GPUParticles3D::set_explosiveness_ratio(real_t p_ratio) { |
104 | explosiveness_ratio = p_ratio; |
105 | RS::get_singleton()->particles_set_explosiveness_ratio(particles, explosiveness_ratio); |
106 | } |
107 | |
108 | void GPUParticles3D::set_randomness_ratio(real_t p_ratio) { |
109 | randomness_ratio = p_ratio; |
110 | RS::get_singleton()->particles_set_randomness_ratio(particles, randomness_ratio); |
111 | } |
112 | |
113 | void GPUParticles3D::set_visibility_aabb(const AABB &p_aabb) { |
114 | visibility_aabb = p_aabb; |
115 | RS::get_singleton()->particles_set_custom_aabb(particles, visibility_aabb); |
116 | update_gizmos(); |
117 | } |
118 | |
119 | void GPUParticles3D::set_use_local_coordinates(bool p_enable) { |
120 | local_coords = p_enable; |
121 | RS::get_singleton()->particles_set_use_local_coordinates(particles, local_coords); |
122 | } |
123 | |
124 | void GPUParticles3D::set_process_material(const Ref<Material> &p_material) { |
125 | process_material = p_material; |
126 | RID material_rid; |
127 | if (process_material.is_valid()) { |
128 | material_rid = process_material->get_rid(); |
129 | } |
130 | RS::get_singleton()->particles_set_process_material(particles, material_rid); |
131 | |
132 | update_configuration_warnings(); |
133 | } |
134 | |
135 | void GPUParticles3D::set_speed_scale(double p_scale) { |
136 | speed_scale = p_scale; |
137 | RS::get_singleton()->particles_set_speed_scale(particles, p_scale); |
138 | } |
139 | |
140 | void GPUParticles3D::set_collision_base_size(real_t p_size) { |
141 | collision_base_size = p_size; |
142 | RS::get_singleton()->particles_set_collision_base_size(particles, p_size); |
143 | } |
144 | |
145 | bool GPUParticles3D::is_emitting() const { |
146 | return emitting; |
147 | } |
148 | |
149 | int GPUParticles3D::get_amount() const { |
150 | return amount; |
151 | } |
152 | |
153 | double GPUParticles3D::get_lifetime() const { |
154 | return lifetime; |
155 | } |
156 | |
157 | bool GPUParticles3D::get_one_shot() const { |
158 | return one_shot; |
159 | } |
160 | |
161 | double GPUParticles3D::get_pre_process_time() const { |
162 | return pre_process_time; |
163 | } |
164 | |
165 | real_t GPUParticles3D::get_explosiveness_ratio() const { |
166 | return explosiveness_ratio; |
167 | } |
168 | |
169 | real_t GPUParticles3D::get_randomness_ratio() const { |
170 | return randomness_ratio; |
171 | } |
172 | |
173 | AABB GPUParticles3D::get_visibility_aabb() const { |
174 | return visibility_aabb; |
175 | } |
176 | |
177 | bool GPUParticles3D::get_use_local_coordinates() const { |
178 | return local_coords; |
179 | } |
180 | |
181 | Ref<Material> GPUParticles3D::get_process_material() const { |
182 | return process_material; |
183 | } |
184 | |
185 | double GPUParticles3D::get_speed_scale() const { |
186 | return speed_scale; |
187 | } |
188 | |
189 | real_t GPUParticles3D::get_collision_base_size() const { |
190 | return collision_base_size; |
191 | } |
192 | |
193 | void GPUParticles3D::set_draw_order(DrawOrder p_order) { |
194 | draw_order = p_order; |
195 | RS::get_singleton()->particles_set_draw_order(particles, RS::ParticlesDrawOrder(p_order)); |
196 | } |
197 | |
198 | void GPUParticles3D::set_trail_enabled(bool p_enabled) { |
199 | trail_enabled = p_enabled; |
200 | RS::get_singleton()->particles_set_trails(particles, trail_enabled, trail_lifetime); |
201 | update_configuration_warnings(); |
202 | } |
203 | |
204 | void GPUParticles3D::set_trail_lifetime(double p_seconds) { |
205 | ERR_FAIL_COND(p_seconds < 0.01); |
206 | trail_lifetime = p_seconds; |
207 | RS::get_singleton()->particles_set_trails(particles, trail_enabled, trail_lifetime); |
208 | } |
209 | |
210 | bool GPUParticles3D::is_trail_enabled() const { |
211 | return trail_enabled; |
212 | } |
213 | |
214 | double GPUParticles3D::get_trail_lifetime() const { |
215 | return trail_lifetime; |
216 | } |
217 | |
218 | GPUParticles3D::DrawOrder GPUParticles3D::get_draw_order() const { |
219 | return draw_order; |
220 | } |
221 | |
222 | void GPUParticles3D::set_draw_passes(int p_count) { |
223 | ERR_FAIL_COND(p_count < 1); |
224 | for (int i = p_count; i < draw_passes.size(); i++) { |
225 | set_draw_pass_mesh(i, Ref<Mesh>()); |
226 | } |
227 | draw_passes.resize(p_count); |
228 | RS::get_singleton()->particles_set_draw_passes(particles, p_count); |
229 | notify_property_list_changed(); |
230 | } |
231 | |
232 | int GPUParticles3D::get_draw_passes() const { |
233 | return draw_passes.size(); |
234 | } |
235 | |
236 | void GPUParticles3D::set_draw_pass_mesh(int p_pass, const Ref<Mesh> &p_mesh) { |
237 | ERR_FAIL_INDEX(p_pass, draw_passes.size()); |
238 | |
239 | if (Engine::get_singleton()->is_editor_hint() && draw_passes.write[p_pass].is_valid()) { |
240 | draw_passes.write[p_pass]->disconnect_changed(callable_mp((Node *)this, &Node::update_configuration_warnings)); |
241 | } |
242 | |
243 | draw_passes.write[p_pass] = p_mesh; |
244 | |
245 | if (Engine::get_singleton()->is_editor_hint() && draw_passes.write[p_pass].is_valid()) { |
246 | draw_passes.write[p_pass]->connect_changed(callable_mp((Node *)this, &Node::update_configuration_warnings), CONNECT_DEFERRED); |
247 | } |
248 | |
249 | RID mesh_rid; |
250 | if (p_mesh.is_valid()) { |
251 | mesh_rid = p_mesh->get_rid(); |
252 | } |
253 | |
254 | RS::get_singleton()->particles_set_draw_pass_mesh(particles, p_pass, mesh_rid); |
255 | |
256 | _skinning_changed(); |
257 | update_configuration_warnings(); |
258 | } |
259 | |
260 | Ref<Mesh> GPUParticles3D::get_draw_pass_mesh(int p_pass) const { |
261 | ERR_FAIL_INDEX_V(p_pass, draw_passes.size(), Ref<Mesh>()); |
262 | |
263 | return draw_passes[p_pass]; |
264 | } |
265 | |
266 | void GPUParticles3D::set_fixed_fps(int p_count) { |
267 | fixed_fps = p_count; |
268 | RS::get_singleton()->particles_set_fixed_fps(particles, p_count); |
269 | } |
270 | |
271 | int GPUParticles3D::get_fixed_fps() const { |
272 | return fixed_fps; |
273 | } |
274 | |
275 | void GPUParticles3D::set_fractional_delta(bool p_enable) { |
276 | fractional_delta = p_enable; |
277 | RS::get_singleton()->particles_set_fractional_delta(particles, p_enable); |
278 | } |
279 | |
280 | bool GPUParticles3D::get_fractional_delta() const { |
281 | return fractional_delta; |
282 | } |
283 | |
284 | void GPUParticles3D::set_interpolate(bool p_enable) { |
285 | interpolate = p_enable; |
286 | RS::get_singleton()->particles_set_interpolate(particles, p_enable); |
287 | } |
288 | |
289 | bool GPUParticles3D::get_interpolate() const { |
290 | return interpolate; |
291 | } |
292 | |
293 | PackedStringArray GPUParticles3D::get_configuration_warnings() const { |
294 | PackedStringArray warnings = GeometryInstance3D::get_configuration_warnings(); |
295 | |
296 | bool meshes_found = false; |
297 | bool anim_material_found = false; |
298 | |
299 | for (int i = 0; i < draw_passes.size(); i++) { |
300 | if (draw_passes[i].is_valid()) { |
301 | meshes_found = true; |
302 | for (int j = 0; j < draw_passes[i]->get_surface_count(); j++) { |
303 | anim_material_found = Object::cast_to<ShaderMaterial>(draw_passes[i]->surface_get_material(j).ptr()) != nullptr; |
304 | BaseMaterial3D *spat = Object::cast_to<BaseMaterial3D>(draw_passes[i]->surface_get_material(j).ptr()); |
305 | anim_material_found = anim_material_found || (spat && spat->get_billboard_mode() == StandardMaterial3D::BILLBOARD_PARTICLES); |
306 | } |
307 | if (anim_material_found) { |
308 | break; |
309 | } |
310 | } |
311 | } |
312 | |
313 | anim_material_found = anim_material_found || Object::cast_to<ShaderMaterial>(get_material_override().ptr()) != nullptr; |
314 | { |
315 | BaseMaterial3D *spat = Object::cast_to<BaseMaterial3D>(get_material_override().ptr()); |
316 | anim_material_found = anim_material_found || (spat && spat->get_billboard_mode() == BaseMaterial3D::BILLBOARD_PARTICLES); |
317 | } |
318 | |
319 | if (!meshes_found) { |
320 | warnings.push_back(RTR("Nothing is visible because meshes have not been assigned to draw passes." )); |
321 | } |
322 | |
323 | if (process_material.is_null()) { |
324 | warnings.push_back(RTR("A material to process the particles is not assigned, so no behavior is imprinted." )); |
325 | } else { |
326 | const ParticleProcessMaterial *process = Object::cast_to<ParticleProcessMaterial>(process_material.ptr()); |
327 | if (!anim_material_found && process && |
328 | (process->get_param_max(ParticleProcessMaterial::PARAM_ANIM_SPEED) != 0.0 || process->get_param_max(ParticleProcessMaterial::PARAM_ANIM_OFFSET) != 0.0 || |
329 | process->get_param_texture(ParticleProcessMaterial::PARAM_ANIM_SPEED).is_valid() || process->get_param_texture(ParticleProcessMaterial::PARAM_ANIM_OFFSET).is_valid())) { |
330 | warnings.push_back(RTR("Particles animation requires the usage of a BaseMaterial3D whose Billboard Mode is set to \"Particle Billboard\"." )); |
331 | } |
332 | } |
333 | |
334 | if (trail_enabled) { |
335 | int dp_count = 0; |
336 | bool missing_trails = false; |
337 | bool no_materials = false; |
338 | |
339 | for (int i = 0; i < draw_passes.size(); i++) { |
340 | Ref<Mesh> draw_pass = draw_passes[i]; |
341 | if (draw_pass.is_valid() && draw_pass->get_builtin_bind_pose_count() > 0) { |
342 | dp_count++; |
343 | } |
344 | |
345 | if (draw_pass.is_valid()) { |
346 | int mats_found = 0; |
347 | for (int j = 0; j < draw_passes[i]->get_surface_count(); j++) { |
348 | BaseMaterial3D *spat = Object::cast_to<BaseMaterial3D>(draw_passes[i]->surface_get_material(j).ptr()); |
349 | if (spat) { |
350 | mats_found++; |
351 | } |
352 | if (spat && !spat->get_flag(BaseMaterial3D::FLAG_PARTICLE_TRAILS_MODE)) { |
353 | missing_trails = true; |
354 | } |
355 | } |
356 | |
357 | if (mats_found != draw_passes[i]->get_surface_count()) { |
358 | no_materials = true; |
359 | } |
360 | } |
361 | } |
362 | |
363 | BaseMaterial3D *spat = Object::cast_to<BaseMaterial3D>(get_material_override().ptr()); |
364 | if (spat) { |
365 | no_materials = false; |
366 | } |
367 | if (spat && !spat->get_flag(BaseMaterial3D::FLAG_PARTICLE_TRAILS_MODE)) { |
368 | missing_trails = true; |
369 | } |
370 | |
371 | if (dp_count && skin.is_valid()) { |
372 | warnings.push_back(RTR("Using Trail meshes with a skin causes Skin to override Trail poses. Suggest removing the Skin." )); |
373 | } else if (dp_count == 0 && skin.is_null()) { |
374 | warnings.push_back(RTR("Trails active, but neither Trail meshes or a Skin were found." )); |
375 | } else if (dp_count > 1) { |
376 | warnings.push_back(RTR("Only one Trail mesh is supported. If you want to use more than a single mesh, a Skin is needed (see documentation)." )); |
377 | } |
378 | |
379 | if ((dp_count || !skin.is_null()) && (missing_trails || no_materials)) { |
380 | warnings.push_back(RTR("Trails enabled, but one or more mesh materials are either missing or not set for trails rendering." )); |
381 | } |
382 | if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility" ) { |
383 | warnings.push_back(RTR("Particle trails are only available when using the Forward+ or Mobile rendering backends." )); |
384 | } |
385 | } |
386 | |
387 | if (sub_emitter != NodePath() && OS::get_singleton()->get_current_rendering_method() == "gl_compatibility" ) { |
388 | warnings.push_back(RTR("Particle sub-emitters are only available when using the Forward+ or Mobile rendering backends." )); |
389 | } |
390 | |
391 | return warnings; |
392 | } |
393 | |
394 | void GPUParticles3D::restart() { |
395 | RenderingServer::get_singleton()->particles_restart(particles); |
396 | RenderingServer::get_singleton()->particles_set_emitting(particles, true); |
397 | |
398 | emitting = true; |
399 | active = true; |
400 | signal_canceled = false; |
401 | time = 0; |
402 | emission_time = lifetime * (1 - explosiveness_ratio); |
403 | active_time = lifetime * (2 - explosiveness_ratio); |
404 | if (one_shot) { |
405 | set_process_internal(true); |
406 | } |
407 | } |
408 | |
409 | AABB GPUParticles3D::capture_aabb() const { |
410 | return RS::get_singleton()->particles_get_current_aabb(particles); |
411 | } |
412 | |
413 | void GPUParticles3D::_validate_property(PropertyInfo &p_property) const { |
414 | if (p_property.name.begins_with("draw_pass_" )) { |
415 | int index = p_property.name.get_slicec('_', 2).to_int() - 1; |
416 | if (index >= draw_passes.size()) { |
417 | p_property.usage = PROPERTY_USAGE_NONE; |
418 | return; |
419 | } |
420 | } |
421 | } |
422 | |
423 | void GPUParticles3D::emit_particle(const Transform3D &p_transform, const Vector3 &p_velocity, const Color &p_color, const Color &p_custom, uint32_t p_emit_flags) { |
424 | RS::get_singleton()->particles_emit(particles, p_transform, p_velocity, p_color, p_custom, p_emit_flags); |
425 | } |
426 | |
427 | void GPUParticles3D::_attach_sub_emitter() { |
428 | Node *n = get_node_or_null(sub_emitter); |
429 | if (n) { |
430 | GPUParticles3D *sen = Object::cast_to<GPUParticles3D>(n); |
431 | if (sen && sen != this) { |
432 | RS::get_singleton()->particles_set_subemitter(particles, sen->particles); |
433 | } |
434 | } |
435 | } |
436 | |
437 | void GPUParticles3D::set_sub_emitter(const NodePath &p_path) { |
438 | if (is_inside_tree()) { |
439 | RS::get_singleton()->particles_set_subemitter(particles, RID()); |
440 | } |
441 | |
442 | sub_emitter = p_path; |
443 | |
444 | if (is_inside_tree() && sub_emitter != NodePath()) { |
445 | _attach_sub_emitter(); |
446 | } |
447 | update_configuration_warnings(); |
448 | } |
449 | |
450 | NodePath GPUParticles3D::get_sub_emitter() const { |
451 | return sub_emitter; |
452 | } |
453 | |
454 | void GPUParticles3D::_notification(int p_what) { |
455 | switch (p_what) { |
456 | // Use internal process when emitting and one_shot is on so that when |
457 | // the shot ends the editor can properly update. |
458 | case NOTIFICATION_INTERNAL_PROCESS: { |
459 | if (one_shot) { |
460 | time += get_process_delta_time(); |
461 | if (time > emission_time) { |
462 | emitting = false; |
463 | if (!active) { |
464 | set_process_internal(false); |
465 | } |
466 | } |
467 | if (time > active_time) { |
468 | if (active && !signal_canceled) { |
469 | emit_signal(SceneStringNames::get_singleton()->finished); |
470 | } |
471 | active = false; |
472 | if (!emitting) { |
473 | set_process_internal(false); |
474 | } |
475 | } |
476 | } |
477 | } break; |
478 | |
479 | case NOTIFICATION_ENTER_TREE: { |
480 | if (sub_emitter != NodePath()) { |
481 | _attach_sub_emitter(); |
482 | } |
483 | if (can_process()) { |
484 | RS::get_singleton()->particles_set_speed_scale(particles, speed_scale); |
485 | } else { |
486 | RS::get_singleton()->particles_set_speed_scale(particles, 0); |
487 | } |
488 | } break; |
489 | |
490 | case NOTIFICATION_EXIT_TREE: { |
491 | RS::get_singleton()->particles_set_subemitter(particles, RID()); |
492 | } break; |
493 | |
494 | case NOTIFICATION_PAUSED: |
495 | case NOTIFICATION_UNPAUSED: { |
496 | if (is_inside_tree()) { |
497 | if (can_process()) { |
498 | RS::get_singleton()->particles_set_speed_scale(particles, speed_scale); |
499 | } else { |
500 | RS::get_singleton()->particles_set_speed_scale(particles, 0); |
501 | } |
502 | } |
503 | } break; |
504 | |
505 | case NOTIFICATION_VISIBILITY_CHANGED: { |
506 | // Make sure particles are updated before rendering occurs if they were active before. |
507 | if (is_visible_in_tree() && !RS::get_singleton()->particles_is_inactive(particles)) { |
508 | RS::get_singleton()->particles_request_process(particles); |
509 | } |
510 | } break; |
511 | } |
512 | } |
513 | |
514 | void GPUParticles3D::_skinning_changed() { |
515 | Vector<Transform3D> xforms; |
516 | if (skin.is_valid()) { |
517 | xforms.resize(skin->get_bind_count()); |
518 | for (int i = 0; i < skin->get_bind_count(); i++) { |
519 | xforms.write[i] = skin->get_bind_pose(i); |
520 | } |
521 | } else { |
522 | for (int i = 0; i < draw_passes.size(); i++) { |
523 | Ref<Mesh> draw_pass = draw_passes[i]; |
524 | if (draw_pass.is_valid() && draw_pass->get_builtin_bind_pose_count() > 0) { |
525 | xforms.resize(draw_pass->get_builtin_bind_pose_count()); |
526 | for (int j = 0; j < draw_pass->get_builtin_bind_pose_count(); j++) { |
527 | xforms.write[j] = draw_pass->get_builtin_bind_pose(j); |
528 | } |
529 | break; |
530 | } |
531 | } |
532 | } |
533 | |
534 | RS::get_singleton()->particles_set_trail_bind_poses(particles, xforms); |
535 | update_configuration_warnings(); |
536 | } |
537 | |
538 | void GPUParticles3D::set_skin(const Ref<Skin> &p_skin) { |
539 | skin = p_skin; |
540 | _skinning_changed(); |
541 | } |
542 | |
543 | Ref<Skin> GPUParticles3D::get_skin() const { |
544 | return skin; |
545 | } |
546 | |
547 | void GPUParticles3D::set_transform_align(TransformAlign p_align) { |
548 | ERR_FAIL_INDEX(uint32_t(p_align), 4); |
549 | transform_align = p_align; |
550 | RS::get_singleton()->particles_set_transform_align(particles, RS::ParticlesTransformAlign(transform_align)); |
551 | } |
552 | |
553 | GPUParticles3D::TransformAlign GPUParticles3D::get_transform_align() const { |
554 | return transform_align; |
555 | } |
556 | |
557 | void GPUParticles3D::convert_from_particles(Node *p_particles) { |
558 | CPUParticles3D *cpu_particles = Object::cast_to<CPUParticles3D>(p_particles); |
559 | ERR_FAIL_NULL_MSG(cpu_particles, "Only CPUParticles3D nodes can be converted to GPUParticles3D." ); |
560 | |
561 | set_emitting(cpu_particles->is_emitting()); |
562 | set_amount(cpu_particles->get_amount()); |
563 | set_lifetime(cpu_particles->get_lifetime()); |
564 | set_one_shot(cpu_particles->get_one_shot()); |
565 | set_pre_process_time(cpu_particles->get_pre_process_time()); |
566 | set_explosiveness_ratio(cpu_particles->get_explosiveness_ratio()); |
567 | set_randomness_ratio(cpu_particles->get_randomness_ratio()); |
568 | set_use_local_coordinates(cpu_particles->get_use_local_coordinates()); |
569 | set_fixed_fps(cpu_particles->get_fixed_fps()); |
570 | set_fractional_delta(cpu_particles->get_fractional_delta()); |
571 | set_speed_scale(cpu_particles->get_speed_scale()); |
572 | set_draw_order(DrawOrder(cpu_particles->get_draw_order())); |
573 | set_draw_pass_mesh(0, cpu_particles->get_mesh()); |
574 | |
575 | Ref<ParticleProcessMaterial> proc_mat = memnew(ParticleProcessMaterial); |
576 | set_process_material(proc_mat); |
577 | |
578 | proc_mat->set_direction(cpu_particles->get_direction()); |
579 | proc_mat->set_spread(cpu_particles->get_spread()); |
580 | proc_mat->set_flatness(cpu_particles->get_flatness()); |
581 | proc_mat->set_color(cpu_particles->get_color()); |
582 | |
583 | Ref<Gradient> grad = cpu_particles->get_color_ramp(); |
584 | if (grad.is_valid()) { |
585 | Ref<GradientTexture1D> tex = memnew(GradientTexture1D); |
586 | tex->set_gradient(grad); |
587 | proc_mat->set_color_ramp(tex); |
588 | } |
589 | |
590 | Ref<Gradient> grad_init = cpu_particles->get_color_initial_ramp(); |
591 | if (grad_init.is_valid()) { |
592 | Ref<GradientTexture1D> tex = memnew(GradientTexture1D); |
593 | tex->set_gradient(grad_init); |
594 | proc_mat->set_color_initial_ramp(tex); |
595 | } |
596 | |
597 | proc_mat->set_particle_flag(ParticleProcessMaterial::PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY, cpu_particles->get_particle_flag(CPUParticles3D::PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY)); |
598 | proc_mat->set_particle_flag(ParticleProcessMaterial::PARTICLE_FLAG_ROTATE_Y, cpu_particles->get_particle_flag(CPUParticles3D::PARTICLE_FLAG_ROTATE_Y)); |
599 | proc_mat->set_particle_flag(ParticleProcessMaterial::PARTICLE_FLAG_DISABLE_Z, cpu_particles->get_particle_flag(CPUParticles3D::PARTICLE_FLAG_DISABLE_Z)); |
600 | |
601 | proc_mat->set_emission_shape(ParticleProcessMaterial::EmissionShape(cpu_particles->get_emission_shape())); |
602 | proc_mat->set_emission_sphere_radius(cpu_particles->get_emission_sphere_radius()); |
603 | proc_mat->set_emission_box_extents(cpu_particles->get_emission_box_extents()); |
604 | |
605 | if (cpu_particles->get_split_scale()) { |
606 | Ref<CurveXYZTexture> scale3D = memnew(CurveXYZTexture); |
607 | scale3D->set_curve_x(cpu_particles->get_scale_curve_x()); |
608 | scale3D->set_curve_y(cpu_particles->get_scale_curve_y()); |
609 | scale3D->set_curve_z(cpu_particles->get_scale_curve_z()); |
610 | proc_mat->set_param_texture(ParticleProcessMaterial::PARAM_SCALE, scale3D); |
611 | } |
612 | |
613 | proc_mat->set_gravity(cpu_particles->get_gravity()); |
614 | proc_mat->set_lifetime_randomness(cpu_particles->get_lifetime_randomness()); |
615 | |
616 | #define CONVERT_PARAM(m_param) \ |
617 | proc_mat->set_param_min(ParticleProcessMaterial::m_param, cpu_particles->get_param_min(CPUParticles3D::m_param)); \ |
618 | { \ |
619 | Ref<Curve> curve = cpu_particles->get_param_curve(CPUParticles3D::m_param); \ |
620 | if (curve.is_valid()) { \ |
621 | Ref<CurveTexture> tex = memnew(CurveTexture); \ |
622 | tex->set_curve(curve); \ |
623 | proc_mat->set_param_texture(ParticleProcessMaterial::m_param, tex); \ |
624 | } \ |
625 | } \ |
626 | proc_mat->set_param_max(ParticleProcessMaterial::m_param, cpu_particles->get_param_max(CPUParticles3D::m_param)); |
627 | |
628 | CONVERT_PARAM(PARAM_INITIAL_LINEAR_VELOCITY); |
629 | CONVERT_PARAM(PARAM_ANGULAR_VELOCITY); |
630 | CONVERT_PARAM(PARAM_ORBIT_VELOCITY); |
631 | CONVERT_PARAM(PARAM_LINEAR_ACCEL); |
632 | CONVERT_PARAM(PARAM_RADIAL_ACCEL); |
633 | CONVERT_PARAM(PARAM_TANGENTIAL_ACCEL); |
634 | CONVERT_PARAM(PARAM_DAMPING); |
635 | CONVERT_PARAM(PARAM_ANGLE); |
636 | CONVERT_PARAM(PARAM_SCALE); |
637 | CONVERT_PARAM(PARAM_HUE_VARIATION); |
638 | CONVERT_PARAM(PARAM_ANIM_SPEED); |
639 | CONVERT_PARAM(PARAM_ANIM_OFFSET); |
640 | |
641 | #undef CONVERT_PARAM |
642 | } |
643 | |
644 | void GPUParticles3D::_bind_methods() { |
645 | ClassDB::bind_method(D_METHOD("set_emitting" , "emitting" ), &GPUParticles3D::set_emitting); |
646 | ClassDB::bind_method(D_METHOD("set_amount" , "amount" ), &GPUParticles3D::set_amount); |
647 | ClassDB::bind_method(D_METHOD("set_lifetime" , "secs" ), &GPUParticles3D::set_lifetime); |
648 | ClassDB::bind_method(D_METHOD("set_one_shot" , "enable" ), &GPUParticles3D::set_one_shot); |
649 | ClassDB::bind_method(D_METHOD("set_pre_process_time" , "secs" ), &GPUParticles3D::set_pre_process_time); |
650 | ClassDB::bind_method(D_METHOD("set_explosiveness_ratio" , "ratio" ), &GPUParticles3D::set_explosiveness_ratio); |
651 | ClassDB::bind_method(D_METHOD("set_randomness_ratio" , "ratio" ), &GPUParticles3D::set_randomness_ratio); |
652 | ClassDB::bind_method(D_METHOD("set_visibility_aabb" , "aabb" ), &GPUParticles3D::set_visibility_aabb); |
653 | ClassDB::bind_method(D_METHOD("set_use_local_coordinates" , "enable" ), &GPUParticles3D::set_use_local_coordinates); |
654 | ClassDB::bind_method(D_METHOD("set_fixed_fps" , "fps" ), &GPUParticles3D::set_fixed_fps); |
655 | ClassDB::bind_method(D_METHOD("set_fractional_delta" , "enable" ), &GPUParticles3D::set_fractional_delta); |
656 | ClassDB::bind_method(D_METHOD("set_interpolate" , "enable" ), &GPUParticles3D::set_interpolate); |
657 | ClassDB::bind_method(D_METHOD("set_process_material" , "material" ), &GPUParticles3D::set_process_material); |
658 | ClassDB::bind_method(D_METHOD("set_speed_scale" , "scale" ), &GPUParticles3D::set_speed_scale); |
659 | ClassDB::bind_method(D_METHOD("set_collision_base_size" , "size" ), &GPUParticles3D::set_collision_base_size); |
660 | |
661 | ClassDB::bind_method(D_METHOD("is_emitting" ), &GPUParticles3D::is_emitting); |
662 | ClassDB::bind_method(D_METHOD("get_amount" ), &GPUParticles3D::get_amount); |
663 | ClassDB::bind_method(D_METHOD("get_lifetime" ), &GPUParticles3D::get_lifetime); |
664 | ClassDB::bind_method(D_METHOD("get_one_shot" ), &GPUParticles3D::get_one_shot); |
665 | ClassDB::bind_method(D_METHOD("get_pre_process_time" ), &GPUParticles3D::get_pre_process_time); |
666 | ClassDB::bind_method(D_METHOD("get_explosiveness_ratio" ), &GPUParticles3D::get_explosiveness_ratio); |
667 | ClassDB::bind_method(D_METHOD("get_randomness_ratio" ), &GPUParticles3D::get_randomness_ratio); |
668 | ClassDB::bind_method(D_METHOD("get_visibility_aabb" ), &GPUParticles3D::get_visibility_aabb); |
669 | ClassDB::bind_method(D_METHOD("get_use_local_coordinates" ), &GPUParticles3D::get_use_local_coordinates); |
670 | ClassDB::bind_method(D_METHOD("get_fixed_fps" ), &GPUParticles3D::get_fixed_fps); |
671 | ClassDB::bind_method(D_METHOD("get_fractional_delta" ), &GPUParticles3D::get_fractional_delta); |
672 | ClassDB::bind_method(D_METHOD("get_interpolate" ), &GPUParticles3D::get_interpolate); |
673 | ClassDB::bind_method(D_METHOD("get_process_material" ), &GPUParticles3D::get_process_material); |
674 | ClassDB::bind_method(D_METHOD("get_speed_scale" ), &GPUParticles3D::get_speed_scale); |
675 | ClassDB::bind_method(D_METHOD("get_collision_base_size" ), &GPUParticles3D::get_collision_base_size); |
676 | |
677 | ClassDB::bind_method(D_METHOD("set_draw_order" , "order" ), &GPUParticles3D::set_draw_order); |
678 | |
679 | ClassDB::bind_method(D_METHOD("get_draw_order" ), &GPUParticles3D::get_draw_order); |
680 | |
681 | ClassDB::bind_method(D_METHOD("set_draw_passes" , "passes" ), &GPUParticles3D::set_draw_passes); |
682 | ClassDB::bind_method(D_METHOD("set_draw_pass_mesh" , "pass" , "mesh" ), &GPUParticles3D::set_draw_pass_mesh); |
683 | |
684 | ClassDB::bind_method(D_METHOD("get_draw_passes" ), &GPUParticles3D::get_draw_passes); |
685 | ClassDB::bind_method(D_METHOD("get_draw_pass_mesh" , "pass" ), &GPUParticles3D::get_draw_pass_mesh); |
686 | |
687 | ClassDB::bind_method(D_METHOD("set_skin" , "skin" ), &GPUParticles3D::set_skin); |
688 | ClassDB::bind_method(D_METHOD("get_skin" ), &GPUParticles3D::get_skin); |
689 | |
690 | ClassDB::bind_method(D_METHOD("restart" ), &GPUParticles3D::restart); |
691 | ClassDB::bind_method(D_METHOD("capture_aabb" ), &GPUParticles3D::capture_aabb); |
692 | |
693 | ClassDB::bind_method(D_METHOD("set_sub_emitter" , "path" ), &GPUParticles3D::set_sub_emitter); |
694 | ClassDB::bind_method(D_METHOD("get_sub_emitter" ), &GPUParticles3D::get_sub_emitter); |
695 | |
696 | ClassDB::bind_method(D_METHOD("emit_particle" , "xform" , "velocity" , "color" , "custom" , "flags" ), &GPUParticles3D::emit_particle); |
697 | |
698 | ClassDB::bind_method(D_METHOD("set_trail_enabled" , "enabled" ), &GPUParticles3D::set_trail_enabled); |
699 | ClassDB::bind_method(D_METHOD("set_trail_lifetime" , "secs" ), &GPUParticles3D::set_trail_lifetime); |
700 | |
701 | ClassDB::bind_method(D_METHOD("is_trail_enabled" ), &GPUParticles3D::is_trail_enabled); |
702 | ClassDB::bind_method(D_METHOD("get_trail_lifetime" ), &GPUParticles3D::get_trail_lifetime); |
703 | |
704 | ClassDB::bind_method(D_METHOD("set_transform_align" , "align" ), &GPUParticles3D::set_transform_align); |
705 | ClassDB::bind_method(D_METHOD("get_transform_align" ), &GPUParticles3D::get_transform_align); |
706 | |
707 | ClassDB::bind_method(D_METHOD("convert_from_particles" , "particles" ), &GPUParticles3D::convert_from_particles); |
708 | |
709 | ADD_SIGNAL(MethodInfo("finished" )); |
710 | |
711 | ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting" ), "set_emitting" , "is_emitting" ); |
712 | ADD_PROPERTY_DEFAULT("emitting" , true); // Workaround for doctool in headless mode, as dummy rasterizer always returns false. |
713 | ADD_PROPERTY(PropertyInfo(Variant::INT, "amount" , PROPERTY_HINT_RANGE, "1,1000000,1,exp" ), "set_amount" , "get_amount" ); |
714 | ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "sub_emitter" , PROPERTY_HINT_NODE_PATH_VALID_TYPES, "GPUParticles3D" ), "set_sub_emitter" , "get_sub_emitter" ); |
715 | ADD_GROUP("Time" , "" ); |
716 | ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lifetime" , PROPERTY_HINT_RANGE, "0.01,600.0,0.01,or_greater,exp,suffix:s" ), "set_lifetime" , "get_lifetime" ); |
717 | ADD_PROPERTY(PropertyInfo(Variant::BOOL, "one_shot" ), "set_one_shot" , "get_one_shot" ); |
718 | ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "preprocess" , PROPERTY_HINT_RANGE, "0.00,600.0,0.01,exp,suffix:s" ), "set_pre_process_time" , "get_pre_process_time" ); |
719 | ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed_scale" , PROPERTY_HINT_RANGE, "0,64,0.01" ), "set_speed_scale" , "get_speed_scale" ); |
720 | ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "explosiveness" , PROPERTY_HINT_RANGE, "0,1,0.01" ), "set_explosiveness_ratio" , "get_explosiveness_ratio" ); |
721 | ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "randomness" , PROPERTY_HINT_RANGE, "0,1,0.01" ), "set_randomness_ratio" , "get_randomness_ratio" ); |
722 | ADD_PROPERTY(PropertyInfo(Variant::INT, "fixed_fps" , PROPERTY_HINT_RANGE, "0,1000,1,suffix:FPS" ), "set_fixed_fps" , "get_fixed_fps" ); |
723 | ADD_PROPERTY(PropertyInfo(Variant::BOOL, "interpolate" ), "set_interpolate" , "get_interpolate" ); |
724 | ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fract_delta" ), "set_fractional_delta" , "get_fractional_delta" ); |
725 | ADD_GROUP("Collision" , "collision_" ); |
726 | ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision_base_size" , PROPERTY_HINT_RANGE, "0,128,0.01,or_greater,suffix:m" ), "set_collision_base_size" , "get_collision_base_size" ); |
727 | ADD_GROUP("Drawing" , "" ); |
728 | ADD_PROPERTY(PropertyInfo(Variant::AABB, "visibility_aabb" , PROPERTY_HINT_NONE, "suffix:m" ), "set_visibility_aabb" , "get_visibility_aabb" ); |
729 | ADD_PROPERTY(PropertyInfo(Variant::BOOL, "local_coords" ), "set_use_local_coordinates" , "get_use_local_coordinates" ); |
730 | ADD_PROPERTY(PropertyInfo(Variant::INT, "draw_order" , PROPERTY_HINT_ENUM, "Index,Lifetime,Reverse Lifetime,View Depth" ), "set_draw_order" , "get_draw_order" ); |
731 | ADD_PROPERTY(PropertyInfo(Variant::INT, "transform_align" , PROPERTY_HINT_ENUM, "Disabled,Z-Billboard,Y to Velocity,Z-Billboard + Y to Velocity" ), "set_transform_align" , "get_transform_align" ); |
732 | ADD_GROUP("Trails" , "trail_" ); |
733 | ADD_PROPERTY(PropertyInfo(Variant::BOOL, "trail_enabled" ), "set_trail_enabled" , "is_trail_enabled" ); |
734 | 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" ); |
735 | ADD_GROUP("Process Material" , "" ); |
736 | ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "process_material" , PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial,ParticleProcessMaterial" ), "set_process_material" , "get_process_material" ); |
737 | ADD_GROUP("Draw Passes" , "draw_" ); |
738 | ADD_PROPERTY(PropertyInfo(Variant::INT, "draw_passes" , PROPERTY_HINT_RANGE, "0," + itos(MAX_DRAW_PASSES) + ",1" ), "set_draw_passes" , "get_draw_passes" ); |
739 | for (int i = 0; i < MAX_DRAW_PASSES; i++) { |
740 | ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "draw_pass_" + itos(i + 1), PROPERTY_HINT_RESOURCE_TYPE, "Mesh" ), "set_draw_pass_mesh" , "get_draw_pass_mesh" , i); |
741 | } |
742 | ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "draw_skin" , PROPERTY_HINT_RESOURCE_TYPE, "Skin" ), "set_skin" , "get_skin" ); |
743 | |
744 | BIND_ENUM_CONSTANT(DRAW_ORDER_INDEX); |
745 | BIND_ENUM_CONSTANT(DRAW_ORDER_LIFETIME); |
746 | BIND_ENUM_CONSTANT(DRAW_ORDER_REVERSE_LIFETIME); |
747 | BIND_ENUM_CONSTANT(DRAW_ORDER_VIEW_DEPTH); |
748 | |
749 | BIND_ENUM_CONSTANT(EMIT_FLAG_POSITION); |
750 | BIND_ENUM_CONSTANT(EMIT_FLAG_ROTATION_SCALE); |
751 | BIND_ENUM_CONSTANT(EMIT_FLAG_VELOCITY); |
752 | BIND_ENUM_CONSTANT(EMIT_FLAG_COLOR); |
753 | BIND_ENUM_CONSTANT(EMIT_FLAG_CUSTOM); |
754 | |
755 | BIND_CONSTANT(MAX_DRAW_PASSES); |
756 | |
757 | BIND_ENUM_CONSTANT(TRANSFORM_ALIGN_DISABLED); |
758 | BIND_ENUM_CONSTANT(TRANSFORM_ALIGN_Z_BILLBOARD); |
759 | BIND_ENUM_CONSTANT(TRANSFORM_ALIGN_Y_TO_VELOCITY); |
760 | BIND_ENUM_CONSTANT(TRANSFORM_ALIGN_Z_BILLBOARD_Y_TO_VELOCITY); |
761 | } |
762 | |
763 | GPUParticles3D::GPUParticles3D() { |
764 | particles = RS::get_singleton()->particles_create(); |
765 | RS::get_singleton()->particles_set_mode(particles, RS::PARTICLES_MODE_3D); |
766 | set_base(particles); |
767 | one_shot = false; // Needed so that set_emitting doesn't access uninitialized values |
768 | set_emitting(true); |
769 | set_one_shot(false); |
770 | set_amount(8); |
771 | set_lifetime(1); |
772 | set_fixed_fps(30); |
773 | set_fractional_delta(true); |
774 | set_interpolate(true); |
775 | set_pre_process_time(0); |
776 | set_explosiveness_ratio(0); |
777 | set_randomness_ratio(0); |
778 | set_trail_lifetime(0.3); |
779 | set_visibility_aabb(AABB(Vector3(-4, -4, -4), Vector3(8, 8, 8))); |
780 | set_use_local_coordinates(false); |
781 | set_draw_passes(1); |
782 | set_draw_order(DRAW_ORDER_INDEX); |
783 | set_speed_scale(1); |
784 | set_collision_base_size(collision_base_size); |
785 | set_transform_align(TRANSFORM_ALIGN_DISABLED); |
786 | } |
787 | |
788 | GPUParticles3D::~GPUParticles3D() { |
789 | ERR_FAIL_NULL(RenderingServer::get_singleton()); |
790 | RS::get_singleton()->free(particles); |
791 | } |
792 | |