Skip to content

Commit

Permalink
Merge pull request #42628 from reduz/particle-collisions
Browse files Browse the repository at this point in the history
Implement GPU Particle Collisions
  • Loading branch information
akien-mga authored Oct 9, 2020
2 parents c35005b + 26f5bd2 commit b724d38
Show file tree
Hide file tree
Showing 32 changed files with 2,947 additions and 102 deletions.
8 changes: 8 additions & 0 deletions core/math/vector2.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
27 changes: 25 additions & 2 deletions core/thread_work_pool.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -90,16 +92,37 @@ class ThreadWorkPool {
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);
Expand Down
2 changes: 2 additions & 0 deletions editor/editor_node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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));
Expand Down
2 changes: 1 addition & 1 deletion editor/import/resource_importer_layered_texture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
261 changes: 261 additions & 0 deletions editor/node_3d_editor_gizmos.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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));

Expand Down
17 changes: 17 additions & 0 deletions editor/node_3d_editor_gizmos.h
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,23 @@ class GPUParticles3DGizmoPlugin : public EditorNode3DGizmoPlugin {
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);

Expand Down
Loading

0 comments on commit b724d38

Please sign in to comment.