| 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 | |