diff options
author | Rémi Verschelde <rverschelde@gmail.com> | 2020-10-09 19:28:29 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-10-09 19:28:29 +0200 |
commit | b724d3851a49b033a8bc24bc1c73a3aa29eb0390 (patch) | |
tree | 7d20274c657c5f154186b690c1c0a67ca0174a9f | |
parent | c35005ba25473ea8fa48aadbd1687984c76457cf (diff) | |
parent | 26f5bd245c535fec5bfdd51a0f939d0a51179d85 (diff) |
Merge pull request #42628 from reduz/particle-collisions
Implement GPU Particle Collisions
32 files changed, 2947 insertions, 102 deletions
diff --git a/core/math/vector2.h b/core/math/vector2.h index f41bcc15bc..c2a2656e72 100644 --- a/core/math/vector2.h +++ b/core/math/vector2.h @@ -65,6 +65,14 @@ struct Vector2 { real_t length() const; real_t length_squared() const; + Vector2 min(const Vector2 &p_vector2) const { + return Vector2(MIN(x, p_vector2.x), MIN(y, p_vector2.y)); + } + + Vector2 max(const Vector2 &p_vector2) const { + return Vector2(MAX(x, p_vector2.x), MAX(y, p_vector2.y)); + } + real_t distance_to(const Vector2 &p_vector2) const; real_t distance_squared_to(const Vector2 &p_vector2) const; real_t angle_to(const Vector2 &p_vector2) const; diff --git a/core/thread_work_pool.h b/core/thread_work_pool.h index e21d3974ee..661060aa3f 100644 --- a/core/thread_work_pool.h +++ b/core/thread_work_pool.h @@ -73,13 +73,15 @@ class ThreadWorkPool { ThreadData *threads = nullptr; uint32_t thread_count = 0; + BaseWork *current_work = nullptr; static void _thread_function(ThreadData *p_thread); public: template <class C, class M, class U> - void do_work(uint32_t p_elements, C *p_instance, M p_method, U p_userdata) { + void begin_work(uint32_t p_elements, C *p_instance, M p_method, U p_userdata) { ERR_FAIL_COND(!threads); //never initialized + ERR_FAIL_COND(current_work != nullptr); index.store(0); @@ -90,16 +92,37 @@ public: w->index = &index; w->max_elements = p_elements; + current_work = w; + for (uint32_t i = 0; i < thread_count; i++) { threads[i].work = w; threads[i].start.post(); } + } + + bool is_working() const { + return current_work != nullptr; + } + + uint32_t get_work_index() const { + return index; + } + + void end_work() { + ERR_FAIL_COND(current_work == nullptr); for (uint32_t i = 0; i < thread_count; i++) { threads[i].completed.wait(); threads[i].work = nullptr; } - memdelete(w); + memdelete(current_work); + current_work = nullptr; + } + + template <class C, class M, class U> + void do_work(uint32_t p_elements, C *p_instance, M p_method, U p_userdata) { + begin_work(p_elements, p_instance, p_method, p_userdata); + end_work(); } void init(int p_thread_count = -1); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 8edda3123b..73562e0695 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -128,6 +128,7 @@ #include "editor/plugins/gi_probe_editor_plugin.h" #include "editor/plugins/gpu_particles_2d_editor_plugin.h" #include "editor/plugins/gpu_particles_3d_editor_plugin.h" +#include "editor/plugins/gpu_particles_collision_sdf_editor_plugin.h" #include "editor/plugins/gradient_editor_plugin.h" #include "editor/plugins/item_list_editor_plugin.h" #include "editor/plugins/light_occluder_2d_editor_plugin.h" @@ -6635,6 +6636,7 @@ EditorNode::EditorNode() { add_editor_plugin(memnew(PhysicalBone3DEditorPlugin(this))); add_editor_plugin(memnew(MeshEditorPlugin(this))); add_editor_plugin(memnew(MaterialEditorPlugin(this))); + add_editor_plugin(memnew(GPUParticlesCollisionSDFEditorPlugin(this))); for (int i = 0; i < EditorPlugins::get_plugin_count(); i++) { add_editor_plugin(EditorPlugins::create(i, this)); diff --git a/editor/import/resource_importer_layered_texture.cpp b/editor/import/resource_importer_layered_texture.cpp index bbf62596d0..ac068c05cf 100644 --- a/editor/import/resource_importer_layered_texture.cpp +++ b/editor/import/resource_importer_layered_texture.cpp @@ -51,7 +51,7 @@ String ResourceImporterLayeredTexture::get_importer_name() const { return "cubemap_array_texture"; } break; case MODE_3D: { - return "cubemap_3d_texture"; + return "3d_texture"; } break; } diff --git a/editor/node_3d_editor_gizmos.cpp b/editor/node_3d_editor_gizmos.cpp index 6e5fb6389d..397a958d8f 100644 --- a/editor/node_3d_editor_gizmos.cpp +++ b/editor/node_3d_editor_gizmos.cpp @@ -41,6 +41,7 @@ #include "scene/3d/decal.h" #include "scene/3d/gi_probe.h" #include "scene/3d/gpu_particles_3d.h" +#include "scene/3d/gpu_particles_collision_3d.h" #include "scene/3d/light_3d.h" #include "scene/3d/lightmap_probe.h" #include "scene/3d/listener_3d.h" @@ -2455,6 +2456,266 @@ void GPUParticles3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { //// +//// + +GPUParticlesCollision3DGizmoPlugin::GPUParticlesCollision3DGizmoPlugin() { + Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/particle_collision", Color(0.5, 0.7, 1)); + create_material("shape_material", gizmo_color); + gizmo_color.a = 0.15; + create_material("shape_material_internal", gizmo_color); + + create_handle_material("handles"); +} + +bool GPUParticlesCollision3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { + return (Object::cast_to<GPUParticlesCollision3D>(p_spatial) != nullptr) || (Object::cast_to<GPUParticlesAttractor3D>(p_spatial) != nullptr); +} + +String GPUParticlesCollision3DGizmoPlugin::get_name() const { + return "GPUParticlesCollision3D"; +} + +int GPUParticlesCollision3DGizmoPlugin::get_priority() const { + return -1; +} + +String GPUParticlesCollision3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_idx) const { + const Node3D *cs = p_gizmo->get_spatial_node(); + + if (Object::cast_to<GPUParticlesCollisionSphere>(cs) || Object::cast_to<GPUParticlesAttractorSphere>(cs)) { + return "Radius"; + } + + if (Object::cast_to<GPUParticlesCollisionBox>(cs) || Object::cast_to<GPUParticlesAttractorBox>(cs) || Object::cast_to<GPUParticlesAttractorVectorField>(cs) || Object::cast_to<GPUParticlesCollisionSDF>(cs) || Object::cast_to<GPUParticlesCollisionHeightField>(cs)) { + return "Extents"; + } + + return ""; +} + +Variant GPUParticlesCollision3DGizmoPlugin::get_handle_value(EditorNode3DGizmo *p_gizmo, int p_idx) const { + const Node3D *cs = p_gizmo->get_spatial_node(); + + if (Object::cast_to<GPUParticlesCollisionSphere>(cs) || Object::cast_to<GPUParticlesAttractorSphere>(cs)) { + return p_gizmo->get_spatial_node()->call("get_radius"); + } + + if (Object::cast_to<GPUParticlesCollisionBox>(cs) || Object::cast_to<GPUParticlesAttractorBox>(cs) || Object::cast_to<GPUParticlesAttractorVectorField>(cs) || Object::cast_to<GPUParticlesCollisionSDF>(cs) || Object::cast_to<GPUParticlesCollisionHeightField>(cs)) { + return Vector3(p_gizmo->get_spatial_node()->call("get_extents")); + } + + return Variant(); +} + +void GPUParticlesCollision3DGizmoPlugin::set_handle(EditorNode3DGizmo *p_gizmo, int p_idx, Camera3D *p_camera, const Point2 &p_point) { + Node3D *sn = p_gizmo->get_spatial_node(); + + Transform gt = sn->get_global_transform(); + Transform gi = gt.affine_inverse(); + + Vector3 ray_from = p_camera->project_ray_origin(p_point); + Vector3 ray_dir = p_camera->project_ray_normal(p_point); + + Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 4096) }; + + if (Object::cast_to<GPUParticlesCollisionSphere>(sn) || Object::cast_to<GPUParticlesAttractorSphere>(sn)) { + Vector3 ra, rb; + Geometry3D::get_closest_points_between_segments(Vector3(), Vector3(4096, 0, 0), sg[0], sg[1], ra, rb); + float d = ra.x; + if (Node3DEditor::get_singleton()->is_snap_enabled()) { + d = Math::stepify(d, Node3DEditor::get_singleton()->get_translate_snap()); + } + + if (d < 0.001) { + d = 0.001; + } + + sn->call("set_radius", d); + } + + if (Object::cast_to<GPUParticlesCollisionBox>(sn) || Object::cast_to<GPUParticlesAttractorBox>(sn) || Object::cast_to<GPUParticlesAttractorVectorField>(sn) || Object::cast_to<GPUParticlesCollisionSDF>(sn) || Object::cast_to<GPUParticlesCollisionHeightField>(sn)) { + Vector3 axis; + axis[p_idx] = 1.0; + Vector3 ra, rb; + Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb); + float d = ra[p_idx]; + if (Node3DEditor::get_singleton()->is_snap_enabled()) { + d = Math::stepify(d, Node3DEditor::get_singleton()->get_translate_snap()); + } + + if (d < 0.001) { + d = 0.001; + } + + Vector3 he = sn->call("get_extents"); + he[p_idx] = d; + sn->call("set_extents", he); + } +} + +void GPUParticlesCollision3DGizmoPlugin::commit_handle(EditorNode3DGizmo *p_gizmo, int p_idx, const Variant &p_restore, bool p_cancel) { + Node3D *sn = p_gizmo->get_spatial_node(); + + if (Object::cast_to<GPUParticlesCollisionSphere>(sn) || Object::cast_to<GPUParticlesAttractorSphere>(sn)) { + if (p_cancel) { + sn->call("set_radius", p_restore); + return; + } + + UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Change Radius")); + ur->add_do_method(sn, "set_radius", sn->call("get_radius")); + ur->add_undo_method(sn, "set_radius", p_restore); + ur->commit_action(); + } + + if (Object::cast_to<GPUParticlesCollisionBox>(sn) || Object::cast_to<GPUParticlesAttractorBox>(sn) || Object::cast_to<GPUParticlesAttractorVectorField>(sn) || Object::cast_to<GPUParticlesCollisionSDF>(sn) || Object::cast_to<GPUParticlesCollisionHeightField>(sn)) { + if (p_cancel) { + sn->call("set_extents", p_restore); + return; + } + + UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Change Box Shape Extents")); + ur->add_do_method(sn, "set_extents", sn->call("get_extents")); + ur->add_undo_method(sn, "set_extents", p_restore); + ur->commit_action(); + } +} + +void GPUParticlesCollision3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { + Node3D *cs = p_gizmo->get_spatial_node(); + + print_line("redraw request " + itos(cs != nullptr)); + p_gizmo->clear(); + + const Ref<Material> material = + get_material("shape_material", p_gizmo); + const Ref<Material> material_internal = + get_material("shape_material_internal", p_gizmo); + + Ref<Material> handles_material = get_material("handles"); + + if (Object::cast_to<GPUParticlesCollisionSphere>(cs) || Object::cast_to<GPUParticlesAttractorSphere>(cs)) { + float r = cs->call("get_radius"); + + Vector<Vector3> points; + + for (int i = 0; i <= 360; i++) { + float ra = Math::deg2rad((float)i); + float rb = Math::deg2rad((float)i + 1); + Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * r; + Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * r; + + points.push_back(Vector3(a.x, 0, a.y)); + points.push_back(Vector3(b.x, 0, b.y)); + points.push_back(Vector3(0, a.x, a.y)); + points.push_back(Vector3(0, b.x, b.y)); + points.push_back(Vector3(a.x, a.y, 0)); + points.push_back(Vector3(b.x, b.y, 0)); + } + + Vector<Vector3> collision_segments; + + for (int i = 0; i < 64; i++) { + float ra = i * Math_PI * 2.0 / 64.0; + float rb = (i + 1) * Math_PI * 2.0 / 64.0; + Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * r; + Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * r; + + collision_segments.push_back(Vector3(a.x, 0, a.y)); + collision_segments.push_back(Vector3(b.x, 0, b.y)); + collision_segments.push_back(Vector3(0, a.x, a.y)); + collision_segments.push_back(Vector3(0, b.x, b.y)); + collision_segments.push_back(Vector3(a.x, a.y, 0)); + collision_segments.push_back(Vector3(b.x, b.y, 0)); + } + + p_gizmo->add_lines(points, material); + p_gizmo->add_collision_segments(collision_segments); + Vector<Vector3> handles; + handles.push_back(Vector3(r, 0, 0)); + p_gizmo->add_handles(handles, handles_material); + } + + if (Object::cast_to<GPUParticlesCollisionBox>(cs) || Object::cast_to<GPUParticlesAttractorBox>(cs) || Object::cast_to<GPUParticlesAttractorVectorField>(cs) || Object::cast_to<GPUParticlesCollisionSDF>(cs) || Object::cast_to<GPUParticlesCollisionHeightField>(cs)) { + Vector<Vector3> lines; + AABB aabb; + aabb.position = -cs->call("get_extents").operator Vector3(); + aabb.size = aabb.position * -2; + + for (int i = 0; i < 12; i++) { + Vector3 a, b; + aabb.get_edge(i, a, b); + lines.push_back(a); + lines.push_back(b); + } + + Vector<Vector3> handles; + + for (int i = 0; i < 3; i++) { + Vector3 ax; + ax[i] = cs->call("get_extents").operator Vector3()[i]; + handles.push_back(ax); + } + + p_gizmo->add_lines(lines, material); + p_gizmo->add_collision_segments(lines); + p_gizmo->add_handles(handles, handles_material); + + GPUParticlesCollisionSDF *col_sdf = Object::cast_to<GPUParticlesCollisionSDF>(cs); + if (col_sdf) { + static const int subdivs[GPUParticlesCollisionSDF::RESOLUTION_MAX] = { 16, 32, 64, 128, 256, 512 }; + int subdiv = subdivs[col_sdf->get_resolution()]; + float cell_size = aabb.get_longest_axis_size() / subdiv; + + lines.clear(); + + for (int i = 1; i < subdiv; i++) { + for (int j = 0; j < 3; j++) { + if (cell_size * i > aabb.size[j]) { + continue; + } + + Vector2 dir; + dir[j] = 1.0; + Vector2 ta, tb; + int j_n1 = (j + 1) % 3; + int j_n2 = (j + 2) % 3; + ta[j_n1] = 1.0; + tb[j_n2] = 1.0; + + for (int k = 0; k < 4; k++) { + Vector3 from = aabb.position, to = aabb.position; + from[j] += cell_size * i; + to[j] += cell_size * i; + + if (k & 1) { + to[j_n1] += aabb.size[j_n1]; + } else { + to[j_n2] += aabb.size[j_n2]; + } + + if (k & 2) { + from[j_n1] += aabb.size[j_n1]; + from[j_n2] += aabb.size[j_n2]; + } + + lines.push_back(from); + lines.push_back(to); + } + } + } + + p_gizmo->add_lines(lines, material_internal); + } + } +} + +///// + +//// + ReflectionProbeGizmoPlugin::ReflectionProbeGizmoPlugin() { Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/reflection_probe", Color(0.6, 1, 0.5)); diff --git a/editor/node_3d_editor_gizmos.h b/editor/node_3d_editor_gizmos.h index c7aae39a45..4826054643 100644 --- a/editor/node_3d_editor_gizmos.h +++ b/editor/node_3d_editor_gizmos.h @@ -253,6 +253,23 @@ public: GPUParticles3DGizmoPlugin(); }; +class GPUParticlesCollision3DGizmoPlugin : public EditorNode3DGizmoPlugin { + GDCLASS(GPUParticlesCollision3DGizmoPlugin, EditorNode3DGizmoPlugin); + +public: + bool has_gizmo(Node3D *p_spatial) override; + String get_name() const override; + int get_priority() const override; + void redraw(EditorNode3DGizmo *p_gizmo) override; + + String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_idx) const override; + Variant get_handle_value(EditorNode3DGizmo *p_gizmo, int p_idx) const override; + void set_handle(EditorNode3DGizmo *p_gizmo, int p_idx, Camera3D *p_camera, const Point2 &p_point) override; + void commit_handle(EditorNode3DGizmo *p_gizmo, int p_idx, const Variant &p_restore, bool p_cancel = false) override; + + GPUParticlesCollision3DGizmoPlugin(); +}; + class ReflectionProbeGizmoPlugin : public EditorNode3DGizmoPlugin { GDCLASS(ReflectionProbeGizmoPlugin, EditorNode3DGizmoPlugin); diff --git a/editor/plugins/gpu_particles_collision_sdf_editor_plugin.cpp b/editor/plugins/gpu_particles_collision_sdf_editor_plugin.cpp new file mode 100644 index 0000000000..0288645f91 --- /dev/null +++ b/editor/plugins/gpu_particles_collision_sdf_editor_plugin.cpp @@ -0,0 +1,201 @@ +/*************************************************************************/ +/* gpu_particles_collision_sdf_editor_plugin.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "gpu_particles_collision_sdf_editor_plugin.h" + +void GPUParticlesCollisionSDFEditorPlugin::_bake() { + if (col_sdf) { + if (col_sdf->get_texture().is_null() || !col_sdf->get_texture()->get_path().is_resource_file()) { + String path = get_tree()->get_edited_scene_root()->get_filename(); + if (path == String()) { + path = "res://" + col_sdf->get_name() + "_data.exr"; + } else { + String ext = path.get_extension(); + path = path.get_basename() + "." + col_sdf->get_name() + "_data.exr"; + } + probe_file->set_current_path(path); + probe_file->popup_file_dialog(); + return; + } + + _sdf_save_path_and_bake(col_sdf->get_texture()->get_path()); + } +} + +void GPUParticlesCollisionSDFEditorPlugin::edit(Object *p_object) { + GPUParticlesCollisionSDF *s = Object::cast_to<GPUParticlesCollisionSDF>(p_object); + if (!s) { + return; + } + + col_sdf = s; +} + +bool GPUParticlesCollisionSDFEditorPlugin::handles(Object *p_object) const { + return p_object->is_class("GPUParticlesCollisionSDF"); +} + +void GPUParticlesCollisionSDFEditorPlugin::_notification(int p_what) { + if (p_what == NOTIFICATION_PROCESS) { + if (!col_sdf) { + return; + } + + const Vector3i size = col_sdf->get_estimated_cell_size(); + String text = vformat(String::utf8("%d × %d × %d"), size.x, size.y, size.z); + int data_size = 2; + + const double size_mb = size.x * size.y * size.z * data_size / (1024.0 * 1024.0); + text += " - " + vformat(TTR("VRAM Size: %s MB"), String::num(size_mb, 2)); + + if (bake_info->get_text() == text) { + return; + } + + // Color the label depending on the estimated performance level. + Color color; + if (size_mb <= 16.0 + CMP_EPSILON) { + // Fast. + color = bake_info->get_theme_color("success_color", "Editor"); + } else if (size_mb <= 64.0 + CMP_EPSILON) { + // Medium. + color = bake_info->get_theme_color("warning_color", "Editor"); + } else { + // Slow. + color = bake_info->get_theme_color("error_color", "Editor"); + } + bake_info->add_theme_color_override("font_color", color); + + bake_info->set_text(text); + } +} + +void GPUParticlesCollisionSDFEditorPlugin::make_visible(bool p_visible) { + if (p_visible) { + bake_hb->show(); + set_process(true); + } else { + bake_hb->hide(); + set_process(false); + } +} + +EditorProgress *GPUParticlesCollisionSDFEditorPlugin::tmp_progress = nullptr; + +void GPUParticlesCollisionSDFEditorPlugin::bake_func_begin(int p_steps) { + ERR_FAIL_COND(tmp_progress != nullptr); + + tmp_progress = memnew(EditorProgress("bake_sdf", TTR("Bake SDF"), p_steps)); +} + +void GPUParticlesCollisionSDFEditorPlugin::bake_func_step(int p_step, const String &p_description) { + ERR_FAIL_COND(tmp_progress == nullptr); + tmp_progress->step(p_description, p_step, false); +} + +void GPUParticlesCollisionSDFEditorPlugin::bake_func_end() { + ERR_FAIL_COND(tmp_progress == nullptr); + memdelete(tmp_progress); + tmp_progress = nullptr; +} + +void GPUParticlesCollisionSDFEditorPlugin::_sdf_save_path_and_bake(const String &p_path) { + probe_file->hide(); + if (col_sdf) { + Ref<Image> bake_img = col_sdf->bake(); + if (bake_img.is_null()) { + EditorNode::get_singleton()->show_warning("Bake Error."); + return; + } + + Ref<ConfigFile> config; + + config.instance(); + if (FileAccess::exists(p_path + ".import")) { + config->load(p_path + ".import"); + } + + config->set_value("remap", "importer", "3d_texture"); + config->set_value("remap", "type", "StreamTexture3D"); + if (!config->has_section_key("params", "compress/mode")) { + config->set_value("params", "compress/mode", 3); //user may want another compression, so leave it be + } + config->set_value("params", "compress/channel_pack", 1); + config->set_value("params", "mipmaps/generate", false); + config->set_value("params", "slices/horizontal", 1); + config->set_value("params", "slices/vertical", bake_img->get_meta("depth")); + + config->save(p_path + ".import"); + + Error err = bake_img->save_exr(p_path, false); + ERR_FAIL_COND(err); + ResourceLoader::import(p_path); + Ref<Texture> t = ResourceLoader::load(p_path); //if already loaded, it will be updated on refocus? + ERR_FAIL_COND(t.is_null()); + + col_sdf->set_texture(t); + } +} + +void GPUParticlesCollisionSDFEditorPlugin::_bind_methods() { +} + +GPUParticlesCollisionSDFEditorPlugin::GPUParticlesCollisionSDFEditorPlugin(EditorNode *p_node) { + editor = p_node; + bake_hb = memnew(HBoxContainer); + bake_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + bake_hb->hide(); + bake = memnew(Button); + bake->set_flat(true); + bake->set_icon(editor->get_gui_base()->get_theme_icon("Bake", "EditorIcons")); + bake->set_text(TTR("Bake SDF")); + bake->connect("pressed", callable_mp(this, &GPUParticlesCollisionSDFEditorPlugin::_bake)); + bake_hb->add_child(bake); + bake_info = memnew(Label); + bake_info->set_h_size_flags(Control::SIZE_EXPAND_FILL); + bake_info->set_clip_text(true); + bake_hb->add_child(bake_info); + + add_control_to_container(CONTAINER_SPATIAL_EDITOR_MENU, bake_hb); + col_sdf = nullptr; + probe_file = memnew(EditorFileDialog); + probe_file->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); + probe_file->add_filter("*.exr"); + probe_file->connect("file_selected", callable_mp(this, &GPUParticlesCollisionSDFEditorPlugin::_sdf_save_path_and_bake)); + get_editor_interface()->get_base_control()->add_child(probe_file); + probe_file->set_title(TTR("Select path for SDF Texture")); + + GPUParticlesCollisionSDF::bake_begin_function = bake_func_begin; + GPUParticlesCollisionSDF::bake_step_function = bake_func_step; + GPUParticlesCollisionSDF::bake_end_function = bake_func_end; +} + +GPUParticlesCollisionSDFEditorPlugin::~GPUParticlesCollisionSDFEditorPlugin() { +} diff --git a/editor/plugins/gpu_particles_collision_sdf_editor_plugin.h b/editor/plugins/gpu_particles_collision_sdf_editor_plugin.h new file mode 100644 index 0000000000..0cdc70a62b --- /dev/null +++ b/editor/plugins/gpu_particles_collision_sdf_editor_plugin.h @@ -0,0 +1,74 @@ +/*************************************************************************/ +/* gpu_particles_collision_sdf_editor_plugin.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GPU_PARTICLES_COLLISION_SDF_EDITOR_PLUGIN_H +#define GPU_PARTICLES_COLLISION_SDF_EDITOR_PLUGIN_H + +#include "editor/editor_node.h" +#include "editor/editor_plugin.h" +#include "scene/3d/gpu_particles_collision_3d.h" +#include "scene/resources/material.h" + +class GPUParticlesCollisionSDFEditorPlugin : public EditorPlugin { + GDCLASS(GPUParticlesCollisionSDFEditorPlugin, EditorPlugin); + + GPUParticlesCollisionSDF *col_sdf; + + HBoxContainer *bake_hb; + Label *bake_info; + Button *bake; + EditorNode *editor; + + EditorFileDialog *probe_file; + + static EditorProgress *tmp_progress; + static void bake_func_begin(int p_steps); + static void bake_func_step(int p_step, const String &p_description); + static void bake_func_end(); + + void _bake(); + void _sdf_save_path_and_bake(const String &p_path); + +protected: + static void _bind_methods(); + void _notification(int p_what); + +public: + virtual String get_name() const override { return "GPUParticlesCollisionSDF"; } + bool has_main_screen() const override { return false; } + virtual void edit(Object *p_object) override; + virtual bool handles(Object *p_object) const override; + virtual void make_visible(bool p_visible) override; + + GPUParticlesCollisionSDFEditorPlugin(EditorNode *p_node); + ~GPUParticlesCollisionSDFEditorPlugin(); +}; + +#endif // GPU_PARTICLES_COLLISION_SDF_EDITOR_PLUGIN_H diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index b2fd855834..8bd6e39252 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -6092,6 +6092,7 @@ void Node3DEditor::_register_all_gizmos() { add_gizmo_plugin(Ref<VehicleWheel3DGizmoPlugin>(memnew(VehicleWheel3DGizmoPlugin))); add_gizmo_plugin(Ref<VisibilityNotifier3DGizmoPlugin>(memnew(VisibilityNotifier3DGizmoPlugin))); add_gizmo_plugin(Ref<GPUParticles3DGizmoPlugin>(memnew(GPUParticles3DGizmoPlugin))); + add_gizmo_plugin(Ref<GPUParticlesCollision3DGizmoPlugin>(memnew(GPUParticlesCollision3DGizmoPlugin))); add_gizmo_plugin(Ref<CPUParticles3DGizmoPlugin>(memnew(CPUParticles3DGizmoPlugin))); add_gizmo_plugin(Ref<ReflectionProbeGizmoPlugin>(memnew(ReflectionProbeGizmoPlugin))); add_gizmo_plugin(Ref<DecalGizmoPlugin>(memnew(DecalGizmoPlugin))); diff --git a/scene/3d/gpu_particles_3d.cpp b/scene/3d/gpu_particles_3d.cpp index da5c99a873..ec33d7bcab 100644 --- a/scene/3d/gpu_particles_3d.cpp +++ b/scene/3d/gpu_particles_3d.cpp @@ -124,6 +124,11 @@ void GPUParticles3D::set_speed_scale(float p_scale) { RS::get_singleton()->particles_set_speed_scale(particles, p_scale); } +void GPUParticles3D::set_collision_base_size(float p_size) { + collision_base_size = p_size; + RS::get_singleton()->particles_set_collision_base_size(particles, p_size); +} + bool GPUParticles3D::is_emitting() const { return RS::get_singleton()->particles_get_emitting(particles); } @@ -168,6 +173,10 @@ float GPUParticles3D::get_speed_scale() const { return speed_scale; } +float GPUParticles3D::get_collision_base_size() const { + return collision_base_size; +} + void GPUParticles3D::set_draw_order(DrawOrder p_order) { draw_order = p_order; RS::get_singleton()->particles_set_draw_order(particles, RS::ParticlesDrawOrder(p_order)); @@ -381,6 +390,7 @@ void GPUParticles3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_fractional_delta", "enable"), &GPUParticles3D::set_fractional_delta); ClassDB::bind_method(D_METHOD("set_process_material", "material"), &GPUParticles3D::set_process_material); ClassDB::bind_method(D_METHOD("set_speed_scale", "scale"), &GPUParticles3D::set_speed_scale); + ClassDB::bind_method(D_METHOD("set_collision_base_size", "size"), &GPUParticles3D::set_collision_base_size); ClassDB::bind_method(D_METHOD("is_emitting"), &GPUParticles3D::is_emitting); ClassDB::bind_method(D_METHOD("get_amount"), &GPUParticles3D::get_amount); @@ -395,6 +405,7 @@ void GPUParticles3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_fractional_delta"), &GPUParticles3D::get_fractional_delta); ClassDB::bind_method(D_METHOD("get_process_material"), &GPUParticles3D::get_process_material); ClassDB::bind_method(D_METHOD("get_speed_scale"), &GPUParticles3D::get_speed_scale); + ClassDB::bind_method(D_METHOD("get_collision_base_size"), &GPUParticles3D::get_collision_base_size); ClassDB::bind_method(D_METHOD("set_draw_order", "order"), &GPUParticles3D::set_draw_order); @@ -426,6 +437,8 @@ void GPUParticles3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "randomness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_randomness_ratio", "get_randomness_ratio"); ADD_PROPERTY(PropertyInfo(Variant::INT, "fixed_fps", PROPERTY_HINT_RANGE, "0,1000,1"), "set_fixed_fps", "get_fixed_fps"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fract_delta"), "set_fractional_delta", "get_fractional_delta"); + ADD_GROUP("Collision", "collision_"); + 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"); ADD_GROUP("Drawing", ""); ADD_PROPERTY(PropertyInfo(Variant::AABB, "visibility_aabb"), "set_visibility_aabb", "get_visibility_aabb"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "local_coords"), "set_use_local_coordinates", "get_use_local_coordinates"); @@ -469,6 +482,7 @@ GPUParticles3D::GPUParticles3D() { set_draw_passes(1); set_draw_order(DRAW_ORDER_INDEX); set_speed_scale(1); + set_collision_base_size(0.01); } GPUParticles3D::~GPUParticles3D() { diff --git a/scene/3d/gpu_particles_3d.h b/scene/3d/gpu_particles_3d.h index 0d8dadd31d..b68acef21c 100644 --- a/scene/3d/gpu_particles_3d.h +++ b/scene/3d/gpu_particles_3d.h @@ -65,6 +65,7 @@ private: int fixed_fps; bool fractional_delta; NodePath sub_emitter; + float collision_base_size; Ref<Material> process_material; @@ -94,6 +95,7 @@ public: void set_use_local_coordinates(bool p_enable); void set_process_material(const Ref<Material> &p_material); void set_speed_scale(float p_scale); + void set_collision_base_size(float p_ratio); bool is_emitting() const; int get_amount() const; @@ -106,6 +108,7 @@ public: bool get_use_local_coordinates() const; Ref<Material> get_process_material() const; float get_speed_scale() const; + float get_collision_base_size() const; void set_fixed_fps(int p_count); int get_fixed_fps() const; diff --git a/scene/3d/gpu_particles_collision_3d.cpp b/scene/3d/gpu_particles_collision_3d.cpp new file mode 100644 index 0000000000..af737b058c --- /dev/null +++ b/scene/3d/gpu_particles_collision_3d.cpp @@ -0,0 +1,897 @@ +/*************************************************************************/ +/* gpu_particles_collision_3d.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "gpu_particles_collision_3d.h" +#include "core/thread_work_pool.h" +#include "mesh_instance_3d.h" +#include "scene/3d/camera_3d.h" +#include "scene/main/viewport.h" + +void GPUParticlesCollision3D::set_cull_mask(uint32_t p_cull_mask) { + cull_mask = p_cull_mask; + RS::get_singleton()->particles_collision_set_cull_mask(collision, p_cull_mask); +} + +uint32_t GPUParticlesCollision3D::get_cull_mask() const { + return cull_mask; +} + +void GPUParticlesCollision3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_cull_mask", "mask"), &GPUParticlesCollision3D::set_cull_mask); + ClassDB::bind_method(D_METHOD("get_cull_mask"), &GPUParticlesCollision3D::get_cull_mask); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "cull_mask", PROPERTY_HINT_LAYERS_3D_RENDER), "set_cull_mask", "get_cull_mask"); +} + +GPUParticlesCollision3D::GPUParticlesCollision3D(RS::ParticlesCollisionType p_type) { + collision = RS::get_singleton()->particles_collision_create(); + RS::get_singleton()->particles_collision_set_collision_type(collision, p_type); + set_base(collision); +} + +GPUParticlesCollision3D::~GPUParticlesCollision3D() { + RS::get_singleton()->free(collision); +} + +///////////////////////////////// + +void GPUParticlesCollisionSphere::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_radius", "radius"), &GPUParticlesCollisionSphere::set_radius); + ClassDB::bind_method(D_METHOD("get_radius"), &GPUParticlesCollisionSphere::get_radius); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater"), "set_radius", "get_radius"); +} + +void GPUParticlesCollisionSphere::set_radius(float p_radius) { + radius = p_radius; + RS::get_singleton()->particles_collision_set_sphere_radius(_get_collision(), radius); + update_gizmo(); +} + +float GPUParticlesCollisionSphere::get_radius() const { + return radius; +} + +AABB GPUParticlesCollisionSphere::get_aabb() const { + return AABB(Vector3(-radius, -radius, -radius), Vector3(radius * 2, radius * 2, radius * 2)); +} + +GPUParticlesCollisionSphere::GPUParticlesCollisionSphere() : + GPUParticlesCollision3D(RS::PARTICLES_COLLISION_TYPE_SPHERE_COLLIDE) { +} + +GPUParticlesCollisionSphere::~GPUParticlesCollisionSphere() { +} + +/////////////////////////// + +void GPUParticlesCollisionBox::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_extents", "extents"), &GPUParticlesCollisionBox::set_extents); + ClassDB::bind_method(D_METHOD("get_extents"), &GPUParticlesCollisionBox::get_extents); + + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "extents", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater"), "set_extents", "get_extents"); +} + +void GPUParticlesCollisionBox::set_extents(const Vector3 &p_extents) { + extents = p_extents; + RS::get_singleton()->particles_collision_set_box_extents(_get_collision(), extents); + update_gizmo(); +} + +Vector3 GPUParticlesCollisionBox::get_extents() const { + return extents; +} + +AABB GPUParticlesCollisionBox::get_aabb() const { + return AABB(-extents, extents * 2); +} + +GPUParticlesCollisionBox::GPUParticlesCollisionBox() : + GPUParticlesCollision3D(RS::PARTICLES_COLLISION_TYPE_BOX_COLLIDE) { +} + +GPUParticlesCollisionBox::~GPUParticlesCollisionBox() { +} + +/////////////////////////////// +/////////////////////////// + +void GPUParticlesCollisionSDF::_find_meshes(const AABB &p_aabb, Node *p_at_node, List<PlotMesh> &plot_meshes) { + MeshInstance3D *mi = Object::cast_to<MeshInstance3D>(p_at_node); + if (mi && mi->is_visible_in_tree()) { + Ref<Mesh> mesh = mi->get_mesh(); + if (mesh.is_valid()) { + AABB aabb = mesh->get_aabb(); + + Transform xf = get_global_transform().affine_inverse() * mi->get_global_transform(); + + if (p_aabb.intersects(xf.xform(aabb))) { + PlotMesh pm; + pm.local_xform = xf; + pm.mesh = mesh; + plot_meshes.push_back(pm); + } + } + } + + Node3D *s = Object::cast_to<Node3D>(p_at_node); + if (s) { + if (s->is_visible_in_tree()) { + Array meshes = p_at_node->call("get_meshes"); + for (int i = 0; i < meshes.size(); i += 2) { + Transform mxf = meshes[i]; + Ref<Mesh> mesh = meshes[i + 1]; + if (!mesh.is_valid()) { + continue; + } + + AABB aabb = mesh->get_aabb(); + + Transform xf = get_global_transform().affine_inverse() * (s->get_global_transform() * mxf); + + if (p_aabb.intersects(xf.xform(aabb))) { + PlotMesh pm; + pm.local_xform = xf; + pm.mesh = mesh; + plot_meshes.push_back(pm); + } + } + } + } + + for (int i = 0; i < p_at_node->get_child_count(); i++) { + Node *child = p_at_node->get_child(i); + _find_meshes(p_aabb, child, plot_meshes); + } +} + +uint32_t GPUParticlesCollisionSDF::_create_bvh(LocalVector<BVH> &bvh_tree, FacePos *p_faces, uint32_t p_face_count, const Face3 *p_triangles, float p_thickness) { + if (p_face_count == 1) { + return BVH::LEAF_BIT | p_faces[0].index; + } + + uint32_t index = bvh_tree.size(); + { + BVH bvh; + + for (uint32_t i = 0; i < p_face_count; i++) { + const Face3 &f = p_triangles[p_faces[i].index]; + AABB aabb(f.vertex[0], Vector3()); + aabb.expand_to(f.vertex[1]); + aabb.expand_to(f.vertex[2]); + if (p_thickness > 0.0) { + Vector3 normal = p_triangles[p_faces[i].index].get_plane().normal; + aabb.expand_to(f.vertex[0] - normal * p_thickness); + aabb.expand_to(f.vertex[1] - normal * p_thickness); + aabb.expand_to(f.vertex[2] - normal * p_thickness); + } + if (i == 0) { + bvh.bounds = aabb; + } else { + bvh.bounds.merge_with(aabb); + } + } + bvh_tree.push_back(bvh); + } + + uint32_t middle = p_face_count / 2; + + SortArray<FacePos, FaceSort> s; + s.compare.axis = bvh_tree[index].bounds.get_longest_axis_index(); + s.sort(p_faces, p_face_count); + + uint32_t left = _create_bvh(bvh_tree, p_faces, middle, p_triangles, p_thickness); + uint32_t right = _create_bvh(bvh_tree, p_faces + middle, p_face_count - middle, p_triangles, p_thickness); + + bvh_tree[index].children[0] = left; + bvh_tree[index].children[1] = right; + + return index; +} + +static _FORCE_INLINE_ float Vector3_dot2(const Vector3 &p_vec3) { + return p_vec3.dot(p_vec3); +} + +void GPUParticlesCollisionSDF::_find_closest_distance(const Vector3 &p_pos, const BVH *bvh, uint32_t p_bvh_cell, const Face3 *triangles, float thickness, float &closest_distance) { + if (p_bvh_cell & BVH::LEAF_BIT) { + p_bvh_cell &= BVH::LEAF_MASK; //remove bit + + Vector3 point = p_pos; + Plane p = triangles[p_bvh_cell].get_plane(); + float d = p.distance_to(point); + float inside_d = 1e20; + if (d < 0 && d > -thickness) { + //inside planes, do this in 2D + + Vector3 x_axis = (triangles[p_bvh_cell].vertex[0] - triangles[p_bvh_cell].vertex[1]).normalized(); + Vector3 y_axis = p.normal.cross(x_axis).normalized(); + + Vector2 points[3]; + for (int i = 0; i < 3; i++) { + points[i] = Vector2(x_axis.dot(triangles[p_bvh_cell].vertex[i]), y_axis.dot(triangles[p_bvh_cell].vertex[i])); + } + + Vector2 p2d = Vector2(x_axis.dot(point), y_axis.dot(point)); + + { + // https://www.shadertoy.com/view/XsXSz4 + + Vector2 e0 = points[1] - points[0]; + Vector2 e1 = points[2] - points[1]; + Vector2 e2 = points[0] - points[2]; + + Vector2 v0 = p2d - points[0]; + Vector2 v1 = p2d - points[1]; + Vector2 v2 = p2d - points[2]; + + Vector2 pq0 = v0 - e0 * CLAMP(v0.dot(e0) / e0.dot(e0), 0.0, 1.0); + Vector2 pq1 = v1 - e1 * CLAMP(v1.dot(e1) / e1.dot(e1), 0.0, 1.0); + Vector2 pq2 = v2 - e2 * CLAMP(v2.dot(e2) / e2.dot(e2), 0.0, 1.0); + + float s = SGN(e0.x * e2.y - e0.y * e2.x); + Vector2 d2 = Vector2(pq0.dot(pq0), s * (v0.x * e0.y - v0.y * e0.x)).min(Vector2(pq1.dot(pq1), s * (v1.x * e1.y - v1.y * e1.x))).min(Vector2(pq2.dot(pq2), s * (v2.x * e2.y - v2.y * e2.x))); + + inside_d = -Math::sqrt(d2.x) * SGN(d2.y); + } + + //make sure distance to planes is not shorter if inside + if (inside_d < 0) { + inside_d = MAX(inside_d, d); + inside_d = MAX(inside_d, -(thickness + d)); + } + + closest_distance = MIN(closest_distance, inside_d); + } else { + if (d < 0) { + point -= p.normal * thickness; //flatten + } + + // https://iquilezles.org/www/articles/distfunctions/distfunctions.htm + Vector3 a = triangles[p_bvh_cell].vertex[0]; + Vector3 b = triangles[p_bvh_cell].vertex[1]; + Vector3 c = triangles[p_bvh_cell].vertex[2]; + + Vector3 ba = b - a; + Vector3 pa = point - a; + Vector3 cb = c - b; + Vector3 pb = point - b; + Vector3 ac = a - c; + Vector3 pc = point - c; + Vector3 nor = ba.cross(ac); + + inside_d = Math::sqrt( + (SGN(ba.cross(nor).dot(pa)) + + SGN(cb.cross(nor).dot(pb)) + + SGN(ac.cross(nor).dot(pc)) < + 2.0) ? + MIN(MIN( + Vector3_dot2(ba * CLAMP(ba.dot(pa) / Vector3_dot2(ba), 0.0, 1.0) - pa), + Vector3_dot2(cb * CLAMP(cb.dot(pb) / Vector3_dot2(cb), 0.0, 1.0) - pb)), + Vector3_dot2(ac * CLAMP(ac.dot(pc) / Vector3_dot2(ac), 0.0, 1.0) - pc)) : + nor.dot(pa) * nor.dot(pa) / Vector3_dot2(nor)); + + closest_distance = MIN(closest_distance, inside_d); + } + + } else { + bool pass = true; + if (!bvh[p_bvh_cell].bounds.has_point(p_pos)) { + //outside, find closest point + Vector3 he = bvh[p_bvh_cell].bounds.size * 0.5; + Vector3 center = bvh[p_bvh_cell].bounds.position + he; + + Vector3 rel = (p_pos - center).abs(); + Vector3 closest(MIN(rel.x, he.x), MIN(rel.y, he.y), MIN(rel.z, he.z)); + float d = rel.distance_to(closest); + + if (d >= closest_distance) { + pass = false; //already closer than this aabb, discard + } + } + + if (pass) { + _find_closest_distance(p_pos, bvh, bvh[p_bvh_cell].children[0], triangles, thickness, closest_distance); + _find_closest_distance(p_pos, bvh, bvh[p_bvh_cell].children[1], triangles, thickness, closest_distance); + } + } +} + +void GPUParticlesCollisionSDF::_compute_sdf_z(uint32_t p_z, ComputeSDFParams *params) { + int32_t z_ofs = p_z * params->size.y * params->size.x; + for (int32_t y = 0; y < params->size.y; y++) { + int32_t y_ofs = z_ofs + y * params->size.x; + for (int32_t x = 0; x < params->size.x; x++) { + int32_t x_ofs = y_ofs + x; + float &cell = params->cells[x_ofs]; + + Vector3 pos = params->cell_offset + Vector3(x, y, p_z) * params->cell_size; + + cell = 1e20; + + _find_closest_distance(pos, params->bvh, 0, params->triangles, params->thickness, cell); + } + } +} + +void GPUParticlesCollisionSDF::_compute_sdf(ComputeSDFParams *params) { + ThreadWorkPool work_pool; + work_pool.init(); + work_pool.begin_work(params->size.z, this, &GPUParticlesCollisionSDF::_compute_sdf_z, params); + while (work_pool.get_work_index() < (uint32_t)params->size.z) { + OS::get_singleton()->delay_usec(10000); + bake_step_function(work_pool.get_work_index() * 100 / params->size.z, "Baking SDF"); + } + work_pool.end_work(); + work_pool.finish(); +} + +Vector3i GPUParticlesCollisionSDF::get_estimated_cell_size() const { + static const int subdivs[RESOLUTION_MAX] = { 16, 32, 64, 128, 256, 512 }; + int subdiv = subdivs[get_resolution()]; + + AABB aabb(-extents, extents * 2); + + float cell_size = aabb.get_longest_axis_size() / float(subdiv); + + Vector3i sdf_size = Vector3i(aabb.size / cell_size); + sdf_size.x = MAX(1, sdf_size.x); + sdf_size.y = MAX(1, sdf_size.y); + sdf_size.z = MAX(1, sdf_size.z); + return sdf_size; +} + +Ref<Image> GPUParticlesCollisionSDF::bake() { + static const int subdivs[RESOLUTION_MAX] = { 16, 32, 64, 128, 256, 512 }; + int subdiv = subdivs[get_resolution()]; + + AABB aabb(-extents, extents * 2); + + float cell_size = aabb.get_longest_axis_size() / float(subdiv); + + Vector3i sdf_size = Vector3i(aabb.size / cell_size); + sdf_size.x = MAX(1, sdf_size.x); + sdf_size.y = MAX(1, sdf_size.y); + sdf_size.z = MAX(1, sdf_size.z); + + if (bake_begin_function) { + bake_begin_function(100); + } + + aabb.size = Vector3(sdf_size) * cell_size; + + List<PlotMesh> plot_meshes; + _find_meshes(aabb, get_parent(), plot_meshes); + + LocalVector<Face3> faces; + + if (bake_step_function) { + bake_step_function(0, "Finding Meshes"); + } + + for (List<PlotMesh>::Element *E = plot_meshes.front(); E; E = E->next()) { + const PlotMesh &pm = E->get(); + + for (int i = 0; i < pm.mesh->get_surface_count(); i++) { + if (pm.mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES) { + continue; //only triangles + } + + Array a = pm.mesh->surface_get_arrays(i); + + Vector<Vector3> vertices = a[Mesh::ARRAY_VERTEX]; + const Vector3 *vr = vertices.ptr(); + Vector<int> index = a[Mesh::ARRAY_INDEX]; + + if (index.size()) { + int facecount = index.size() / 3; + const int *ir = index.ptr(); + + for (int j = 0; j < facecount; j++) { + Face3 face; + + for (int k = 0; k < 3; k++) { + face.vertex[k] = pm.local_xform.xform(vr[ir[j * 3 + k]]); + } + + //test against original bounds + if (!Geometry3D::triangle_box_overlap(aabb.position + aabb.size * 0.5, aabb.size * 0.5, face.vertex)) { + continue; + } + + faces.push_back(face); + } + + } else { + int facecount = vertices.size() / 3; + + for (int j = 0; j < facecount; j++) { + Face3 face; + + for (int k = 0; k < 3; k++) { + face.vertex[k] = pm.local_xform.xform(vr[j * 3 + k]); + } + + //test against original bounds + if (!Geometry3D::triangle_box_overlap(aabb.position + aabb.size * 0.5, aabb.size * 0.5, face.vertex)) { + continue; + } + + faces.push_back(face); + } + } + } + } + + //compute bvh + + ERR_FAIL_COND_V(faces.size() <= 1, Ref<Image>()); + + LocalVector<FacePos> face_pos; + + face_pos.resize(faces.size()); + + float th = cell_size * thickness; + + for (uint32_t i = 0; i < faces.size(); i++) { + face_pos[i].index = i; + face_pos[i].center = (faces[i].vertex[0] + faces[i].vertex[1] + faces[i].vertex[2]) / 2; + if (th > 0.0) { + face_pos[i].center -= faces[i].get_plane().normal * th * 0.5; + } + } + + if (bake_step_function) { + bake_step_function(0, "Creating BVH"); + } + + LocalVector<BVH> bvh; + + _create_bvh(bvh, face_pos.ptr(), face_pos.size(), faces.ptr(), th); + + Vector<uint8_t> data; + data.resize(sdf_size.z * sdf_size.y * sdf_size.x * sizeof(float)); + + if (bake_step_function) { + bake_step_function(0, "Baking SDF"); + } + + ComputeSDFParams params; + params.cells = (float *)data.ptrw(); + params.size = sdf_size; + params.cell_size = cell_size; + params.cell_offset = aabb.position + Vector3(cell_size * 0.5, cell_size * 0.5, cell_size * 0.5); + params.bvh = bvh.ptr(); + params.triangles = faces.ptr(); + params.thickness = th; + _compute_sdf(¶ms); + + Ref<Image> ret; + ret.instance(); + ret->create(sdf_size.x, sdf_size.y * sdf_size.z, false, Image::FORMAT_RF, data); + ret->convert(Image::FORMAT_RH); //convert to half, save space + ret->set_meta("depth", sdf_size.z); //hack, make sure to add to the docs of this function + + if (bake_end_function) { + bake_end_function(); + } + + return ret; +} + +void GPUParticlesCollisionSDF::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_extents", "extents"), &GPUParticlesCollisionSDF::set_extents); + ClassDB::bind_method(D_METHOD("get_extents"), &GPUParticlesCollisionSDF::get_extents); + + ClassDB::bind_method(D_METHOD("set_resolution", "resolution"), &GPUParticlesCollisionSDF::set_resolution); + ClassDB::bind_method(D_METHOD("get_resolution"), &GPUParticlesCollisionSDF::get_resolution); + + ClassDB::bind_method(D_METHOD("set_texture", "texture"), &GPUParticlesCollisionSDF::set_texture); + ClassDB::bind_method(D_METHOD("get_texture"), &GPUParticlesCollisionSDF::get_texture); + + ClassDB::bind_method(D_METHOD("set_thickness", "thickness"), &GPUParticlesCollisionSDF::set_thickness); + ClassDB::bind_method(D_METHOD("get_thickness"), &GPUParticlesCollisionSDF::get_thickness); + + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "extents", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater"), "set_extents", "get_extents"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "resolution", PROPERTY_HINT_ENUM, "16,32,64,128,256,512"), "set_resolution", "get_resolution"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "thickness", PROPERTY_HINT_RANGE, "0.0,2.0,0.01"), "set_thickness", "get_thickness"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture3D"), "set_texture", "get_texture"); + + BIND_ENUM_CONSTANT(RESOLUTION_16); + BIND_ENUM_CONSTANT(RESOLUTION_32); + BIND_ENUM_CONSTANT(RESOLUTION_64); + BIND_ENUM_CONSTANT(RESOLUTION_128); + BIND_ENUM_CONSTANT(RESOLUTION_256); + BIND_ENUM_CONSTANT(RESOLUTION_512); + BIND_ENUM_CONSTANT(RESOLUTION_MAX); +} + +void GPUParticlesCollisionSDF::set_thickness(float p_thickness) { + thickness = p_thickness; +} + +float GPUParticlesCollisionSDF::get_thickness() const { + return thickness; +} + +void GPUParticlesCollisionSDF::set_extents(const Vector3 &p_extents) { + extents = p_extents; + RS::get_singleton()->particles_collision_set_box_extents(_get_collision(), extents); + update_gizmo(); +} + +Vector3 GPUParticlesCollisionSDF::get_extents() const { + return extents; +} + +void GPUParticlesCollisionSDF::set_resolution(Resolution p_resolution) { + resolution = p_resolution; + update_gizmo(); +} + +GPUParticlesCollisionSDF::Resolution GPUParticlesCollisionSDF::get_resolution() const { + return resolution; +} + +void GPUParticlesCollisionSDF::set_texture(const Ref<Texture3D> &p_texture) { + texture = p_texture; + RID tex = texture.is_valid() ? texture->get_rid() : RID(); + RS::get_singleton()->particles_collision_set_field_texture(_get_collision(), tex); +} + +Ref<Texture3D> GPUParticlesCollisionSDF::get_texture() const { + return texture; +} + +AABB GPUParticlesCollisionSDF::get_aabb() const { + return AABB(-extents, extents * 2); +} + +GPUParticlesCollisionSDF::BakeBeginFunc GPUParticlesCollisionSDF::bake_begin_function = nullptr; +GPUParticlesCollisionSDF::BakeStepFunc GPUParticlesCollisionSDF::bake_step_function = nullptr; +GPUParticlesCollisionSDF::BakeEndFunc GPUParticlesCollisionSDF::bake_end_function = nullptr; + +GPUParticlesCollisionSDF::GPUParticlesCollisionSDF() : + GPUParticlesCollision3D(RS::PARTICLES_COLLISION_TYPE_SDF_COLLIDE) { +} + +GPUParticlesCollisionSDF::~GPUParticlesCollisionSDF() { +} + +//////////////////////////// +//////////////////////////// + +void GPUParticlesCollisionHeightField::_notification(int p_what) { + if (p_what == NOTIFICATION_INTERNAL_PROCESS) { + if (update_mode == UPDATE_MODE_ALWAYS) { + RS::get_singleton()->particles_collision_height_field_update(_get_collision()); + } + + if (follow_camera_mode && get_viewport()) { + Camera3D *cam = get_viewport()->get_camera(); + if (cam) { + Transform xform = get_global_transform(); + Vector3 x_axis = xform.basis.get_axis(Vector3::AXIS_X).normalized(); + Vector3 z_axis = xform.basis.get_axis(Vector3::AXIS_Z).normalized(); + float x_len = xform.basis.get_scale().x; + float z_len = xform.basis.get_scale().z; + + Vector3 cam_pos = cam->get_global_transform().origin; + Transform new_xform = xform; + + while (x_axis.dot(cam_pos - new_xform.origin) > x_len) { + new_xform.origin += x_axis * x_len; + } + while (x_axis.dot(cam_pos - new_xform.origin) < -x_len) { + new_xform.origin -= x_axis * x_len; + } + + while (z_axis.dot(cam_pos - new_xform.origin) > z_len) { + new_xform.origin += z_axis * z_len; + } + while (z_axis.dot(cam_pos - new_xform.origin) < -z_len) { + new_xform.origin -= z_axis * z_len; + } + + if (new_xform != xform) { + set_global_transform(new_xform); + RS::get_singleton()->particles_collision_height_field_update(_get_collision()); + } + } + } + } + + if (p_what == NOTIFICATION_TRANSFORM_CHANGED) { + RS::get_singleton()->particles_collision_height_field_update(_get_collision()); + } +} + +void GPUParticlesCollisionHeightField::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_extents", "extents"), &GPUParticlesCollisionHeightField::set_extents); + ClassDB::bind_method(D_METHOD("get_extents"), &GPUParticlesCollisionHeightField::get_extents); + + ClassDB::bind_method(D_METHOD("set_resolution", "resolution"), &GPUParticlesCollisionHeightField::set_resolution); + ClassDB::bind_method(D_METHOD("get_resolution"), &GPUParticlesCollisionHeightField::get_resolution); + + ClassDB::bind_method(D_METHOD("set_update_mode", "update_mode"), &GPUParticlesCollisionHeightField::set_update_mode); + ClassDB::bind_method(D_METHOD("get_update_mode"), &GPUParticlesCollisionHeightField::get_update_mode); + + ClassDB::bind_method(D_METHOD("set_follow_camera_mode", "enabled"), &GPUParticlesCollisionHeightField::set_follow_camera_mode); + ClassDB::bind_method(D_METHOD("is_follow_camera_mode_enabled"), &GPUParticlesCollisionHeightField::is_follow_camera_mode_enabled); + + ClassDB::bind_method(D_METHOD("set_follow_camera_push_ratio", "ratio"), &GPUParticlesCollisionHeightField::set_follow_camera_push_ratio); + ClassDB::bind_method(D_METHOD("get_follow_camera_push_ratio"), &GPUParticlesCollisionHeightField::get_follow_camera_push_ratio); + + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "extents", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater"), "set_extents", "get_extents"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "resolution", PROPERTY_HINT_ENUM, "256,512,1024,2048,4096,8192"), "set_resolution", "get_resolution"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "update_mode", PROPERTY_HINT_ENUM, "WhenMoved,Always"), "set_update_mode", "get_update_mode"); + ADD_GROUP("Folow Camera", "follow_camera_"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "follow_camera_enabled"), "set_follow_camera_mode", "is_follow_camera_mode_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "follow_camera_push_ratio", PROPERTY_HINT_RANGE, "0.01,1,0.01"), "set_follow_camera_push_ratio", "get_follow_camera_push_ratio"); + + BIND_ENUM_CONSTANT(RESOLUTION_256); + BIND_ENUM_CONSTANT(RESOLUTION_512); + BIND_ENUM_CONSTANT(RESOLUTION_1024); + BIND_ENUM_CONSTANT(RESOLUTION_2048); + BIND_ENUM_CONSTANT(RESOLUTION_4096); + BIND_ENUM_CONSTANT(RESOLUTION_8192); + BIND_ENUM_CONSTANT(RESOLUTION_MAX); +} + +void GPUParticlesCollisionHeightField::set_follow_camera_push_ratio(float p_follow_camera_push_ratio) { + follow_camera_push_ratio = p_follow_camera_push_ratio; +} + +float GPUParticlesCollisionHeightField::get_follow_camera_push_ratio() const { + return follow_camera_push_ratio; +} + +void GPUParticlesCollisionHeightField::set_extents(const Vector3 &p_extents) { + extents = p_extents; + RS::get_singleton()->particles_collision_set_box_extents(_get_collision(), extents); + update_gizmo(); + RS::get_singleton()->particles_collision_height_field_update(_get_collision()); +} + +Vector3 GPUParticlesCollisionHeightField::get_extents() const { + return extents; +} + +void GPUParticlesCollisionHeightField::set_resolution(Resolution p_resolution) { + resolution = p_resolution; + RS::get_singleton()->particles_collision_set_height_field_resolution(_get_collision(), RS::ParticlesCollisionHeightfieldResolution(resolution)); + update_gizmo(); + RS::get_singleton()->particles_collision_height_field_update(_get_collision()); +} + +GPUParticlesCollisionHeightField::Resolution GPUParticlesCollisionHeightField::get_resolution() const { + return resolution; +} + +void GPUParticlesCollisionHeightField::set_update_mode(UpdateMode p_update_mode) { + update_mode = p_update_mode; + set_process_internal(follow_camera_mode || update_mode == UPDATE_MODE_ALWAYS); +} + +GPUParticlesCollisionHeightField::UpdateMode GPUParticlesCollisionHeightField::get_update_mode() const { + return update_mode; +} + +void GPUParticlesCollisionHeightField::set_follow_camera_mode(bool p_enabled) { + follow_camera_mode = p_enabled; + set_process_internal(follow_camera_mode || update_mode == UPDATE_MODE_ALWAYS); +} + +bool GPUParticlesCollisionHeightField::is_follow_camera_mode_enabled() const { + return follow_camera_mode; +} + +AABB GPUParticlesCollisionHeightField::get_aabb() const { + return AABB(-extents, extents * 2); +} + +GPUParticlesCollisionHeightField::GPUParticlesCollisionHeightField() : + GPUParticlesCollision3D(RS::PARTICLES_COLLISION_TYPE_HEIGHTFIELD_COLLIDE) { +} + +GPUParticlesCollisionHeightField::~GPUParticlesCollisionHeightField() { +} + +//////////////////////////// +//////////////////////////// + +void GPUParticlesAttractor3D::set_cull_mask(uint32_t p_cull_mask) { + cull_mask = p_cull_mask; + RS::get_singleton()->particles_collision_set_cull_mask(collision, p_cull_mask); +} + +uint32_t GPUParticlesAttractor3D::get_cull_mask() const { + return cull_mask; +} + +void GPUParticlesAttractor3D::set_strength(float p_strength) { + strength = p_strength; + RS::get_singleton()->particles_collision_set_attractor_strength(collision, p_strength); +} + +float GPUParticlesAttractor3D::get_strength() const { + return strength; +} + +void GPUParticlesAttractor3D::set_attenuation(float p_attenuation) { + attenuation = p_attenuation; + RS::get_singleton()->particles_collision_set_attractor_attenuation(collision, p_attenuation); +} + +float GPUParticlesAttractor3D::get_attenuation() const { + return attenuation; +} + +void GPUParticlesAttractor3D::set_directionality(float p_directionality) { + directionality = p_directionality; + RS::get_singleton()->particles_collision_set_attractor_directionality(collision, p_directionality); + update_gizmo(); +} + +float GPUParticlesAttractor3D::get_directionality() const { + return directionality; +} + +void GPUParticlesAttractor3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_cull_mask", "mask"), &GPUParticlesAttractor3D::set_cull_mask); + ClassDB::bind_method(D_METHOD("get_cull_mask"), &GPUParticlesAttractor3D::get_cull_mask); + + ClassDB::bind_method(D_METHOD("set_strength", "strength"), &GPUParticlesAttractor3D::set_strength); + ClassDB::bind_method(D_METHOD("get_strength"), &GPUParticlesAttractor3D::get_strength); + + ClassDB::bind_method(D_METHOD("set_attenuation", "attenuation"), &GPUParticlesAttractor3D::set_attenuation); + ClassDB::bind_method(D_METHOD("get_attenuation"), &GPUParticlesAttractor3D::get_attenuation); + + ClassDB::bind_method(D_METHOD("set_directionality", "amount"), &GPUParticlesAttractor3D::set_directionality); + ClassDB::bind_method(D_METHOD("get_directionality"), &GPUParticlesAttractor3D::get_directionality); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "strength", PROPERTY_HINT_RANGE, "-128,128,0.01,or_greater,or_lesser"), "set_strength", "get_strength"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "attenuation", PROPERTY_HINT_EXP_EASING, "0,8,0.01"), "set_attenuation", "get_attenuation"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "directionality", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_directionality", "get_directionality"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "cull_mask", PROPERTY_HINT_LAYERS_3D_RENDER), "set_cull_mask", "get_cull_mask"); +} + +GPUParticlesAttractor3D::GPUParticlesAttractor3D(RS::ParticlesCollisionType p_type) { + collision = RS::get_singleton()->particles_collision_create(); + RS::get_singleton()->particles_collision_set_collision_type(collision, p_type); + set_base(collision); +} +GPUParticlesAttractor3D::~GPUParticlesAttractor3D() { + RS::get_singleton()->free(collision); +} + +///////////////////////////////// + +void GPUParticlesAttractorSphere::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_radius", "radius"), &GPUParticlesAttractorSphere::set_radius); + ClassDB::bind_method(D_METHOD("get_radius"), &GPUParticlesAttractorSphere::get_radius); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater"), "set_radius", "get_radius"); +} + +void GPUParticlesAttractorSphere::set_radius(float p_radius) { + radius = p_radius; + RS::get_singleton()->particles_collision_set_sphere_radius(_get_collision(), radius); + update_gizmo(); +} + +float GPUParticlesAttractorSphere::get_radius() const { + return radius; +} + +AABB GPUParticlesAttractorSphere::get_aabb() const { + return AABB(Vector3(-radius, -radius, -radius), Vector3(radius * 2, radius * 2, radius * 2)); +} + +GPUParticlesAttractorSphere::GPUParticlesAttractorSphere() : + GPUParticlesAttractor3D(RS::PARTICLES_COLLISION_TYPE_SPHERE_ATTRACT) { +} + +GPUParticlesAttractorSphere::~GPUParticlesAttractorSphere() { +} + +/////////////////////////// + +void GPUParticlesAttractorBox::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_extents", "extents"), &GPUParticlesAttractorBox::set_extents); + ClassDB::bind_method(D_METHOD("get_extents"), &GPUParticlesAttractorBox::get_extents); + + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "extents", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater"), "set_extents", "get_extents"); +} + +void GPUParticlesAttractorBox::set_extents(const Vector3 &p_extents) { + extents = p_extents; + RS::get_singleton()->particles_collision_set_box_extents(_get_collision(), extents); + update_gizmo(); +} + +Vector3 GPUParticlesAttractorBox::get_extents() const { + return extents; +} + +AABB GPUParticlesAttractorBox::get_aabb() const { + return AABB(-extents, extents * 2); +} + +GPUParticlesAttractorBox::GPUParticlesAttractorBox() : + GPUParticlesAttractor3D(RS::PARTICLES_COLLISION_TYPE_BOX_ATTRACT) { +} + +GPUParticlesAttractorBox::~GPUParticlesAttractorBox() { +} + +/////////////////////////// + +void GPUParticlesAttractorVectorField::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_extents", "extents"), &GPUParticlesAttractorVectorField::set_extents); + ClassDB::bind_method(D_METHOD("get_extents"), &GPUParticlesAttractorVectorField::get_extents); + + ClassDB::bind_method(D_METHOD("set_texture", "texture"), &GPUParticlesAttractorVectorField::set_texture); + ClassDB::bind_method(D_METHOD("get_texture"), &GPUParticlesAttractorVectorField::get_texture); + + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "extents", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater"), "set_extents", "get_extents"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture3D"), "set_texture", "get_texture"); +} + +void GPUParticlesAttractorVectorField::set_extents(const Vector3 &p_extents) { + extents = p_extents; + RS::get_singleton()->particles_collision_set_box_extents(_get_collision(), extents); + update_gizmo(); +} + +Vector3 GPUParticlesAttractorVectorField::get_extents() const { + return extents; +} + +void GPUParticlesAttractorVectorField::set_texture(const Ref<Texture3D> &p_texture) { + texture = p_texture; + RID tex = texture.is_valid() ? texture->get_rid() : RID(); + RS::get_singleton()->particles_collision_set_field_texture(_get_collision(), tex); +} + +Ref<Texture3D> GPUParticlesAttractorVectorField::get_texture() const { + return texture; +} + +AABB GPUParticlesAttractorVectorField::get_aabb() const { + return AABB(-extents, extents * 2); +} + +GPUParticlesAttractorVectorField::GPUParticlesAttractorVectorField() : + GPUParticlesAttractor3D(RS::PARTICLES_COLLISION_TYPE_VECTOR_FIELD_ATTRACT) { +} + +GPUParticlesAttractorVectorField::~GPUParticlesAttractorVectorField() { +} diff --git a/scene/3d/gpu_particles_collision_3d.h b/scene/3d/gpu_particles_collision_3d.h new file mode 100644 index 0000000000..4c73c7bcb2 --- /dev/null +++ b/scene/3d/gpu_particles_collision_3d.h @@ -0,0 +1,342 @@ +/*************************************************************************/ +/* gpu_particles_collision_3d.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GPU_PARTICLES_COLLISION_3D_H +#define GPU_PARTICLES_COLLISION_3D_H + +#include "core/local_vector.h" +#include "core/rid.h" +#include "scene/3d/visual_instance_3d.h" +#include "scene/resources/material.h" + +class GPUParticlesCollision3D : public VisualInstance3D { + GDCLASS(GPUParticlesCollision3D, VisualInstance3D); + + uint32_t cull_mask = 0xFFFFFFFF; + RID collision; + +protected: + _FORCE_INLINE_ RID _get_collision() { return collision; } + static void _bind_methods(); + + GPUParticlesCollision3D(RS::ParticlesCollisionType p_type); + +public: + void set_cull_mask(uint32_t p_cull_mask); + uint32_t get_cull_mask() const; + + virtual Vector<Face3> get_faces(uint32_t p_usage_flags) const override { return Vector<Face3>(); } + + ~GPUParticlesCollision3D(); +}; + +class GPUParticlesCollisionSphere : public GPUParticlesCollision3D { + GDCLASS(GPUParticlesCollisionSphere, GPUParticlesCollision3D); + + float radius = 1.0; + +protected: + static void _bind_methods(); + +public: + void set_radius(float p_radius); + float get_radius() const; + + virtual AABB get_aabb() const override; + + GPUParticlesCollisionSphere(); + ~GPUParticlesCollisionSphere(); +}; + +class GPUParticlesCollisionBox : public GPUParticlesCollision3D { + GDCLASS(GPUParticlesCollisionBox, GPUParticlesCollision3D); + + Vector3 extents = Vector3(1, 1, 1); + +protected: + static void _bind_methods(); + +public: + void set_extents(const Vector3 &p_extents); + Vector3 get_extents() const; + + virtual AABB get_aabb() const override; + + GPUParticlesCollisionBox(); + ~GPUParticlesCollisionBox(); +}; + +class GPUParticlesCollisionSDF : public GPUParticlesCollision3D { + GDCLASS(GPUParticlesCollisionSDF, GPUParticlesCollision3D); + +public: + enum Resolution { + RESOLUTION_16, + RESOLUTION_32, + RESOLUTION_64, + RESOLUTION_128, + RESOLUTION_256, + RESOLUTION_512, + RESOLUTION_MAX, + }; + + typedef void (*BakeBeginFunc)(int); + typedef void (*BakeStepFunc)(int, const String &); + typedef void (*BakeEndFunc)(); + +private: + Vector3 extents = Vector3(1, 1, 1); + Resolution resolution = RESOLUTION_64; + Ref<Texture3D> texture; + float thickness = 1.0; + + struct PlotMesh { + Ref<Mesh> mesh; + Transform local_xform; + }; + + void _find_meshes(const AABB &p_aabb, Node *p_at_node, List<PlotMesh> &plot_meshes); + + struct BVH { + enum { + LEAF_BIT = 1 << 30, + LEAF_MASK = LEAF_BIT - 1 + }; + AABB bounds; + uint32_t children[2]; + }; + + struct FacePos { + Vector3 center; + uint32_t index; + }; + + struct FaceSort { + uint32_t axis; + bool operator()(const FacePos &p_left, const FacePos &p_right) const { + return p_left.center[axis] < p_right.center[axis]; + } + }; + + uint32_t _create_bvh(LocalVector<BVH> &bvh_tree, FacePos *p_faces, uint32_t p_face_count, const Face3 *p_triangles, float p_thickness); + + struct ComputeSDFParams { + float *cells; + Vector3i size; + float cell_size; + Vector3 cell_offset; + const BVH *bvh; + const Face3 *triangles; + float thickness; + }; + + void _find_closest_distance(const Vector3 &p_pos, const BVH *bvh, uint32_t p_bvh_cell, const Face3 *triangles, float thickness, float &closest_distance); + void _compute_sdf_z(uint32_t p_z, ComputeSDFParams *params); + void _compute_sdf(ComputeSDFParams *params); + +protected: + static void _bind_methods(); + +public: + void set_thickness(float p_thickness); + float get_thickness() const; + + void set_extents(const Vector3 &p_extents); + Vector3 get_extents() const; + + void set_resolution(Resolution p_resolution); + Resolution get_resolution() const; + + void set_texture(const Ref<Texture3D> &p_texture); + Ref<Texture3D> get_texture() const; + + Vector3i get_estimated_cell_size() const; + Ref<Image> bake(); + + virtual AABB get_aabb() const override; + + static BakeBeginFunc bake_begin_function; + static BakeStepFunc bake_step_function; + static BakeEndFunc bake_end_function; + + GPUParticlesCollisionSDF(); + ~GPUParticlesCollisionSDF(); +}; + +VARIANT_ENUM_CAST(GPUParticlesCollisionSDF::Resolution) + +class GPUParticlesCollisionHeightField : public GPUParticlesCollision3D { + GDCLASS(GPUParticlesCollisionHeightField, GPUParticlesCollision3D); + +public: + enum Resolution { + RESOLUTION_256, + RESOLUTION_512, + RESOLUTION_1024, + RESOLUTION_2048, + RESOLUTION_4096, + RESOLUTION_8192, + RESOLUTION_MAX, + }; + + enum UpdateMode { + UPDATE_MODE_WHEN_MOVED, + UPDATE_MODE_ALWAYS, + }; + +private: + Vector3 extents = Vector3(1, 1, 1); + Resolution resolution = RESOLUTION_1024; + bool follow_camera_mode = false; + float follow_camera_push_ratio = 0.1; + + UpdateMode update_mode = UPDATE_MODE_WHEN_MOVED; + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + void set_extents(const Vector3 &p_extents); + Vector3 get_extents() const; + + void set_resolution(Resolution p_resolution); + Resolution get_resolution() const; + + void set_update_mode(UpdateMode p_update_mode); + UpdateMode get_update_mode() const; + + void set_follow_camera_mode(bool p_enabled); + bool is_follow_camera_mode_enabled() const; + + void set_follow_camera_push_ratio(float p_ratio); + float get_follow_camera_push_ratio() const; + + virtual AABB get_aabb() const override; + + GPUParticlesCollisionHeightField(); + ~GPUParticlesCollisionHeightField(); +}; + +VARIANT_ENUM_CAST(GPUParticlesCollisionHeightField::Resolution) +VARIANT_ENUM_CAST(GPUParticlesCollisionHeightField::UpdateMode) + +class GPUParticlesAttractor3D : public VisualInstance3D { + GDCLASS(GPUParticlesAttractor3D, VisualInstance3D); + + uint32_t cull_mask = 0xFFFFFFFF; + RID collision; + float strength = 1.0; + float attenuation = 1.0; + float directionality = 0.0; + +protected: + _FORCE_INLINE_ RID _get_collision() { return collision; } + static void _bind_methods(); + + GPUParticlesAttractor3D(RS::ParticlesCollisionType p_type); + +public: + void set_cull_mask(uint32_t p_cull_mask); + uint32_t get_cull_mask() const; + + void set_strength(float p_strength); + float get_strength() const; + + void set_attenuation(float p_attenuation); + float get_attenuation() const; + + void set_directionality(float p_directionality); + float get_directionality() const; + + virtual Vector<Face3> get_faces(uint32_t p_usage_flags) const override { return Vector<Face3>(); } + + ~GPUParticlesAttractor3D(); +}; + +class GPUParticlesAttractorSphere : public GPUParticlesAttractor3D { + GDCLASS(GPUParticlesAttractorSphere, GPUParticlesAttractor3D); + + float radius = 1.0; + +protected: + static void _bind_methods(); + +public: + void set_radius(float p_radius); + float get_radius() const; + + virtual AABB get_aabb() const override; + + GPUParticlesAttractorSphere(); + ~GPUParticlesAttractorSphere(); +}; + +class GPUParticlesAttractorBox : public GPUParticlesAttractor3D { + GDCLASS(GPUParticlesAttractorBox, GPUParticlesAttractor3D); + + Vector3 extents = Vector3(1, 1, 1); + +protected: + static void _bind_methods(); + +public: + void set_extents(const Vector3 &p_extents); + Vector3 get_extents() const; + + virtual AABB get_aabb() const override; + + GPUParticlesAttractorBox(); + ~GPUParticlesAttractorBox(); +}; + +class GPUParticlesAttractorVectorField : public GPUParticlesAttractor3D { + GDCLASS(GPUParticlesAttractorVectorField, GPUParticlesAttractor3D); + + Vector3 extents = Vector3(1, 1, 1); + Ref<Texture3D> texture; + +protected: + static void _bind_methods(); + +public: + void set_extents(const Vector3 &p_extents); + Vector3 get_extents() const; + + void set_texture(const Ref<Texture3D> &p_texture); + Ref<Texture3D> get_texture() const; + + virtual AABB get_aabb() const override; + + GPUParticlesAttractorVectorField(); + ~GPUParticlesAttractorVectorField(); +}; + +#endif // GPU_PARTICLES_COLLISION_3D_H diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 820513c53d..6b602ae6e5 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -194,6 +194,7 @@ #include "scene/3d/decal.h" #include "scene/3d/gi_probe.h" #include "scene/3d/gpu_particles_3d.h" +#include "scene/3d/gpu_particles_collision_3d.h" #include "scene/3d/immediate_geometry_3d.h" #include "scene/3d/light_3d.h" #include "scene/3d/lightmap_probe.h" @@ -450,6 +451,15 @@ void register_scene_types() { ClassDB::register_class<LightmapProbe>(); ClassDB::register_virtual_class<Lightmapper>(); ClassDB::register_class<GPUParticles3D>(); + ClassDB::register_virtual_class<GPUParticlesCollision3D>(); + ClassDB::register_class<GPUParticlesCollisionBox>(); + ClassDB::register_class<GPUParticlesCollisionSphere>(); + ClassDB::register_class<GPUParticlesCollisionSDF>(); + ClassDB::register_class<GPUParticlesCollisionHeightField>(); + ClassDB::register_virtual_class<GPUParticlesAttractor3D>(); + ClassDB::register_class<GPUParticlesAttractorBox>(); + ClassDB::register_class<GPUParticlesAttractorSphere>(); + ClassDB::register_class<GPUParticlesAttractorVectorField>(); ClassDB::register_class<CPUParticles3D>(); ClassDB::register_class<Position3D>(); diff --git a/scene/resources/particles_material.cpp b/scene/resources/particles_material.cpp index 4bbfa8965a..a286184aee 100644 --- a/scene/resources/particles_material.cpp +++ b/scene/resources/particles_material.cpp @@ -98,6 +98,9 @@ void ParticlesMaterial::init_shaders() { shader_names->sub_emitter_frequency = "sub_emitter_frequency"; shader_names->sub_emitter_amount_at_end = "sub_emitter_amount_at_end"; shader_names->sub_emitter_keep_velocity = "sub_emitter_keep_velocity"; + + shader_names->collision_friction = "collision_friction"; + shader_names->collision_bounce = "collision_bounce"; } void ParticlesMaterial::finish_shaders() { @@ -136,6 +139,10 @@ void ParticlesMaterial::_update_shader() { String code = "shader_type particles;\n"; + if (collision_scale) { + code += "render_mode collision_use_scale;\n"; + } + code += "uniform vec3 direction;\n"; code += "uniform float spread;\n"; code += "uniform float flatness;\n"; @@ -247,6 +254,11 @@ void ParticlesMaterial::_update_shader() { code += "uniform sampler2D anim_offset_texture;\n"; } + if (collision_enabled) { + code += "uniform float collision_friction;\n"; + code += "uniform float collision_bounce;\n"; + } + //need a random function code += "\n\n"; code += "float rand_from_seed(inout uint seed) {\n"; @@ -476,6 +488,10 @@ void ParticlesMaterial::_update_shader() { code += " vec3 crossDiff = cross(normalize(diff), normalize(gravity));\n"; code += " force += length(crossDiff) > 0.0 ? normalize(crossDiff) * ((tangent_accel + tex_tangent_accel) * mix(1.0, rand_from_seed(alt_seed), tangent_accel_random)) : vec3(0.0);\n"; } + if (attractor_interaction_enabled) { + code += " force += ATTRACTOR_FORCE;\n\n"; + } + code += " // apply attractor forces\n"; code += " VELOCITY += force * DELTA;\n"; code += " // orbit velocity\n"; @@ -599,6 +615,13 @@ void ParticlesMaterial::_update_shader() { code += " VELOCITY.z = 0.0;\n"; code += " TRANSFORM[3].z = 0.0;\n"; } + if (collision_enabled) { + code += " if (COLLIDED) {\n"; + code += " TRANSFORM[3].xyz+=COLLISION_NORMAL * COLLISION_DEPTH;\n"; + code += " VELOCITY -= COLLISION_NORMAL * dot(COLLISION_NORMAL, VELOCITY) * (1.0 + collision_bounce);\n"; + code += " VELOCITY = mix(VELOCITY,vec3(0.0),collision_friction * DELTA * 100.0);\n"; + code += " }\n"; + } if (sub_emitter_mode != SUB_EMITTER_DISABLED) { code += " int emit_count = 0;\n"; switch (sub_emitter_mode) { @@ -609,6 +632,7 @@ void ParticlesMaterial::_update_shader() { } break; case SUB_EMITTER_AT_COLLISION: { //not implemented yet + code += " if (COLLIDED) emit_count = 1;\n"; } break; case SUB_EMITTER_AT_END: { //not implemented yet @@ -1072,6 +1096,51 @@ bool ParticlesMaterial::get_sub_emitter_keep_velocity() const { return sub_emitter_keep_velocity; } +void ParticlesMaterial::set_attractor_interaction_enabled(bool p_enable) { + attractor_interaction_enabled = p_enable; + _queue_shader_change(); +} + +bool ParticlesMaterial::is_attractor_interaction_enabled() const { + return attractor_interaction_enabled; +} + +void ParticlesMaterial::set_collision_enabled(bool p_enabled) { + collision_enabled = p_enabled; + _queue_shader_change(); +} + +bool ParticlesMaterial::is_collision_enabled() const { + return collision_enabled; +} + +void ParticlesMaterial::set_collision_use_scale(bool p_scale) { + collision_scale = p_scale; + _queue_shader_change(); +} + +bool ParticlesMaterial::is_collision_using_scale() const { + return collision_scale; +} + +void ParticlesMaterial::set_collision_friction(float p_friction) { + collision_friction = p_friction; + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->collision_friction, p_friction); +} + +float ParticlesMaterial::get_collision_friction() const { + return collision_friction; +} + +void ParticlesMaterial::set_collision_bounce(float p_bounce) { + collision_bounce = p_bounce; + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->collision_bounce, p_bounce); +} + +float ParticlesMaterial::get_collision_bounce() const { + return collision_bounce; +} + Shader::Mode ParticlesMaterial::get_shader_mode() const { return Shader::MODE_PARTICLES; } @@ -1143,6 +1212,21 @@ void ParticlesMaterial::_bind_methods() { ClassDB::bind_method(D_METHOD("get_sub_emitter_keep_velocity"), &ParticlesMaterial::get_sub_emitter_keep_velocity); ClassDB::bind_method(D_METHOD("set_sub_emitter_keep_velocity", "enable"), &ParticlesMaterial::set_sub_emitter_keep_velocity); + ClassDB::bind_method(D_METHOD("set_attractor_interaction_enabled", "enabled"), &ParticlesMaterial::set_attractor_interaction_enabled); + ClassDB::bind_method(D_METHOD("is_attractor_interaction_enabled"), &ParticlesMaterial::is_attractor_interaction_enabled); + + ClassDB::bind_method(D_METHOD("set_collision_enabled", "enabled"), &ParticlesMaterial::set_collision_enabled); + ClassDB::bind_method(D_METHOD("is_collision_enabled"), &ParticlesMaterial::is_collision_enabled); + + ClassDB::bind_method(D_METHOD("set_collision_use_scale", "radius"), &ParticlesMaterial::set_collision_use_scale); + ClassDB::bind_method(D_METHOD("is_collision_using_scale"), &ParticlesMaterial::is_collision_using_scale); + + ClassDB::bind_method(D_METHOD("set_collision_friction", "friction"), &ParticlesMaterial::set_collision_friction); + ClassDB::bind_method(D_METHOD("get_collision_friction"), &ParticlesMaterial::get_collision_friction); + + ClassDB::bind_method(D_METHOD("set_collision_bounce", "bounce"), &ParticlesMaterial::set_collision_bounce); + ClassDB::bind_method(D_METHOD("get_collision_bounce"), &ParticlesMaterial::get_collision_bounce); + ADD_GROUP("Time", ""); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lifetime_randomness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_lifetime_randomness", "get_lifetime_randomness"); @@ -1221,6 +1305,14 @@ void ParticlesMaterial::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "sub_emitter_amount_at_end", PROPERTY_HINT_RANGE, "1,32,1"), "set_sub_emitter_amount_at_end", "get_sub_emitter_amount_at_end"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sub_emitter_keep_velocity"), "set_sub_emitter_keep_velocity", "get_sub_emitter_keep_velocity"); + ADD_GROUP("Attractor Interaction", "attractor_interaction_"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "attractor_interaction_enabled"), "set_attractor_interaction_enabled", "is_attractor_interaction_enabled"); + ADD_GROUP("Collision", "collision_"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collision_enabled"), "set_collision_enabled", "is_collision_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision_friction", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_collision_friction", "get_collision_friction"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision_bounce", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_collision_bounce", "get_collision_bounce"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collision_use_scale"), "set_collision_use_scale", "is_collision_using_scale"); + BIND_ENUM_CONSTANT(PARAM_INITIAL_LINEAR_VELOCITY); BIND_ENUM_CONSTANT(PARAM_ANGULAR_VELOCITY); BIND_ENUM_CONSTANT(PARAM_ORBIT_VELOCITY); @@ -1283,6 +1375,12 @@ ParticlesMaterial::ParticlesMaterial() : set_sub_emitter_amount_at_end(1); set_sub_emitter_keep_velocity(false); + set_attractor_interaction_enabled(true); + set_collision_enabled(true); + set_collision_bounce(0.0); + set_collision_friction(0.0); + set_collision_use_scale(false); + for (int i = 0; i < PARAM_MAX; i++) { set_param_randomness(Parameter(i), 0); } diff --git a/scene/resources/particles_material.h b/scene/resources/particles_material.h index fa8858f67f..7aca708889 100644 --- a/scene/resources/particles_material.h +++ b/scene/resources/particles_material.h @@ -37,11 +37,7 @@ /* TODO: -Path following -*Manual emission --Sub Emitters --Attractors -Emitter positions deformable by bones --Collision -Proper trails */ @@ -99,6 +95,9 @@ private: uint32_t invalid_key : 1; uint32_t has_emission_color : 1; uint32_t sub_emitter : 2; + uint32_t attractor_enabled : 1; + uint32_t collision_enabled : 1; + uint32_t collision_scale : 1; }; uint32_t key; @@ -135,6 +134,9 @@ private: mk.emission_shape = emission_shape; mk.has_emission_color = emission_shape >= EMISSION_SHAPE_POINTS && emission_color_texture.is_valid(); mk.sub_emitter = sub_emitter_mode; + mk.collision_enabled = collision_enabled; + mk.attractor_enabled = attractor_interaction_enabled; + mk.collision_scale = collision_scale; return mk; } @@ -201,6 +203,9 @@ private: StringName sub_emitter_frequency; StringName sub_emitter_amount_at_end; StringName sub_emitter_keep_velocity; + + StringName collision_friction; + StringName collision_bounce; }; static ShaderNames *shader_names; @@ -244,6 +249,12 @@ private: bool sub_emitter_keep_velocity; //do not save emission points here + bool attractor_interaction_enabled; + bool collision_enabled; + bool collision_scale; + float collision_friction; + float collision_bounce; + protected: static void _bind_methods(); virtual void _validate_property(PropertyInfo &property) const override; @@ -298,6 +309,21 @@ public: void set_lifetime_randomness(float p_lifetime); float get_lifetime_randomness() const; + void set_attractor_interaction_enabled(bool p_enable); + bool is_attractor_interaction_enabled() const; + + void set_collision_enabled(bool p_enabled); + bool is_collision_enabled() const; + + void set_collision_use_scale(bool p_scale); + bool is_collision_using_scale() const; + + void set_collision_friction(float p_friction); + float get_collision_friction() const; + + void set_collision_bounce(float p_bounce); + float get_collision_bounce() const; + static void init_shaders(); static void finish_shaders(); static void flush_changes(); diff --git a/servers/rendering/rasterizer.h b/servers/rendering/rasterizer.h index a24189bdd7..84c04f34b6 100644 --- a/servers/rendering/rasterizer.h +++ b/servers/rendering/rasterizer.h @@ -299,6 +299,7 @@ public: virtual void render_material(const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_ortogonal, InstanceBase **p_cull_result, int p_cull_count, RID p_framebuffer, const Rect2i &p_region) = 0; virtual void render_sdfgi(RID p_render_buffers, int p_region, InstanceBase **p_cull_result, int p_cull_count) = 0; virtual void render_sdfgi_static_lights(RID p_render_buffers, uint32_t p_cascade_count, const uint32_t *p_cascade_indices, const RID **p_positional_light_cull_result, const uint32_t *p_positional_light_cull_count) = 0; + virtual void render_particle_collider_heightfield(RID p_collider, const Transform &p_transform, InstanceBase **p_cull_result, int p_cull_count) = 0; virtual void set_scene_pass(uint64_t p_pass) = 0; virtual void set_time(double p_time, double p_step) = 0; @@ -660,6 +661,7 @@ public: virtual void particles_set_process_material(RID p_particles, RID p_material) = 0; virtual void particles_set_fixed_fps(RID p_particles, int p_fps) = 0; virtual void particles_set_fractional_delta(RID p_particles, bool p_enable) = 0; + virtual void particles_set_collision_base_size(RID p_particles, float p_size) = 0; virtual void particles_restart(RID p_particles) = 0; virtual void particles_emit(RID p_particles, const Transform &p_transform, const Vector3 &p_velocity, const Color &p_color, const Color &p_custom, uint32_t p_emit_flags) = 0; virtual void particles_set_subemitter(RID p_particles, RID p_subemitter_particles) = 0; @@ -682,6 +684,28 @@ public: virtual void particles_set_view_axis(RID p_particles, const Vector3 &p_axis) = 0; + virtual void particles_add_collision(RID p_particles, RasterizerScene::InstanceBase *p_instance) = 0; + virtual void particles_remove_collision(RID p_particles, RasterizerScene::InstanceBase *p_instance) = 0; + + virtual void update_particles() = 0; + + /* PARTICLES COLLISION */ + + virtual RID particles_collision_create() = 0; + virtual void particles_collision_set_collision_type(RID p_particles_collision, RS::ParticlesCollisionType p_type) = 0; + virtual void particles_collision_set_cull_mask(RID p_particles_collision, uint32_t p_cull_mask) = 0; + virtual void particles_collision_set_sphere_radius(RID p_particles_collision, float p_radius) = 0; //for spheres + virtual void particles_collision_set_box_extents(RID p_particles_collision, const Vector3 &p_extents) = 0; //for non-spheres + virtual void particles_collision_set_attractor_strength(RID p_particles_collision, float p_strength) = 0; + virtual void particles_collision_set_attractor_directionality(RID p_particles_collision, float p_directionality) = 0; + virtual void particles_collision_set_attractor_attenuation(RID p_particles_collision, float p_curve) = 0; + virtual void particles_collision_set_field_texture(RID p_particles_collision, RID p_texture) = 0; //for SDF and vector field, heightfield is dynamic + virtual void particles_collision_height_field_update(RID p_particles_collision) = 0; //for SDF and vector field + virtual void particles_collision_set_height_field_resolution(RID p_particles_collision, RS::ParticlesCollisionHeightfieldResolution p_resolution) = 0; //for SDF and vector field + virtual AABB particles_collision_get_aabb(RID p_particles_collision) const = 0; + virtual bool particles_collision_is_heightfield(RID p_particles_collision) const = 0; + virtual RID particles_collision_get_heightfield_framebuffer(RID p_particles_collision) const = 0; + /* GLOBAL VARIABLES */ virtual void global_variable_add(const StringName &p_name, RS::GlobalVariableType p_type, const Variant &p_value) = 0; diff --git a/servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.cpp b/servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.cpp index c56c208098..ac028e93f1 100644 --- a/servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.cpp +++ b/servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.cpp @@ -2018,6 +2018,39 @@ void RasterizerSceneHighEndRD::_render_shadow(RID p_framebuffer, InstanceBase ** } } +void RasterizerSceneHighEndRD::_render_particle_collider_heightfield(RID p_fb, const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, InstanceBase **p_cull_result, int p_cull_count) { + RENDER_TIMESTAMP("Setup Render Collider Heightfield"); + + _update_render_base_uniform_set(); + + render_pass++; + + scene_state.ubo.dual_paraboloid_side = 0; + + _setup_environment(RID(), RID(), p_cam_projection, p_cam_transform, RID(), true, Vector2(1, 1), RID(), true, Color(), 0, p_cam_projection.get_z_far(), false, false); + + render_list.clear(); + + PassMode pass_mode = PASS_MODE_SHADOW; + + _fill_render_list(p_cull_result, p_cull_count, pass_mode); + + _setup_view_dependant_uniform_set(RID(), RID(), nullptr, 0); + + RENDER_TIMESTAMP("Render Collider Heightield"); + + render_list.sort_by_key(false); + + _fill_instances(render_list.elements, render_list.element_count, true); + + { + //regular forward for now + RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(p_fb, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_READ); + _render_list(draw_list, RD::get_singleton()->framebuffer_get_format(p_fb), render_list.elements, render_list.element_count, false, pass_mode, true, RID(), RID()); + RD::get_singleton()->draw_list_end(); + } +} + void RasterizerSceneHighEndRD::_render_material(const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_ortogonal, InstanceBase **p_cull_result, int p_cull_count, RID p_framebuffer, const Rect2i &p_region) { RENDER_TIMESTAMP("Setup Rendering Material"); diff --git a/servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.h b/servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.h index 1aad9039ff..4c89928c95 100644 --- a/servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.h +++ b/servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.h @@ -581,6 +581,7 @@ protected: virtual void _render_material(const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_ortogonal, InstanceBase **p_cull_result, int p_cull_count, RID p_framebuffer, const Rect2i &p_region); virtual void _render_uv2(InstanceBase **p_cull_result, int p_cull_count, RID p_framebuffer, const Rect2i &p_region); virtual void _render_sdfgi(RID p_render_buffers, const Vector3i &p_from, const Vector3i &p_size, const AABB &p_bounds, InstanceBase **p_cull_result, int p_cull_count, const RID &p_albedo_texture, const RID &p_emission_texture, const RID &p_emission_aniso_texture, const RID &p_geom_facing_texture); + virtual void _render_particle_collider_heightfield(RID p_fb, const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, InstanceBase **p_cull_result, int p_cull_count); public: virtual void set_time(double p_time, double p_step); diff --git a/servers/rendering/rasterizer_rd/rasterizer_scene_rd.cpp b/servers/rendering/rasterizer_rd/rasterizer_scene_rd.cpp index 958d8eac1f..934330cc9b 100644 --- a/servers/rendering/rasterizer_rd/rasterizer_scene_rd.cpp +++ b/servers/rendering/rasterizer_rd/rasterizer_scene_rd.cpp @@ -7511,6 +7511,23 @@ void RasterizerSceneRD::render_sdfgi(RID p_render_buffers, int p_region, Instanc } } +void RasterizerSceneRD::render_particle_collider_heightfield(RID p_collider, const Transform &p_transform, InstanceBase **p_cull_result, int p_cull_count) { + ERR_FAIL_COND(!storage->particles_collision_is_heightfield(p_collider)); + Vector3 extents = storage->particles_collision_get_extents(p_collider) * p_transform.basis.get_scale(); + CameraMatrix cm; + cm.set_orthogonal(-extents.x, extents.x, -extents.z, extents.z, 0, extents.y * 2.0); + + Vector3 cam_pos = p_transform.origin; + cam_pos.y += extents.y; + + Transform cam_xform; + cam_xform.set_look_at(cam_pos, cam_pos - p_transform.basis.get_axis(Vector3::AXIS_Y), -p_transform.basis.get_axis(Vector3::AXIS_Z).normalized()); + + RID fb = storage->particles_collision_get_heightfield_framebuffer(p_collider); + + _render_particle_collider_heightfield(fb, cam_xform, cm, p_cull_result, p_cull_count); +} + void RasterizerSceneRD::render_sdfgi_static_lights(RID p_render_buffers, uint32_t p_cascade_count, const uint32_t *p_cascade_indices, const RID **p_positional_light_cull_result, const uint32_t *p_positional_light_cull_count) { RenderBuffers *rb = render_buffers_owner.getornull(p_render_buffers); ERR_FAIL_COND(!rb); diff --git a/servers/rendering/rasterizer_rd/rasterizer_scene_rd.h b/servers/rendering/rasterizer_rd/rasterizer_scene_rd.h index fe31d2f76b..0e7e56716b 100644 --- a/servers/rendering/rasterizer_rd/rasterizer_scene_rd.h +++ b/servers/rendering/rasterizer_rd/rasterizer_scene_rd.h @@ -112,6 +112,7 @@ protected: virtual void _render_material(const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_ortogonal, InstanceBase **p_cull_result, int p_cull_count, RID p_framebuffer, const Rect2i &p_region) = 0; virtual void _render_uv2(InstanceBase **p_cull_result, int p_cull_count, RID p_framebuffer, const Rect2i &p_region) = 0; virtual void _render_sdfgi(RID p_render_buffers, const Vector3i &p_from, const Vector3i &p_size, const AABB &p_bounds, InstanceBase **p_cull_result, int p_cull_count, const RID &p_albedo_texture, const RID &p_emission_texture, const RID &p_emission_aniso_texture, const RID &p_geom_facing_texture) = 0; + virtual void _render_particle_collider_heightfield(RID p_fb, const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, InstanceBase **p_cull_result, int p_cull_count) = 0; virtual void _debug_giprobe(RID p_gi_probe, RenderingDevice::DrawListID p_draw_list, RID p_framebuffer, const CameraMatrix &p_camera_with_transform, bool p_lighting, bool p_emission, float p_alpha); void _debug_sdfgi_probes(RID p_render_buffers, RD::DrawListID p_draw_list, RID p_framebuffer, const CameraMatrix &p_camera_with_transform); @@ -1876,6 +1877,8 @@ public: void render_sdfgi(RID p_render_buffers, int p_region, InstanceBase **p_cull_result, int p_cull_count); void render_sdfgi_static_lights(RID p_render_buffers, uint32_t p_cascade_count, const uint32_t *p_cascade_indices, const RID **p_positional_light_cull_result, const uint32_t *p_positional_light_cull_count); + void render_particle_collider_heightfield(RID p_collider, const Transform &p_transform, InstanceBase **p_cull_result, int p_cull_count); + virtual void set_scene_pass(uint64_t p_pass) { scene_pass = p_pass; } diff --git a/servers/rendering/rasterizer_rd/rasterizer_storage_rd.cpp b/servers/rendering/rasterizer_rd/rasterizer_storage_rd.cpp index db2c707984..6448872610 100644 --- a/servers/rendering/rasterizer_rd/rasterizer_storage_rd.cpp +++ b/servers/rendering/rasterizer_rd/rasterizer_storage_rd.cpp @@ -3333,6 +3333,10 @@ void RasterizerStorageRD::_particles_free_data(Particles *particles) { particles->particles_transforms_buffer_uniform_set = RID(); particles->particle_buffer = RID(); + if (RD::get_singleton()->uniform_set_is_valid(particles->collision_textures_uniform_set)) { + RD::get_singleton()->free(particles->collision_textures_uniform_set); + } + if (particles->particles_sort_buffer.is_valid()) { RD::get_singleton()->free(particles->particles_sort_buffer); particles->particles_sort_buffer = RID(); @@ -3454,6 +3458,13 @@ void RasterizerStorageRD::particles_set_fractional_delta(RID p_particles, bool p particles->fractional_delta = p_enable; } +void RasterizerStorageRD::particles_set_collision_base_size(RID p_particles, float p_size) { + Particles *particles = particles_owner.getornull(p_particles); + ERR_FAIL_COND(!particles); + + particles->collision_base_size = p_size; +} + void RasterizerStorageRD::particles_set_process_material(RID p_particles, RID p_material) { Particles *particles = particles_owner.getornull(p_particles); ERR_FAIL_COND(!particles); @@ -3646,6 +3657,22 @@ RID RasterizerStorageRD::particles_get_draw_pass_mesh(RID p_particles, int p_pas return particles->draw_passes[p_pass]; } +void RasterizerStorageRD::particles_add_collision(RID p_particles, RasterizerScene::InstanceBase *p_instance) { + Particles *particles = particles_owner.getornull(p_particles); + ERR_FAIL_COND(!particles); + + ERR_FAIL_COND(p_instance->base_type != RS::INSTANCE_PARTICLES_COLLISION); + + particles->collisions.insert(p_instance); +} + +void RasterizerStorageRD::particles_remove_collision(RID p_particles, RasterizerScene::InstanceBase *p_instance) { + Particles *particles = particles_owner.getornull(p_particles); + ERR_FAIL_COND(!particles); + + particles->collisions.erase(p_instance); +} + void RasterizerStorageRD::_particles_process(Particles *p_particles, float p_delta) { if (p_particles->particles_material_uniform_set.is_null() || !RD::get_singleton()->uniform_set_is_valid(p_particles->particles_material_uniform_set)) { Vector<RD::Uniform> uniforms; @@ -3729,6 +3756,195 @@ void RasterizerStorageRD::_particles_process(Particles *p_particles, float p_del frame_params.cycle = p_particles->cycle_number; + { //collision and attractors + + frame_params.collider_count = 0; + frame_params.attractor_count = 0; + frame_params.particle_size = p_particles->collision_base_size; + + RID collision_3d_textures[ParticlesFrameParams::MAX_3D_TEXTURES]; + RID collision_heightmap_texture; + + Transform to_particles; + if (p_particles->use_local_coords) { + to_particles = p_particles->emission_transform.affine_inverse(); + } + uint32_t collision_3d_textures_used = 0; + for (const Set<RasterizerScene::InstanceBase *>::Element *E = p_particles->collisions.front(); E; E = E->next()) { + ParticlesCollision *pc = particles_collision_owner.getornull(E->get()->base); + Transform to_collider = E->get()->transform; + if (p_particles->use_local_coords) { + to_collider = to_particles * to_collider; + } + Vector3 scale = to_collider.basis.get_scale(); + to_collider.basis.orthonormalize(); + + if (pc->type <= RS::PARTICLES_COLLISION_TYPE_VECTOR_FIELD_ATTRACT) { + //attractor + if (frame_params.attractor_count >= ParticlesFrameParams::MAX_ATTRACTORS) { + continue; + } + + ParticlesFrameParams::Attractor &attr = frame_params.attractors[frame_params.attractor_count]; + + store_transform(to_collider, attr.transform); + attr.strength = pc->attractor_strength; + attr.attenuation = pc->attractor_attenuation; + attr.directionality = pc->attractor_directionality; + + switch (pc->type) { + case RS::PARTICLES_COLLISION_TYPE_SPHERE_ATTRACT: { + attr.type = ParticlesFrameParams::ATTRACTOR_TYPE_SPHERE; + float radius = pc->radius; + radius *= (scale.x + scale.y + scale.z) / 3.0; + attr.extents[0] = radius; + attr.extents[1] = radius; + attr.extents[2] = radius; + } break; + case RS::PARTICLES_COLLISION_TYPE_BOX_ATTRACT: { + attr.type = ParticlesFrameParams::ATTRACTOR_TYPE_BOX; + Vector3 extents = pc->extents * scale; + attr.extents[0] = extents.x; + attr.extents[1] = extents.y; + attr.extents[2] = extents.z; + } break; + case RS::PARTICLES_COLLISION_TYPE_VECTOR_FIELD_ATTRACT: { + if (collision_3d_textures_used >= ParticlesFrameParams::MAX_3D_TEXTURES) { + continue; + } + attr.type = ParticlesFrameParams::ATTRACTOR_TYPE_VECTOR_FIELD; + Vector3 extents = pc->extents * scale; + attr.extents[0] = extents.x; + attr.extents[1] = extents.y; + attr.extents[2] = extents.z; + attr.texture_index = collision_3d_textures_used; + + collision_3d_textures[collision_3d_textures_used] = pc->field_texture; + collision_3d_textures_used++; + } break; + default: { + } + } + + frame_params.attractor_count++; + } else { + //collider + if (frame_params.collider_count >= ParticlesFrameParams::MAX_COLLIDERS) { + continue; + } + + ParticlesFrameParams::Collider &col = frame_params.colliders[frame_params.collider_count]; + + store_transform(to_collider, col.transform); + switch (pc->type) { + case RS::PARTICLES_COLLISION_TYPE_SPHERE_COLLIDE: { + col.type = ParticlesFrameParams::COLLISION_TYPE_SPHERE; + float radius = pc->radius; + radius *= (scale.x + scale.y + scale.z) / 3.0; + col.extents[0] = radius; + col.extents[1] = radius; + col.extents[2] = radius; + } break; + case RS::PARTICLES_COLLISION_TYPE_BOX_COLLIDE: { + col.type = ParticlesFrameParams::COLLISION_TYPE_BOX; + Vector3 extents = pc->extents * scale; + col.extents[0] = extents.x; + col.extents[1] = extents.y; + col.extents[2] = extents.z; + } break; + case RS::PARTICLES_COLLISION_TYPE_SDF_COLLIDE: { + if (collision_3d_textures_used >= ParticlesFrameParams::MAX_3D_TEXTURES) { + continue; + } + col.type = ParticlesFrameParams::COLLISION_TYPE_SDF; + Vector3 extents = pc->extents * scale; + col.extents[0] = extents.x; + col.extents[1] = extents.y; + col.extents[2] = extents.z; + col.texture_index = collision_3d_textures_used; + col.scale = (scale.x + scale.y + scale.z) * 0.333333333333; //non uniform scale non supported + + collision_3d_textures[collision_3d_textures_used] = pc->field_texture; + collision_3d_textures_used++; + } break; + case RS::PARTICLES_COLLISION_TYPE_HEIGHTFIELD_COLLIDE: { + if (collision_heightmap_texture != RID()) { //already taken + continue; + } + + col.type = ParticlesFrameParams::COLLISION_TYPE_HEIGHT_FIELD; + Vector3 extents = pc->extents * scale; + col.extents[0] = extents.x; + col.extents[1] = extents.y; + col.extents[2] = extents.z; + collision_heightmap_texture = pc->heightfield_texture; + } break; + default: { + } + } + + frame_params.collider_count++; + } + } + + bool different = false; + if (collision_3d_textures_used == p_particles->collision_3d_textures_used) { + for (int i = 0; i < ParticlesFrameParams::MAX_3D_TEXTURES; i++) { + if (p_particles->collision_3d_textures[i] != collision_3d_textures[i]) { + different = true; + break; + } + } + } + + if (collision_heightmap_texture != p_particles->collision_heightmap_texture) { + different = true; + } + + bool uniform_set_valid = RD::get_singleton()->uniform_set_is_valid(p_particles->collision_textures_uniform_set); + + if (different || !uniform_set_valid) { + if (uniform_set_valid) { + RD::get_singleton()->free(p_particles->collision_textures_uniform_set); + } + + Vector<RD::Uniform> uniforms; + + { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_TEXTURE; + u.binding = 0; + for (uint32_t i = 0; i < ParticlesFrameParams::MAX_3D_TEXTURES; i++) { + RID rd_tex; + if (i < collision_3d_textures_used) { + Texture *t = texture_owner.getornull(collision_3d_textures[i]); + if (t && t->type == Texture::TYPE_3D) { + rd_tex = t->rd_texture; + } + } + + if (rd_tex == RID()) { + rd_tex = default_rd_textures[DEFAULT_RD_TEXTURE_3D_WHITE]; + } + u.ids.push_back(rd_tex); + } + uniforms.push_back(u); + } + { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_TEXTURE; + u.binding = 1; + if (collision_heightmap_texture.is_valid()) { + u.ids.push_back(collision_heightmap_texture); + } else { + u.ids.push_back(default_rd_textures[DEFAULT_RD_TEXTURE_BLACK]); + } + uniforms.push_back(u); + } + p_particles->collision_textures_uniform_set = RD::get_singleton()->uniform_set_create(uniforms, particles_shader.default_shader_rd, 2); + } + } + ParticlesShader::PushConstant push_constant; push_constant.clear = p_particles->clear; @@ -3783,8 +3999,10 @@ void RasterizerStorageRD::_particles_process(Particles *p_particles, float p_del RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, m->shader_data->pipeline); RD::get_singleton()->compute_list_bind_uniform_set(compute_list, particles_shader.base_uniform_set, 0); RD::get_singleton()->compute_list_bind_uniform_set(compute_list, p_particles->particles_material_uniform_set, 1); + RD::get_singleton()->compute_list_bind_uniform_set(compute_list, p_particles->collision_textures_uniform_set, 2); + if (m->uniform_set.is_valid()) { - RD::get_singleton()->compute_list_bind_uniform_set(compute_list, m->uniform_set, 2); + RD::get_singleton()->compute_list_bind_uniform_set(compute_list, m->uniform_set, 3); } RD::get_singleton()->compute_list_set_push_constant(compute_list, &push_constant, sizeof(ParticlesShader::PushConstant)); @@ -4190,7 +4408,7 @@ void RasterizerStorageRD::ParticlesMaterialData::update_parameters(const Map<Str } } - uniform_set = RD::get_singleton()->uniform_set_create(uniforms, base_singleton->particles_shader.shader.version_get_shader(shader_data->version, 0), 2); + uniform_set = RD::get_singleton()->uniform_set_create(uniforms, base_singleton->particles_shader.shader.version_get_shader(shader_data->version, 0), 3); } RasterizerStorageRD::ParticlesMaterialData::~ParticlesMaterialData() { @@ -4211,6 +4429,171 @@ RasterizerStorageRD::MaterialData *RasterizerStorageRD::_create_particles_materi return material_data; } //////// + +/* PARTICLES COLLISION API */ + +RID RasterizerStorageRD::particles_collision_create() { + return particles_collision_owner.make_rid(ParticlesCollision()); +} + +RID RasterizerStorageRD::particles_collision_get_heightfield_framebuffer(RID p_particles_collision) const { + ParticlesCollision *particles_collision = particles_collision_owner.getornull(p_particles_collision); + ERR_FAIL_COND_V(!particles_collision, RID()); + ERR_FAIL_COND_V(particles_collision->type != RS::PARTICLES_COLLISION_TYPE_HEIGHTFIELD_COLLIDE, RID()); + + if (particles_collision->heightfield_texture == RID()) { + //create + int resolutions[RS::PARTICLES_COLLISION_HEIGHTFIELD_RESOLUTION_MAX] = { 256, 512, 1024, 2048, 4096, 8192 }; + Size2i size; + if (particles_collision->extents.x > particles_collision->extents.z) { + size.x = resolutions[particles_collision->heightfield_resolution]; + size.y = int32_t(particles_collision->extents.z / particles_collision->extents.x * size.x); + } else { + size.y = resolutions[particles_collision->heightfield_resolution]; + size.x = int32_t(particles_collision->extents.x / particles_collision->extents.z * size.y); + } + + RD::TextureFormat tf; + tf.format = RD::DATA_FORMAT_D32_SFLOAT; + tf.width = size.x; + tf.height = size.y; + tf.type = RD::TEXTURE_TYPE_2D; + tf.usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + + particles_collision->heightfield_texture = RD::get_singleton()->texture_create(tf, RD::TextureView()); + + Vector<RID> fb_tex; + fb_tex.push_back(particles_collision->heightfield_texture); + particles_collision->heightfield_fb = RD::get_singleton()->framebuffer_create(fb_tex); + particles_collision->heightfield_fb_size = size; + } + + return particles_collision->heightfield_fb; +} + +void RasterizerStorageRD::particles_collision_set_collision_type(RID p_particles_collision, RS::ParticlesCollisionType p_type) { + ParticlesCollision *particles_collision = particles_collision_owner.getornull(p_particles_collision); + ERR_FAIL_COND(!particles_collision); + + if (p_type == particles_collision->type) { + return; + } + + if (particles_collision->heightfield_texture.is_valid()) { + RD::get_singleton()->free(particles_collision->heightfield_texture); + particles_collision->heightfield_texture = RID(); + } + particles_collision->type = p_type; + particles_collision->instance_dependency.instance_notify_changed(true, false); +} + +void RasterizerStorageRD::particles_collision_set_cull_mask(RID p_particles_collision, uint32_t p_cull_mask) { + ParticlesCollision *particles_collision = particles_collision_owner.getornull(p_particles_collision); + ERR_FAIL_COND(!particles_collision); + particles_collision->cull_mask = p_cull_mask; +} + +void RasterizerStorageRD::particles_collision_set_sphere_radius(RID p_particles_collision, float p_radius) { + ParticlesCollision *particles_collision = particles_collision_owner.getornull(p_particles_collision); + ERR_FAIL_COND(!particles_collision); + + particles_collision->radius = p_radius; + particles_collision->instance_dependency.instance_notify_changed(true, false); +} + +void RasterizerStorageRD::particles_collision_set_box_extents(RID p_particles_collision, const Vector3 &p_extents) { + ParticlesCollision *particles_collision = particles_collision_owner.getornull(p_particles_collision); + ERR_FAIL_COND(!particles_collision); + + particles_collision->extents = p_extents; + particles_collision->instance_dependency.instance_notify_changed(true, false); +} + +void RasterizerStorageRD::particles_collision_set_attractor_strength(RID p_particles_collision, float p_strength) { + ParticlesCollision *particles_collision = particles_collision_owner.getornull(p_particles_collision); + ERR_FAIL_COND(!particles_collision); + + particles_collision->attractor_strength = p_strength; +} + +void RasterizerStorageRD::particles_collision_set_attractor_directionality(RID p_particles_collision, float p_directionality) { + ParticlesCollision *particles_collision = particles_collision_owner.getornull(p_particles_collision); + ERR_FAIL_COND(!particles_collision); + + particles_collision->attractor_directionality = p_directionality; +} + +void RasterizerStorageRD::particles_collision_set_attractor_attenuation(RID p_particles_collision, float p_curve) { + ParticlesCollision *particles_collision = particles_collision_owner.getornull(p_particles_collision); + ERR_FAIL_COND(!particles_collision); + + particles_collision->attractor_attenuation = p_curve; +} + +void RasterizerStorageRD::particles_collision_set_field_texture(RID p_particles_collision, RID p_texture) { + ParticlesCollision *particles_collision = particles_collision_owner.getornull(p_particles_collision); + ERR_FAIL_COND(!particles_collision); + + particles_collision->field_texture = p_texture; +} + +void RasterizerStorageRD::particles_collision_height_field_update(RID p_particles_collision) { + ParticlesCollision *particles_collision = particles_collision_owner.getornull(p_particles_collision); + ERR_FAIL_COND(!particles_collision); + particles_collision->instance_dependency.instance_notify_changed(true, false); +} + +void RasterizerStorageRD::particles_collision_set_height_field_resolution(RID p_particles_collision, RS::ParticlesCollisionHeightfieldResolution p_resolution) { + ParticlesCollision *particles_collision = particles_collision_owner.getornull(p_particles_collision); + ERR_FAIL_COND(!particles_collision); + + if (particles_collision->heightfield_resolution == p_resolution) { + return; + } + + particles_collision->heightfield_resolution = p_resolution; + + if (particles_collision->heightfield_texture.is_valid()) { + RD::get_singleton()->free(particles_collision->heightfield_texture); + particles_collision->heightfield_texture = RID(); + } +} + +AABB RasterizerStorageRD::particles_collision_get_aabb(RID p_particles_collision) const { + ParticlesCollision *particles_collision = particles_collision_owner.getornull(p_particles_collision); + ERR_FAIL_COND_V(!particles_collision, AABB()); + + switch (particles_collision->type) { + case RS::PARTICLES_COLLISION_TYPE_SPHERE_ATTRACT: + case RS::PARTICLES_COLLISION_TYPE_SPHERE_COLLIDE: { + AABB aabb; + aabb.position = -Vector3(1, 1, 1) * particles_collision->radius; + aabb.size = Vector3(2, 2, 2) * particles_collision->radius; + return aabb; + } + default: { + AABB aabb; + aabb.position = -particles_collision->extents; + aabb.size = particles_collision->extents * 2; + return aabb; + } + } + + return AABB(); +} + +Vector3 RasterizerStorageRD::particles_collision_get_extents(RID p_particles_collision) const { + const ParticlesCollision *particles_collision = particles_collision_owner.getornull(p_particles_collision); + ERR_FAIL_COND_V(!particles_collision, Vector3()); + return particles_collision->extents; +} + +bool RasterizerStorageRD::particles_collision_is_heightfield(RID p_particles_collision) const { + const ParticlesCollision *particles_collision = particles_collision_owner.getornull(p_particles_collision); + ERR_FAIL_COND_V(!particles_collision, false); + return particles_collision->type == RS::PARTICLES_COLLISION_TYPE_HEIGHTFIELD_COLLIDE; +} + /* SKELETON API */ RID RasterizerStorageRD::skeleton_create() { @@ -4680,6 +5063,9 @@ void RasterizerStorageRD::reflection_probe_set_extents(RID p_probe, const Vector ReflectionProbe *reflection_probe = reflection_probe_owner.getornull(p_probe); ERR_FAIL_COND(!reflection_probe); + if (reflection_probe->extents == p_extents) { + return; + } reflection_probe->extents = p_extents; reflection_probe->instance_dependency.instance_notify_changed(true, false); } @@ -5797,6 +6183,9 @@ void RasterizerStorageRD::base_update_dependency(RID p_base, RasterizerScene::In } else if (particles_owner.owns(p_base)) { Particles *p = particles_owner.getornull(p_base); p_instance->update_dependency(&p->instance_dependency); + } else if (particles_collision_owner.owns(p_base)) { + ParticlesCollision *pc = particles_collision_owner.getornull(p_base); + p_instance->update_dependency(&pc->instance_dependency); } } @@ -5832,6 +6221,9 @@ RS::InstanceType RasterizerStorageRD::get_base_type(RID p_rid) const { if (particles_owner.owns(p_rid)) { return RS::INSTANCE_PARTICLES; } + if (particles_collision_owner.owns(p_rid)) { + return RS::INSTANCE_PARTICLES_COLLISION; + } return RS::INSTANCE_NONE; } @@ -6735,8 +7127,6 @@ void RasterizerStorageRD::update_dirty_resources() { _update_dirty_multimeshes(); _update_dirty_skeletons(); _update_decal_atlas(); - - update_particles(); } bool RasterizerStorageRD::has_os_feature(const String &p_feature) const { @@ -6871,6 +7261,14 @@ bool RasterizerStorageRD::free(RID p_rid) { _particles_free_data(particles); particles->instance_dependency.instance_notify_deleted(p_rid); particles_owner.free(p_rid); + } else if (particles_collision_owner.owns(p_rid)) { + ParticlesCollision *particles_collision = particles_collision_owner.getornull(p_rid); + + if (particles_collision->heightfield_texture.is_valid()) { + RD::get_singleton()->free(particles_collision->heightfield_texture); + } + particles_collision->instance_dependency.instance_notify_deleted(p_rid); + particles_collision_owner.free(p_rid); } else if (render_target_owner.owns(p_rid)) { RenderTarget *rt = render_target_owner.getornull(p_rid); @@ -7379,14 +7777,19 @@ RasterizerStorageRD::RasterizerStorageRD() { actions.renames["RESTART_COLOR"] = "restart_color"; actions.renames["RESTART_CUSTOM"] = "restart_custom"; actions.renames["emit_particle"] = "emit_particle"; + actions.renames["COLLIDED"] = "collided"; + actions.renames["COLLISION_NORMAL"] = "collision_normal"; + actions.renames["COLLISION_DEPTH"] = "collision_depth"; + actions.renames["ATTRACTOR_FORCE"] = "attractor_force"; actions.render_mode_defines["disable_force"] = "#define DISABLE_FORCE\n"; actions.render_mode_defines["disable_velocity"] = "#define DISABLE_VELOCITY\n"; actions.render_mode_defines["keep_data"] = "#define ENABLE_KEEP_DATA\n"; + actions.render_mode_defines["collision_use_scale"] = "#define USE_COLLISON_SCALE\n"; actions.sampler_array_name = "material_samplers"; actions.base_texture_binding_index = 1; - actions.texture_layout_set = 2; + actions.texture_layout_set = 3; actions.base_uniform_string = "material."; actions.base_varying_index = 10; diff --git a/servers/rendering/rasterizer_rd/rasterizer_storage_rd.h b/servers/rendering/rasterizer_rd/rasterizer_storage_rd.h index e14b9528cf..b03a26e200 100644 --- a/servers/rendering/rasterizer_rd/rasterizer_storage_rd.h +++ b/servers/rendering/rasterizer_rd/rasterizer_storage_rd.h @@ -475,6 +475,46 @@ private: }; struct ParticlesFrameParams { + enum { + MAX_ATTRACTORS = 32, + MAX_COLLIDERS = 32, + MAX_3D_TEXTURES = 7 + }; + + enum AttractorType { + ATTRACTOR_TYPE_SPHERE, + ATTRACTOR_TYPE_BOX, + ATTRACTOR_TYPE_VECTOR_FIELD, + }; + + struct Attractor { + float transform[16]; + float extents[3]; //exents or radius + uint32_t type; + + uint32_t texture_index; //texture index for vector field + float strength; + float attenuation; + float directionality; + }; + + enum CollisionType { + COLLISION_TYPE_SPHERE, + COLLISION_TYPE_BOX, + COLLISION_TYPE_SDF, + COLLISION_TYPE_HEIGHT_FIELD + }; + + struct Collider { + float transform[16]; + float extents[3]; //exents or radius + uint32_t type; + + uint32_t texture_index; //texture index for vector field + float scale; + uint32_t pad[2]; + }; + uint32_t emitting; float system_phase; float prev_system_phase; @@ -486,9 +526,14 @@ private: float delta; uint32_t random_seed; - uint32_t pad[3]; + uint32_t attractor_count; + uint32_t collider_count; + float particle_size; float emission_transform[16]; + + Attractor attractors[MAX_ATTRACTORS]; + Collider colliders[MAX_COLLIDERS]; }; struct ParticleEmissionBufferData { @@ -536,6 +581,11 @@ private: RID particles_material_uniform_set; RID particles_copy_uniform_set; RID particles_transforms_buffer_uniform_set; + RID collision_textures_uniform_set; + + RID collision_3d_textures[ParticlesFrameParams::MAX_3D_TEXTURES]; + uint32_t collision_3d_textures_used = 0; + RID collision_heightmap_texture; RID particles_sort_buffer; RID particles_sort_uniform_set; @@ -557,6 +607,7 @@ private: int fixed_fps; bool fractional_delta; float frame_remainder; + float collision_base_size; bool clear; @@ -569,6 +620,8 @@ private: ParticleEmissionBuffer *emission_buffer = nullptr; RID emission_storage_buffer; + Set<RasterizerScene::InstanceBase *> collisions; + Particles() : inactive(true), inactive_time(0.0), @@ -590,6 +643,7 @@ private: fixed_fps(0), fractional_delta(false), frame_remainder(0), + collision_base_size(0.01), clear(true) { } @@ -704,6 +758,28 @@ private: mutable RID_Owner<Particles> particles_owner; + /* Particles Collision */ + + struct ParticlesCollision { + RS::ParticlesCollisionType type = RS::PARTICLES_COLLISION_TYPE_SPHERE_ATTRACT; + uint32_t cull_mask = 0xFFFFFFFF; + float radius = 1.0; + Vector3 extents = Vector3(1, 1, 1); + float attractor_strength = 1.0; + float attractor_attenuation = 1.0; + float attractor_directionality = 0.0; + RID field_texture; + RID heightfield_texture; + RID heightfield_fb; + Size2i heightfield_fb_size; + + RS::ParticlesCollisionHeightfieldResolution heightfield_resolution = RS::PARTICLES_COLLISION_HEIGHTFIELD_RESOLUTION_1024; + + RasterizerScene::InstanceDependency instance_dependency; + }; + + mutable RID_Owner<ParticlesCollision> particles_collision_owner; + /* Skeleton */ struct Skeleton { @@ -1691,6 +1767,7 @@ public: void particles_set_process_material(RID p_particles, RID p_material); void particles_set_fixed_fps(RID p_particles, int p_fps); void particles_set_fractional_delta(RID p_particles, bool p_enable); + void particles_set_collision_base_size(RID p_particles, float p_size); void particles_restart(RID p_particles); void particles_emit(RID p_particles, const Transform &p_transform, const Vector3 &p_velocity, const Color &p_color, const Color &p_custom, uint32_t p_emit_flags); void particles_set_subemitter(RID p_particles, RID p_subemitter_particles); @@ -1748,6 +1825,27 @@ public: return particles->particles_transforms_buffer_uniform_set; } + virtual void particles_add_collision(RID p_particles, RasterizerScene::InstanceBase *p_instance); + virtual void particles_remove_collision(RID p_particles, RasterizerScene::InstanceBase *p_instance); + + /* PARTICLES COLLISION */ + + virtual RID particles_collision_create(); + virtual void particles_collision_set_collision_type(RID p_particles_collision, RS::ParticlesCollisionType p_type); + virtual void particles_collision_set_cull_mask(RID p_particles_collision, uint32_t p_cull_mask); + virtual void particles_collision_set_sphere_radius(RID p_particles_collision, float p_radius); //for spheres + virtual void particles_collision_set_box_extents(RID p_particles_collision, const Vector3 &p_extents); //for non-spheres + virtual void particles_collision_set_attractor_strength(RID p_particles_collision, float p_strength); + virtual void particles_collision_set_attractor_directionality(RID p_particles_collision, float p_directionality); + virtual void particles_collision_set_attractor_attenuation(RID p_particles_collision, float p_curve); + virtual void particles_collision_set_field_texture(RID p_particles_collision, RID p_texture); //for SDF and vector field, heightfield is dynamic + virtual void particles_collision_height_field_update(RID p_particles_collision); //for SDF and vector field + virtual void particles_collision_set_height_field_resolution(RID p_particles_collision, RS::ParticlesCollisionHeightfieldResolution p_resolution); //for SDF and vector field + virtual AABB particles_collision_get_aabb(RID p_particles_collision) const; + virtual Vector3 particles_collision_get_extents(RID p_particles_collision) const; + virtual bool particles_collision_is_heightfield(RID p_particles_collision) const; + RID particles_collision_get_heightfield_framebuffer(RID p_particles_collision) const; + /* GLOBAL VARIABLES API */ virtual void global_variable_add(const StringName &p_name, RS::GlobalVariableType p_type, const Variant &p_value); diff --git a/servers/rendering/rasterizer_rd/shaders/particles.glsl b/servers/rendering/rasterizer_rd/shaders/particles.glsl index a924509771..926c7ef9fc 100644 --- a/servers/rendering/rasterizer_rd/shaders/particles.glsl +++ b/servers/rendering/rasterizer_rd/shaders/particles.glsl @@ -31,6 +31,40 @@ global_variables; /* Set 1: FRAME AND PARTICLE DATA */ // a frame history is kept for trail deterministic behavior + +#define MAX_ATTRACTORS 32 + +#define ATTRACTOR_TYPE_SPHERE 0 +#define ATTRACTOR_TYPE_BOX 1 +#define ATTRACTOR_TYPE_VECTOR_FIELD 2 + +struct Attractor { + mat4 transform; + vec3 extents; //exents or radius + uint type; + uint texture_index; //texture index for vector field + float strength; + float attenuation; + float directionality; +}; + +#define MAX_COLLIDERS 32 + +#define COLLIDER_TYPE_SPHERE 0 +#define COLLIDER_TYPE_BOX 1 +#define COLLIDER_TYPE_SDF 2 +#define COLLIDER_TYPE_HEIGHT_FIELD 3 + +struct Collider { + mat4 transform; + vec3 extents; //exents or radius + uint type; + + uint texture_index; //texture index for vector field + float scale; + uint pad[2]; +}; + struct FrameParams { bool emitting; float system_phase; @@ -43,9 +77,14 @@ struct FrameParams { float delta; uint random_seed; - uint pad[3]; + uint attractor_count; + uint collider_count; + float particle_size; mat4 emission_transform; + + Attractor attractors[MAX_ATTRACTORS]; + Collider colliders[MAX_COLLIDERS]; }; layout(set = 1, binding = 0, std430) restrict buffer FrameHistory { @@ -80,7 +119,7 @@ struct ParticleEmission { vec4 custom; }; -layout(set = 1, binding = 2, std430) restrict volatile coherent buffer SourceEmission { +layout(set = 1, binding = 2, std430) restrict buffer SourceEmission { int particle_count; uint pad0; uint pad1; @@ -89,7 +128,7 @@ layout(set = 1, binding = 2, std430) restrict volatile coherent buffer SourceEmi } src_particles; -layout(set = 1, binding = 3, std430) restrict volatile coherent buffer DestEmission { +layout(set = 1, binding = 3, std430) restrict buffer DestEmission { int particle_count; int particle_max; uint pad1; @@ -98,10 +137,17 @@ layout(set = 1, binding = 3, std430) restrict volatile coherent buffer DestEmiss } dst_particles; -/* SET 2: MATERIAL */ +/* SET 2: COLLIDER/ATTRACTOR TEXTURES */ + +#define MAX_3D_TEXTURES 7 + +layout(set = 2, binding = 0) uniform texture3D sdf_vec_textures[MAX_3D_TEXTURES]; +layout(set = 2, binding = 1) uniform texture2D height_field_texture; + +/* SET 3: MATERIAL */ #ifdef USE_MATERIAL_UNIFORMS -layout(set = 2, binding = 0, std140) uniform MaterialUniforms{ +layout(set = 3, binding = 0, std140) uniform MaterialUniforms{ /* clang-format off */ MATERIAL_UNIFORMS /* clang-format on */ @@ -140,29 +186,7 @@ bool emit_particle(mat4 p_xform, vec3 p_velocity, vec4 p_color, vec4 p_custom, u atomicAdd(dst_particles.particle_count, -1); return false; } - /* - valid = true; - - int attempts = 256; // never trust compute - while(attempts-- > 0) { - dst_index = dst_particles.particle_count; - if (dst_index == dst_particles.particle_max) { - return false; //can't emit anymore - } - - if (atomicCompSwap(dst_particles.particle_count, dst_index, dst_index +1 ) != dst_index) { - continue; - } - valid=true; - break; - } - barrier(); - - if (!valid) { - return false; //gave up (attempts exhausted) - } -*/ dst_particles.data[dst_index].xform = p_xform; dst_particles.data[dst_index].velocity = p_velocity; dst_particles.data[dst_index].color = p_color; @@ -217,6 +241,199 @@ void main() { vec4(0.0, 0.0, 0.0, 1.0)); } + bool collided = false; + vec3 collision_normal = vec3(0.0); + float collision_depth = 0.0; + + vec3 attractor_force = vec3(0.0); + +#if !defined(DISABLE_VELOCITY) + + if (PARTICLE.is_active) { + PARTICLE.xform[3].xyz += PARTICLE.velocity * local_delta; + } +#endif + + /* Process physics if active */ + + if (PARTICLE.is_active) { + for (uint i = 0; i < FRAME.attractor_count; i++) { + vec3 dir; + float amount; + vec3 rel_vec = PARTICLE.xform[3].xyz - FRAME.attractors[i].transform[3].xyz; + vec3 local_pos = rel_vec * mat3(FRAME.attractors[i].transform); + + switch (FRAME.attractors[i].type) { + case ATTRACTOR_TYPE_SPHERE: { + dir = normalize(rel_vec); + float d = length(local_pos) / FRAME.attractors[i].extents.x; + if (d > 1.0) { + continue; + } + amount = max(0.0, 1.0 - d); + } break; + case ATTRACTOR_TYPE_BOX: { + dir = normalize(rel_vec); + + vec3 abs_pos = abs(local_pos / FRAME.attractors[i].extents); + float d = max(abs_pos.x, max(abs_pos.y, abs_pos.z)); + if (d > 1.0) { + continue; + } + amount = max(0.0, 1.0 - d); + + } break; + case ATTRACTOR_TYPE_VECTOR_FIELD: { + vec3 uvw_pos = (local_pos / FRAME.attractors[i].extents) * 2.0 - 1.0; + if (any(lessThan(uvw_pos, vec3(0.0))) || any(greaterThan(uvw_pos, vec3(1.0)))) { + continue; + } + vec3 s = texture(sampler3D(sdf_vec_textures[FRAME.attractors[i].texture_index], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw_pos).xyz; + dir = mat3(FRAME.attractors[i].transform) * normalize(s); //revert direction + amount = length(s); + + } break; + } + amount = pow(amount, FRAME.attractors[i].attenuation); + dir = normalize(mix(dir, FRAME.attractors[i].transform[2].xyz, FRAME.attractors[i].directionality)); + attractor_force -= amount * dir * FRAME.attractors[i].strength; + } + + float particle_size = FRAME.particle_size; + +#ifdef USE_COLLISON_SCALE + + particle_size *= dot(vec3(length(PARTICLE.xform[0].xyz), length(PARTICLE.xform[1].xyz), length(PARTICLE.xform[2].xyz)), vec3(0.33333333333)); + +#endif + + for (uint i = 0; i < FRAME.collider_count; i++) { + vec3 normal; + float depth; + bool col = false; + + vec3 rel_vec = PARTICLE.xform[3].xyz - FRAME.colliders[i].transform[3].xyz; + vec3 local_pos = rel_vec * mat3(FRAME.colliders[i].transform); + + switch (FRAME.colliders[i].type) { + case COLLIDER_TYPE_SPHERE: { + float d = length(rel_vec) - (particle_size + FRAME.colliders[i].extents.x); + + if (d < 0.0) { + col = true; + depth = -d; + normal = normalize(rel_vec); + } + + } break; + case COLLIDER_TYPE_BOX: { + vec3 abs_pos = abs(local_pos); + vec3 sgn_pos = sign(local_pos); + + if (any(greaterThan(abs_pos, FRAME.colliders[i].extents))) { + //point outside box + + vec3 closest = min(abs_pos, FRAME.colliders[i].extents); + vec3 rel = abs_pos - closest; + depth = length(rel) - particle_size; + if (depth < 0.0) { + col = true; + normal = mat3(FRAME.colliders[i].transform) * (normalize(rel) * sgn_pos); + depth = -depth; + } + } else { + //point inside box + vec3 axis_len = FRAME.colliders[i].extents - abs_pos; + // there has to be a faster way to do this? + if (all(lessThan(axis_len.xx, axis_len.yz))) { + normal = vec3(1, 0, 0); + } else if (all(lessThan(axis_len.yy, axis_len.xz))) { + normal = vec3(0, 1, 0); + } else { + normal = vec3(0, 0, 1); + } + + col = true; + depth = dot(normal * axis_len, vec3(1)) + particle_size; + normal = mat3(FRAME.colliders[i].transform) * (normal * sgn_pos); + } + + } break; + case COLLIDER_TYPE_SDF: { + vec3 apos = abs(local_pos); + float extra_dist = 0.0; + if (any(greaterThan(apos, FRAME.colliders[i].extents))) { //outside + vec3 mpos = min(apos, FRAME.colliders[i].extents); + extra_dist = distance(mpos, apos); + } + + if (extra_dist > particle_size) { + continue; + } + + vec3 uvw_pos = (local_pos / FRAME.colliders[i].extents) * 0.5 + 0.5; + float s = texture(sampler3D(sdf_vec_textures[FRAME.colliders[i].texture_index], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw_pos).r; + s *= FRAME.colliders[i].scale; + s += extra_dist; + if (s < particle_size) { + col = true; + depth = particle_size - s; + const float EPSILON = 0.001; + normal = mat3(FRAME.colliders[i].transform) * + normalize( + vec3( + texture(sampler3D(sdf_vec_textures[FRAME.colliders[i].texture_index], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw_pos + vec3(EPSILON, 0.0, 0.0)).r - texture(sampler3D(sdf_vec_textures[FRAME.colliders[i].texture_index], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw_pos - vec3(EPSILON, 0.0, 0.0)).r, + texture(sampler3D(sdf_vec_textures[FRAME.colliders[i].texture_index], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw_pos + vec3(0.0, EPSILON, 0.0)).r - texture(sampler3D(sdf_vec_textures[FRAME.colliders[i].texture_index], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw_pos - vec3(0.0, EPSILON, 0.0)).r, + texture(sampler3D(sdf_vec_textures[FRAME.colliders[i].texture_index], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw_pos + vec3(0.0, 0.0, EPSILON)).r - texture(sampler3D(sdf_vec_textures[FRAME.colliders[i].texture_index], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw_pos - vec3(0.0, 0.0, EPSILON)).r)); + } + + } break; + case COLLIDER_TYPE_HEIGHT_FIELD: { + vec3 local_pos_bottom = local_pos; + local_pos_bottom.y -= particle_size; + + if (any(greaterThan(abs(local_pos_bottom), FRAME.colliders[i].extents))) { + continue; + } + + const float DELTA = 1.0 / 8192.0; + + vec3 uvw_pos = vec3(local_pos_bottom / FRAME.colliders[i].extents) * 0.5 + 0.5; + + float y = 1.0 - texture(sampler2D(height_field_texture, material_samplers[SAMPLER_LINEAR_CLAMP]), uvw_pos.xz).r; + + if (y > uvw_pos.y) { + //inside heightfield + + vec3 pos1 = (vec3(uvw_pos.x, y, uvw_pos.z) * 2.0 - 1.0) * FRAME.colliders[i].extents; + vec3 pos2 = (vec3(uvw_pos.x + DELTA, 1.0 - texture(sampler2D(height_field_texture, material_samplers[SAMPLER_LINEAR_CLAMP]), uvw_pos.xz + vec2(DELTA, 0)).r, uvw_pos.z) * 2.0 - 1.0) * FRAME.colliders[i].extents; + vec3 pos3 = (vec3(uvw_pos.x, 1.0 - texture(sampler2D(height_field_texture, material_samplers[SAMPLER_LINEAR_CLAMP]), uvw_pos.xz + vec2(0, DELTA)).r, uvw_pos.z + DELTA) * 2.0 - 1.0) * FRAME.colliders[i].extents; + + normal = normalize(cross(pos1 - pos2, pos1 - pos3)); + float local_y = (vec3(local_pos / FRAME.colliders[i].extents) * 0.5 + 0.5).y; + + col = true; + depth = dot(normal, pos1) - dot(normal, local_pos_bottom); + } + + } break; + } + + if (col) { + if (!collided) { + collided = true; + collision_normal = normal; + collision_depth = depth; + } else { + vec3 c = collision_normal * collision_depth; + c += normal * max(0.0, depth - dot(normal, c)); + collision_normal = normalize(c); + collision_depth = length(c); + } + } + } + } + if (params.sub_emitter_mode) { if (!PARTICLE.is_active) { int src_index = atomicAdd(src_particles.particle_count, -1) - 1; @@ -329,66 +546,4 @@ COMPUTE_SHADER_CODE /* clang-format on */ } - -#if !defined(DISABLE_VELOCITY) - - if (PARTICLE.is_active) { - PARTICLE.xform[3].xyz += PARTICLE.velocity * local_delta; - } -#endif - -#if 0 - if (PARTICLE.is_active) { - //execute shader - - - - - //!defined(DISABLE_FORCE) - - if (false) { - vec3 force = vec3(0.0); - for (int i = 0; i < attractor_count; i++) { - vec3 rel_vec = xform[3].xyz - attractors[i].pos; - float dist = length(rel_vec); - if (attractors[i].radius < dist) - continue; - if (attractors[i].eat_radius > 0.0 && attractors[i].eat_radius > dist) { - out_velocity_active.a = 0.0; - } - - rel_vec = normalize(rel_vec); - - float attenuation = pow(dist / attractors[i].radius, attractors[i].attenuation); - - if (attractors[i].dir == vec3(0.0)) { - //towards center - force += attractors[i].strength * rel_vec * attenuation * mass; - } else { - force += attractors[i].strength * attractors[i].dir * attenuation * mass; - } - } - - out_velocity_active.xyz += force * local_delta; - } - -#if !defined(DISABLE_VELOCITY) - - if (true) { - xform[3].xyz += out_velocity_active.xyz * local_delta; - } -#endif - } else { - xform = mat4(0.0); - } - - - xform = transpose(xform); - - out_velocity_active.a = mix(0.0, 1.0, shader_active); - - out_xform_1 = xform[0]; - out_xform_2 = xform[1]; - out_xform_3 = xform[2]; -#endif } diff --git a/servers/rendering/rendering_server_raster.cpp b/servers/rendering/rendering_server_raster.cpp index b12e2ff3c1..cbc91497ba 100644 --- a/servers/rendering/rendering_server_raster.cpp +++ b/servers/rendering/rendering_server_raster.cpp @@ -108,6 +108,9 @@ void RenderingServerRaster::draw(bool p_swap_buffers, double frame_step) { RSG::scene->update_dirty_instances(); //update scene stuff + RSG::scene->render_particle_colliders(); + RSG::storage->update_particles(); //need to be done after instances are updated (colliders and particle transforms), and colliders are rendered + RSG::scene->render_probes(); RSG::viewport->draw_viewports(); RSG::canvas_render->update(); diff --git a/servers/rendering/rendering_server_raster.h b/servers/rendering/rendering_server_raster.h index b554425bef..afb3d6f46f 100644 --- a/servers/rendering/rendering_server_raster.h +++ b/servers/rendering/rendering_server_raster.h @@ -452,6 +452,7 @@ public: BIND1(particles_restart, RID) BIND6(particles_emit, RID, const Transform &, const Vector3 &, const Color &, const Color &, uint32_t) BIND2(particles_set_subemitter, RID, RID) + BIND2(particles_set_collision_base_size, RID, float) BIND2(particles_set_draw_order, RID, RS::ParticlesDrawOrder) @@ -461,6 +462,21 @@ public: BIND1R(AABB, particles_get_current_aabb, RID) BIND2(particles_set_emission_transform, RID, const Transform &) + /* PARTICLES COLLISION */ + + BIND0R(RID, particles_collision_create) + + BIND2(particles_collision_set_collision_type, RID, ParticlesCollisionType) + BIND2(particles_collision_set_cull_mask, RID, uint32_t) + BIND2(particles_collision_set_sphere_radius, RID, float) + BIND2(particles_collision_set_box_extents, RID, const Vector3 &) + BIND2(particles_collision_set_attractor_strength, RID, float) + BIND2(particles_collision_set_attractor_directionality, RID, float) + BIND2(particles_collision_set_attractor_attenuation, RID, float) + BIND2(particles_collision_set_field_texture, RID, RID) + BIND1(particles_collision_height_field_update, RID) + BIND2(particles_collision_set_height_field_resolution, RID, ParticlesCollisionHeightfieldResolution) + #undef BINDBASE //from now on, calls forwarded to this singleton #define BINDBASE RSG::scene diff --git a/servers/rendering/rendering_server_scene.cpp b/servers/rendering/rendering_server_scene.cpp index d8e52a5aae..ae6786090a 100644 --- a/servers/rendering/rendering_server_scene.cpp +++ b/servers/rendering/rendering_server_scene.cpp @@ -193,6 +193,8 @@ void *RenderingServerScene::_instance_pair(void *p_self, OctreeElementID, Instan } else if (B->base_type == RS::INSTANCE_GI_PROBE && A->base_type == RS::INSTANCE_LIGHT) { InstanceGIProbeData *gi_probe = static_cast<InstanceGIProbeData *>(B->base_data); return gi_probe->lights.insert(A); + } else if (B->base_type == RS::INSTANCE_PARTICLES_COLLISION && A->base_type == RS::INSTANCE_PARTICLES) { + RSG::storage->particles_add_collision(A->base, B); } return nullptr; @@ -274,6 +276,8 @@ void RenderingServerScene::_instance_unpair(void *p_self, OctreeElementID, Insta Set<Instance *>::Element *E = reinterpret_cast<Set<Instance *>::Element *>(udata); gi_probe->lights.erase(E); + } else if (B->base_type == RS::INSTANCE_PARTICLES_COLLISION && A->base_type == RS::INSTANCE_PARTICLES) { + RSG::storage->particles_remove_collision(A->base, B); } } @@ -539,6 +543,9 @@ void RenderingServerScene::instance_set_scenario(RID p_instance, RID p_scenario) RSG::scene_render->reflection_probe_release_atlas_index(reflection_probe->instance); } break; + case RS::INSTANCE_PARTICLES_COLLISION: { + heightfield_particle_colliders_update_list.erase(instance); + } break; case RS::INSTANCE_GI_PROBE: { InstanceGIProbeData *gi_probe = static_cast<InstanceGIProbeData *>(instance->base_data); @@ -702,6 +709,12 @@ void RenderingServerScene::instance_set_visible(RID p_instance, bool p_visible) } } break; + case RS::INSTANCE_PARTICLES_COLLISION: { + if (instance->octree_id && instance->scenario) { + instance->scenario->octree.set_pairable(instance->octree_id, p_visible, 1 << RS::INSTANCE_PARTICLES_COLLISION, p_visible ? (1 << RS::INSTANCE_PARTICLES) : 0); + } + + } break; default: { } } @@ -1026,6 +1039,13 @@ void RenderingServerScene::_update_instance(Instance *p_instance) { RSG::storage->particles_set_emission_transform(p_instance->base, p_instance->transform); } + if (p_instance->base_type == RS::INSTANCE_PARTICLES_COLLISION) { + //remove materials no longer used and un-own them + if (RSG::storage->particles_collision_is_heightfield(p_instance->base)) { + heightfield_particle_colliders_update_list.insert(p_instance); + } + } + if (p_instance->aabb.has_no_surface()) { return; } @@ -1085,6 +1105,11 @@ void RenderingServerScene::_update_instance(Instance *p_instance) { pairable = true; } + if (p_instance->base_type == RS::INSTANCE_PARTICLES_COLLISION) { + pairable_mask = p_instance->visible ? (1 << RS::INSTANCE_PARTICLES) : 0; + pairable = true; + } + if (p_instance->base_type == RS::INSTANCE_GI_PROBE) { //lights and geometries pairable_mask = p_instance->visible ? RS::INSTANCE_GEOMETRY_MASK | (1 << RS::INSTANCE_LIGHT) : 0; @@ -1146,6 +1171,10 @@ void RenderingServerScene::_update_instance_aabb(Instance *p_instance) { } } break; + case RenderingServer::INSTANCE_PARTICLES_COLLISION: { + new_aabb = RSG::storage->particles_collision_get_aabb(p_instance->base); + + } break; case RenderingServer::INSTANCE_LIGHT: { new_aabb = RSG::storage->light_get_aabb(p_instance->base); @@ -2679,6 +2708,27 @@ void RenderingServerScene::render_probes() { } } +void RenderingServerScene::render_particle_colliders() { + while (heightfield_particle_colliders_update_list.front()) { + Instance *hfpc = heightfield_particle_colliders_update_list.front()->get(); + + if (hfpc->scenario && hfpc->base_type == RS::INSTANCE_PARTICLES_COLLISION && RSG::storage->particles_collision_is_heightfield(hfpc->base)) { + //update heightfield + int cull_count = hfpc->scenario->octree.cull_aabb(hfpc->transformed_aabb, instance_cull_result, MAX_INSTANCE_CULL); //@TODO: cull mask missing + for (int i = 0; i < cull_count; i++) { + Instance *instance = instance_cull_result[i]; + if (!instance->visible || !((1 << instance->base_type) & (RS::INSTANCE_GEOMETRY_MASK & (~(1 << RS::INSTANCE_PARTICLES))))) { //all but particles to avoid self collision + cull_count--; + SWAP(instance_cull_result[i], instance_cull_result[cull_count]); + } + } + + RSG::scene_render->render_particle_collider_heightfield(hfpc->base, hfpc->transform, (RasterizerScene::InstanceBase **)instance_cull_result, cull_count); + } + heightfield_particle_colliders_update_list.erase(heightfield_particle_colliders_update_list.front()); + } +} + void RenderingServerScene::_update_instance_shader_parameters_from_material(Map<StringName, RasterizerScene::InstanceBase::InstanceShaderParameter> &isparams, const Map<StringName, RasterizerScene::InstanceBase::InstanceShaderParameter> &existing_isparams, RID p_material) { List<RasterizerStorage::InstanceShaderParam> plist; RSG::storage->material_get_instance_shader_parameters(p_material, &plist); diff --git a/servers/rendering/rendering_server_scene.h b/servers/rendering/rendering_server_scene.h index 165c3784c1..1b0a617627 100644 --- a/servers/rendering/rendering_server_scene.h +++ b/servers/rendering/rendering_server_scene.h @@ -385,6 +385,8 @@ public: } }; + Set<Instance *> heightfield_particle_colliders_update_list; + int instance_cull_count; Instance *instance_cull_result[MAX_INSTANCE_CULL]; Instance *instance_shadow_cull_result[MAX_INSTANCE_CULL]; //used for generating shadowmaps @@ -461,6 +463,7 @@ public: void render_camera(RID p_render_buffers, Ref<XRInterface> &p_interface, XRInterface::Eyes p_eye, RID p_camera, RID p_scenario, Size2 p_viewport_size, RID p_shadow_atlas); void update_dirty_instances(); + void render_particle_colliders(); void render_probes(); TypedArray<Image> bake_render_uv2(RID p_base, const Vector<RID> &p_material_overrides, const Size2i &p_image_size); diff --git a/servers/rendering/rendering_server_wrap_mt.cpp b/servers/rendering/rendering_server_wrap_mt.cpp index 9aa6593cbe..ab9856e06e 100644 --- a/servers/rendering/rendering_server_wrap_mt.cpp +++ b/servers/rendering/rendering_server_wrap_mt.cpp @@ -124,6 +124,7 @@ void RenderingServerWrapMT::finish() { gi_probe_free_cached_ids(); lightmap_free_cached_ids(); particles_free_cached_ids(); + particles_collision_free_cached_ids(); camera_free_cached_ids(); viewport_free_cached_ids(); environment_free_cached_ids(); diff --git a/servers/rendering/rendering_server_wrap_mt.h b/servers/rendering/rendering_server_wrap_mt.h index 372a7269dc..910acd74cb 100644 --- a/servers/rendering/rendering_server_wrap_mt.h +++ b/servers/rendering/rendering_server_wrap_mt.h @@ -356,6 +356,8 @@ public: FUNC2(particles_set_process_material, RID, RID) FUNC2(particles_set_fixed_fps, RID, int) FUNC2(particles_set_fractional_delta, RID, bool) + FUNC2(particles_set_collision_base_size, RID, float) + FUNC1R(bool, particles_is_inactive, RID) FUNC1(particles_request_process, RID) FUNC1(particles_restart, RID) @@ -371,6 +373,21 @@ public: FUNC1R(AABB, particles_get_current_aabb, RID) + /* PARTICLES COLLISION */ + + FUNCRID(particles_collision) + + FUNC2(particles_collision_set_collision_type, RID, ParticlesCollisionType) + FUNC2(particles_collision_set_cull_mask, RID, uint32_t) + FUNC2(particles_collision_set_sphere_radius, RID, float) + FUNC2(particles_collision_set_box_extents, RID, const Vector3 &) + FUNC2(particles_collision_set_attractor_strength, RID, float) + FUNC2(particles_collision_set_attractor_directionality, RID, float) + FUNC2(particles_collision_set_attractor_attenuation, RID, float) + FUNC2(particles_collision_set_field_texture, RID, RID) + FUNC1(particles_collision_height_field_update, RID) + FUNC2(particles_collision_set_height_field_resolution, RID, ParticlesCollisionHeightfieldResolution) + /* CAMERA API */ FUNCRID(camera) diff --git a/servers/rendering/shader_types.cpp b/servers/rendering/shader_types.cpp index 6d473a75f2..f1209d9d6d 100644 --- a/servers/rendering/shader_types.cpp +++ b/servers/rendering/shader_types.cpp @@ -295,6 +295,10 @@ ShaderTypes::ShaderTypes() { shader_modes[RS::SHADER_PARTICLES].functions["compute"].built_ins["RESTART_VELOCITY"] = constt(ShaderLanguage::TYPE_BOOL); shader_modes[RS::SHADER_PARTICLES].functions["compute"].built_ins["RESTART_COLOR"] = constt(ShaderLanguage::TYPE_BOOL); shader_modes[RS::SHADER_PARTICLES].functions["compute"].built_ins["RESTART_CUSTOM"] = constt(ShaderLanguage::TYPE_BOOL); + shader_modes[RS::SHADER_PARTICLES].functions["compute"].built_ins["COLLIDED"] = constt(ShaderLanguage::TYPE_BOOL); + shader_modes[RS::SHADER_PARTICLES].functions["compute"].built_ins["COLLISION_NORMAL"] = constt(ShaderLanguage::TYPE_VEC3); + shader_modes[RS::SHADER_PARTICLES].functions["compute"].built_ins["COLLISION_DEPTH"] = constt(ShaderLanguage::TYPE_FLOAT); + shader_modes[RS::SHADER_PARTICLES].functions["compute"].built_ins["ATTRACTOR_FORCE"] = constt(ShaderLanguage::TYPE_VEC3); shader_modes[RS::SHADER_PARTICLES].functions["compute"].can_discard = false; { @@ -308,6 +312,7 @@ ShaderTypes::ShaderTypes() { shader_modes[RS::SHADER_PARTICLES].functions["compute"].stage_functions["emit_particle"] = emit_vertex_func; } + shader_modes[RS::SHADER_PARTICLES].modes.push_back("collision_use_scale"); shader_modes[RS::SHADER_PARTICLES].modes.push_back("disable_force"); shader_modes[RS::SHADER_PARTICLES].modes.push_back("disable_velocity"); shader_modes[RS::SHADER_PARTICLES].modes.push_back("keep_data"); diff --git a/servers/rendering_server.h b/servers/rendering_server.h index 7dae45f126..7680dc1390 100644 --- a/servers/rendering_server.h +++ b/servers/rendering_server.h @@ -578,6 +578,7 @@ public: virtual void particles_set_process_material(RID p_particles, RID p_material) = 0; virtual void particles_set_fixed_fps(RID p_particles, int p_fps) = 0; virtual void particles_set_fractional_delta(RID p_particles, bool p_enable) = 0; + virtual void particles_set_collision_base_size(RID p_particles, float p_size) = 0; virtual bool particles_is_inactive(RID p_particles) = 0; virtual void particles_request_process(RID p_particles) = 0; virtual void particles_restart(RID p_particles) = 0; @@ -609,6 +610,43 @@ public: virtual void particles_set_emission_transform(RID p_particles, const Transform &p_transform) = 0; //this is only used for 2D, in 3D it's automatic + /* PARTICLES COLLISION API */ + + virtual RID particles_collision_create() = 0; + + enum ParticlesCollisionType { + PARTICLES_COLLISION_TYPE_SPHERE_ATTRACT, + PARTICLES_COLLISION_TYPE_BOX_ATTRACT, + PARTICLES_COLLISION_TYPE_VECTOR_FIELD_ATTRACT, + PARTICLES_COLLISION_TYPE_SPHERE_COLLIDE, + PARTICLES_COLLISION_TYPE_BOX_COLLIDE, + PARTICLES_COLLISION_TYPE_SDF_COLLIDE, + PARTICLES_COLLISION_TYPE_HEIGHTFIELD_COLLIDE, + }; + + virtual void particles_collision_set_collision_type(RID p_particles_collision, ParticlesCollisionType p_type) = 0; + virtual void particles_collision_set_cull_mask(RID p_particles_collision, uint32_t p_cull_mask) = 0; + virtual void particles_collision_set_sphere_radius(RID p_particles_collision, float p_radius) = 0; //for spheres + virtual void particles_collision_set_box_extents(RID p_particles_collision, const Vector3 &p_extents) = 0; //for non-spheres + virtual void particles_collision_set_attractor_strength(RID p_particles_collision, float p_strength) = 0; + virtual void particles_collision_set_attractor_directionality(RID p_particles_collision, float p_directionality) = 0; + virtual void particles_collision_set_attractor_attenuation(RID p_particles_collision, float p_curve) = 0; + virtual void particles_collision_set_field_texture(RID p_particles_collision, RID p_texture) = 0; //for SDF and vector field, heightfield is dynamic + + virtual void particles_collision_height_field_update(RID p_particles_collision) = 0; //for SDF and vector field + + enum ParticlesCollisionHeightfieldResolution { //longest axis resolution + PARTICLES_COLLISION_HEIGHTFIELD_RESOLUTION_256, + PARTICLES_COLLISION_HEIGHTFIELD_RESOLUTION_512, + PARTICLES_COLLISION_HEIGHTFIELD_RESOLUTION_1024, + PARTICLES_COLLISION_HEIGHTFIELD_RESOLUTION_2048, + PARTICLES_COLLISION_HEIGHTFIELD_RESOLUTION_4096, + PARTICLES_COLLISION_HEIGHTFIELD_RESOLUTION_8192, + PARTICLES_COLLISION_HEIGHTFIELD_RESOLUTION_MAX, + }; + + virtual void particles_collision_set_height_field_resolution(RID p_particles_collision, ParticlesCollisionHeightfieldResolution p_resolution) = 0; //for SDF and vector field + /* CAMERA API */ virtual RID camera_create() = 0; @@ -965,6 +1003,7 @@ public: INSTANCE_MULTIMESH, INSTANCE_IMMEDIATE, INSTANCE_PARTICLES, + INSTANCE_PARTICLES_COLLISION, INSTANCE_LIGHT, INSTANCE_REFLECTION_PROBE, INSTANCE_DECAL, |