1/**************************************************************************/
2/* gpu_particles_collision_3d.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#include "gpu_particles_collision_3d.h"
32
33#include "core/object/worker_thread_pool.h"
34#include "mesh_instance_3d.h"
35#include "scene/3d/camera_3d.h"
36#include "scene/main/viewport.h"
37
38void GPUParticlesCollision3D::set_cull_mask(uint32_t p_cull_mask) {
39 cull_mask = p_cull_mask;
40 RS::get_singleton()->particles_collision_set_cull_mask(collision, p_cull_mask);
41}
42
43uint32_t GPUParticlesCollision3D::get_cull_mask() const {
44 return cull_mask;
45}
46
47void GPUParticlesCollision3D::_bind_methods() {
48 ClassDB::bind_method(D_METHOD("set_cull_mask", "mask"), &GPUParticlesCollision3D::set_cull_mask);
49 ClassDB::bind_method(D_METHOD("get_cull_mask"), &GPUParticlesCollision3D::get_cull_mask);
50
51 ADD_PROPERTY(PropertyInfo(Variant::INT, "cull_mask", PROPERTY_HINT_LAYERS_3D_RENDER), "set_cull_mask", "get_cull_mask");
52}
53
54GPUParticlesCollision3D::GPUParticlesCollision3D(RS::ParticlesCollisionType p_type) {
55 collision = RS::get_singleton()->particles_collision_create();
56 RS::get_singleton()->particles_collision_set_collision_type(collision, p_type);
57 set_base(collision);
58}
59
60GPUParticlesCollision3D::~GPUParticlesCollision3D() {
61 ERR_FAIL_NULL(RenderingServer::get_singleton());
62 RS::get_singleton()->free(collision);
63}
64
65/////////////////////////////////
66
67void GPUParticlesCollisionSphere3D::_bind_methods() {
68 ClassDB::bind_method(D_METHOD("set_radius", "radius"), &GPUParticlesCollisionSphere3D::set_radius);
69 ClassDB::bind_method(D_METHOD("get_radius"), &GPUParticlesCollisionSphere3D::get_radius);
70
71 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater,suffix:m"), "set_radius", "get_radius");
72}
73
74void GPUParticlesCollisionSphere3D::set_radius(real_t p_radius) {
75 radius = p_radius;
76 RS::get_singleton()->particles_collision_set_sphere_radius(_get_collision(), radius);
77 update_gizmos();
78}
79
80real_t GPUParticlesCollisionSphere3D::get_radius() const {
81 return radius;
82}
83
84AABB GPUParticlesCollisionSphere3D::get_aabb() const {
85 return AABB(Vector3(-radius, -radius, -radius), Vector3(radius * 2, radius * 2, radius * 2));
86}
87
88GPUParticlesCollisionSphere3D::GPUParticlesCollisionSphere3D() :
89 GPUParticlesCollision3D(RS::PARTICLES_COLLISION_TYPE_SPHERE_COLLIDE) {
90}
91
92GPUParticlesCollisionSphere3D::~GPUParticlesCollisionSphere3D() {
93}
94
95///////////////////////////
96
97void GPUParticlesCollisionBox3D::_bind_methods() {
98 ClassDB::bind_method(D_METHOD("set_size", "size"), &GPUParticlesCollisionBox3D::set_size);
99 ClassDB::bind_method(D_METHOD("get_size"), &GPUParticlesCollisionBox3D::get_size);
100
101 ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "size", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater,suffix:m"), "set_size", "get_size");
102}
103
104#ifndef DISABLE_DEPRECATED
105bool GPUParticlesCollisionBox3D::_set(const StringName &p_name, const Variant &p_value) {
106 if (p_name == "extents") { // Compatibility with Godot 3.x.
107 set_size((Vector3)p_value * 2);
108 return true;
109 }
110 return false;
111}
112
113bool GPUParticlesCollisionBox3D::_get(const StringName &p_name, Variant &r_property) const {
114 if (p_name == "extents") { // Compatibility with Godot 3.x.
115 r_property = size / 2;
116 return true;
117 }
118 return false;
119}
120#endif // DISABLE_DEPRECATED
121
122void GPUParticlesCollisionBox3D::set_size(const Vector3 &p_size) {
123 size = p_size;
124 RS::get_singleton()->particles_collision_set_box_extents(_get_collision(), size / 2);
125 update_gizmos();
126}
127
128Vector3 GPUParticlesCollisionBox3D::get_size() const {
129 return size;
130}
131
132AABB GPUParticlesCollisionBox3D::get_aabb() const {
133 return AABB(-size / 2, size);
134}
135
136GPUParticlesCollisionBox3D::GPUParticlesCollisionBox3D() :
137 GPUParticlesCollision3D(RS::PARTICLES_COLLISION_TYPE_BOX_COLLIDE) {
138}
139
140GPUParticlesCollisionBox3D::~GPUParticlesCollisionBox3D() {
141}
142
143///////////////////////////////
144///////////////////////////
145
146void GPUParticlesCollisionSDF3D::_find_meshes(const AABB &p_aabb, Node *p_at_node, List<PlotMesh> &plot_meshes) {
147 MeshInstance3D *mi = Object::cast_to<MeshInstance3D>(p_at_node);
148 if (mi && mi->is_visible_in_tree()) {
149 if ((mi->get_layer_mask() & bake_mask) == 0) {
150 return;
151 }
152
153 Ref<Mesh> mesh = mi->get_mesh();
154 if (mesh.is_valid()) {
155 AABB aabb = mesh->get_aabb();
156
157 Transform3D xf = get_global_transform().affine_inverse() * mi->get_global_transform();
158
159 if (p_aabb.intersects(xf.xform(aabb))) {
160 PlotMesh pm;
161 pm.local_xform = xf;
162 pm.mesh = mesh;
163 plot_meshes.push_back(pm);
164 }
165 }
166 }
167
168 Node3D *s = Object::cast_to<Node3D>(p_at_node);
169 if (s) {
170 if (s->is_visible_in_tree()) {
171 Array meshes = p_at_node->call("get_meshes");
172 for (int i = 0; i < meshes.size(); i += 2) {
173 Transform3D mxf = meshes[i];
174 Ref<Mesh> mesh = meshes[i + 1];
175 if (!mesh.is_valid()) {
176 continue;
177 }
178
179 AABB aabb = mesh->get_aabb();
180
181 Transform3D xf = get_global_transform().affine_inverse() * (s->get_global_transform() * mxf);
182
183 if (p_aabb.intersects(xf.xform(aabb))) {
184 PlotMesh pm;
185 pm.local_xform = xf;
186 pm.mesh = mesh;
187 plot_meshes.push_back(pm);
188 }
189 }
190 }
191 }
192
193 for (int i = 0; i < p_at_node->get_child_count(); i++) {
194 Node *child = p_at_node->get_child(i);
195 _find_meshes(p_aabb, child, plot_meshes);
196 }
197}
198
199uint32_t GPUParticlesCollisionSDF3D::_create_bvh(LocalVector<BVH> &bvh_tree, FacePos *p_faces, uint32_t p_face_count, const Face3 *p_triangles, float p_thickness) {
200 if (p_face_count == 1) {
201 return BVH::LEAF_BIT | p_faces[0].index;
202 }
203
204 uint32_t index = bvh_tree.size();
205 {
206 BVH bvh;
207
208 for (uint32_t i = 0; i < p_face_count; i++) {
209 const Face3 &f = p_triangles[p_faces[i].index];
210 AABB aabb(f.vertex[0], Vector3());
211 aabb.expand_to(f.vertex[1]);
212 aabb.expand_to(f.vertex[2]);
213 if (p_thickness > 0.0) {
214 Vector3 normal = p_triangles[p_faces[i].index].get_plane().normal;
215 aabb.expand_to(f.vertex[0] - normal * p_thickness);
216 aabb.expand_to(f.vertex[1] - normal * p_thickness);
217 aabb.expand_to(f.vertex[2] - normal * p_thickness);
218 }
219 if (i == 0) {
220 bvh.bounds = aabb;
221 } else {
222 bvh.bounds.merge_with(aabb);
223 }
224 }
225 bvh_tree.push_back(bvh);
226 }
227
228 uint32_t middle = p_face_count / 2;
229
230 SortArray<FacePos, FaceSort> s;
231 s.compare.axis = bvh_tree[index].bounds.get_longest_axis_index();
232 s.sort(p_faces, p_face_count);
233
234 uint32_t left = _create_bvh(bvh_tree, p_faces, middle, p_triangles, p_thickness);
235 uint32_t right = _create_bvh(bvh_tree, p_faces + middle, p_face_count - middle, p_triangles, p_thickness);
236
237 bvh_tree[index].children[0] = left;
238 bvh_tree[index].children[1] = right;
239
240 return index;
241}
242
243static _FORCE_INLINE_ real_t Vector3_dot2(const Vector3 &p_vec3) {
244 return p_vec3.dot(p_vec3);
245}
246
247void GPUParticlesCollisionSDF3D::_find_closest_distance(const Vector3 &p_pos, const BVH *p_bvh, uint32_t p_bvh_cell, const Face3 *p_triangles, float p_thickness, float &r_closest_distance) {
248 if (p_bvh_cell & BVH::LEAF_BIT) {
249 p_bvh_cell &= BVH::LEAF_MASK; //remove bit
250
251 Vector3 point = p_pos;
252 Plane p = p_triangles[p_bvh_cell].get_plane();
253 float d = p.distance_to(point);
254 float inside_d = 1e20;
255 if (d < 0 && d > -p_thickness) {
256 //inside planes, do this in 2D
257
258 Vector3 x_axis = (p_triangles[p_bvh_cell].vertex[0] - p_triangles[p_bvh_cell].vertex[1]).normalized();
259 Vector3 y_axis = p.normal.cross(x_axis).normalized();
260
261 Vector2 points[3];
262 for (int i = 0; i < 3; i++) {
263 points[i] = Vector2(x_axis.dot(p_triangles[p_bvh_cell].vertex[i]), y_axis.dot(p_triangles[p_bvh_cell].vertex[i]));
264 }
265
266 Vector2 p2d = Vector2(x_axis.dot(point), y_axis.dot(point));
267
268 {
269 // https://www.shadertoy.com/view/XsXSz4
270
271 Vector2 e0 = points[1] - points[0];
272 Vector2 e1 = points[2] - points[1];
273 Vector2 e2 = points[0] - points[2];
274
275 Vector2 v0 = p2d - points[0];
276 Vector2 v1 = p2d - points[1];
277 Vector2 v2 = p2d - points[2];
278
279 Vector2 pq0 = v0 - e0 * CLAMP(v0.dot(e0) / e0.dot(e0), 0.0, 1.0);
280 Vector2 pq1 = v1 - e1 * CLAMP(v1.dot(e1) / e1.dot(e1), 0.0, 1.0);
281 Vector2 pq2 = v2 - e2 * CLAMP(v2.dot(e2) / e2.dot(e2), 0.0, 1.0);
282
283 float s = SIGN(e0.x * e2.y - e0.y * e2.x);
284 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)));
285
286 inside_d = -Math::sqrt(d2.x) * SIGN(d2.y);
287 }
288
289 //make sure distance to planes is not shorter if inside
290 if (inside_d < 0) {
291 inside_d = MAX(inside_d, d);
292 inside_d = MAX(inside_d, -(p_thickness + d));
293 }
294
295 r_closest_distance = MIN(r_closest_distance, inside_d);
296 } else {
297 if (d < 0) {
298 point -= p.normal * p_thickness; //flatten
299 }
300
301 // https://iquilezles.org/www/articles/distfunctions/distfunctions.htm
302 Vector3 a = p_triangles[p_bvh_cell].vertex[0];
303 Vector3 b = p_triangles[p_bvh_cell].vertex[1];
304 Vector3 c = p_triangles[p_bvh_cell].vertex[2];
305
306 Vector3 ba = b - a;
307 Vector3 pa = point - a;
308 Vector3 cb = c - b;
309 Vector3 pb = point - b;
310 Vector3 ac = a - c;
311 Vector3 pc = point - c;
312 Vector3 nor = ba.cross(ac);
313
314 inside_d = Math::sqrt(
315 (SIGN(ba.cross(nor).dot(pa)) + SIGN(cb.cross(nor).dot(pb)) + SIGN(ac.cross(nor).dot(pc)) < 2.0)
316 ? MIN(MIN(
317 Vector3_dot2(ba * CLAMP(ba.dot(pa) / Vector3_dot2(ba), 0.0, 1.0) - pa),
318 Vector3_dot2(cb * CLAMP(cb.dot(pb) / Vector3_dot2(cb), 0.0, 1.0) - pb)),
319 Vector3_dot2(ac * CLAMP(ac.dot(pc) / Vector3_dot2(ac), 0.0, 1.0) - pc))
320 : nor.dot(pa) * nor.dot(pa) / Vector3_dot2(nor));
321
322 r_closest_distance = MIN(r_closest_distance, inside_d);
323 }
324
325 } else {
326 bool pass = true;
327 if (!p_bvh[p_bvh_cell].bounds.has_point(p_pos)) {
328 //outside, find closest point
329 Vector3 he = p_bvh[p_bvh_cell].bounds.size * 0.5;
330 Vector3 center = p_bvh[p_bvh_cell].bounds.position + he;
331
332 Vector3 rel = (p_pos - center).abs();
333 Vector3 closest(MIN(rel.x, he.x), MIN(rel.y, he.y), MIN(rel.z, he.z));
334 float d = rel.distance_to(closest);
335
336 if (d >= r_closest_distance) {
337 pass = false; //already closer than this aabb, discard
338 }
339 }
340
341 if (pass) {
342 _find_closest_distance(p_pos, p_bvh, p_bvh[p_bvh_cell].children[0], p_triangles, p_thickness, r_closest_distance);
343 _find_closest_distance(p_pos, p_bvh, p_bvh[p_bvh_cell].children[1], p_triangles, p_thickness, r_closest_distance);
344 }
345 }
346}
347
348void GPUParticlesCollisionSDF3D::_compute_sdf_z(uint32_t p_z, ComputeSDFParams *params) {
349 int32_t z_ofs = p_z * params->size.y * params->size.x;
350 for (int32_t y = 0; y < params->size.y; y++) {
351 int32_t y_ofs = z_ofs + y * params->size.x;
352 for (int32_t x = 0; x < params->size.x; x++) {
353 int32_t x_ofs = y_ofs + x;
354 float &cell = params->cells[x_ofs];
355
356 Vector3 pos = params->cell_offset + Vector3(x, y, p_z) * params->cell_size;
357
358 cell = 1e20;
359
360 _find_closest_distance(pos, params->bvh, 0, params->triangles, params->thickness, cell);
361 }
362 }
363}
364
365void GPUParticlesCollisionSDF3D::_compute_sdf(ComputeSDFParams *params) {
366 WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_template_group_task(this, &GPUParticlesCollisionSDF3D::_compute_sdf_z, params, params->size.z);
367 while (!WorkerThreadPool::get_singleton()->is_group_task_completed(group_task)) {
368 OS::get_singleton()->delay_usec(10000);
369 if (bake_step_function) {
370 bake_step_function(WorkerThreadPool::get_singleton()->get_group_processed_element_count(group_task) * 100 / params->size.z, "Baking SDF");
371 }
372 }
373 WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task);
374}
375
376Vector3i GPUParticlesCollisionSDF3D::get_estimated_cell_size() const {
377 static const int subdivs[RESOLUTION_MAX] = { 16, 32, 64, 128, 256, 512 };
378 int subdiv = subdivs[get_resolution()];
379
380 AABB aabb(-size / 2, size);
381
382 float cell_size = aabb.get_longest_axis_size() / float(subdiv);
383
384 Vector3i sdf_size = Vector3i(aabb.size / cell_size);
385 sdf_size.x = MAX(1, sdf_size.x);
386 sdf_size.y = MAX(1, sdf_size.y);
387 sdf_size.z = MAX(1, sdf_size.z);
388 return sdf_size;
389}
390
391Ref<Image> GPUParticlesCollisionSDF3D::bake() {
392 static const int subdivs[RESOLUTION_MAX] = { 16, 32, 64, 128, 256, 512 };
393 int subdiv = subdivs[get_resolution()];
394
395 AABB aabb(-size / 2, size);
396
397 float cell_size = aabb.get_longest_axis_size() / float(subdiv);
398
399 Vector3i sdf_size = Vector3i(aabb.size / cell_size);
400 sdf_size.x = MAX(1, sdf_size.x);
401 sdf_size.y = MAX(1, sdf_size.y);
402 sdf_size.z = MAX(1, sdf_size.z);
403
404 if (bake_begin_function) {
405 bake_begin_function(100);
406 }
407
408 aabb.size = Vector3(sdf_size) * cell_size;
409
410 List<PlotMesh> plot_meshes;
411 _find_meshes(aabb, get_parent(), plot_meshes);
412
413 LocalVector<Face3> faces;
414
415 if (bake_step_function) {
416 bake_step_function(0, "Finding Meshes");
417 }
418
419 for (const PlotMesh &pm : plot_meshes) {
420 for (int i = 0; i < pm.mesh->get_surface_count(); i++) {
421 if (pm.mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES) {
422 continue; //only triangles
423 }
424
425 Array a = pm.mesh->surface_get_arrays(i);
426
427 Vector<Vector3> vertices = a[Mesh::ARRAY_VERTEX];
428 const Vector3 *vr = vertices.ptr();
429 Vector<int> index = a[Mesh::ARRAY_INDEX];
430
431 if (index.size()) {
432 int facecount = index.size() / 3;
433 const int *ir = index.ptr();
434
435 for (int j = 0; j < facecount; j++) {
436 Face3 face;
437
438 for (int k = 0; k < 3; k++) {
439 face.vertex[k] = pm.local_xform.xform(vr[ir[j * 3 + k]]);
440 }
441
442 //test against original bounds
443 if (!Geometry3D::triangle_box_overlap(aabb.get_center(), aabb.size * 0.5, face.vertex)) {
444 continue;
445 }
446
447 faces.push_back(face);
448 }
449
450 } else {
451 int facecount = vertices.size() / 3;
452
453 for (int j = 0; j < facecount; j++) {
454 Face3 face;
455
456 for (int k = 0; k < 3; k++) {
457 face.vertex[k] = pm.local_xform.xform(vr[j * 3 + k]);
458 }
459
460 //test against original bounds
461 if (!Geometry3D::triangle_box_overlap(aabb.get_center(), aabb.size * 0.5, face.vertex)) {
462 continue;
463 }
464
465 faces.push_back(face);
466 }
467 }
468 }
469 }
470
471 //compute bvh
472 if (faces.size() <= 1) {
473 ERR_PRINT("No faces detected during GPUParticlesCollisionSDF3D bake. Check whether there are visible meshes matching the bake mask within its extents.");
474 if (bake_end_function) {
475 bake_end_function();
476 }
477 return Ref<Image>();
478 }
479
480 LocalVector<FacePos> face_pos;
481
482 face_pos.resize(faces.size());
483
484 float th = cell_size * thickness;
485
486 for (uint32_t i = 0; i < faces.size(); i++) {
487 face_pos[i].index = i;
488 face_pos[i].center = (faces[i].vertex[0] + faces[i].vertex[1] + faces[i].vertex[2]) / 2;
489 if (th > 0.0) {
490 face_pos[i].center -= faces[i].get_plane().normal * th * 0.5;
491 }
492 }
493
494 if (bake_step_function) {
495 bake_step_function(0, "Creating BVH");
496 }
497
498 LocalVector<BVH> bvh;
499
500 _create_bvh(bvh, face_pos.ptr(), face_pos.size(), faces.ptr(), th);
501
502 Vector<uint8_t> cells_data;
503 cells_data.resize(sdf_size.z * sdf_size.y * sdf_size.x * (int)sizeof(float));
504
505 if (bake_step_function) {
506 bake_step_function(0, "Baking SDF");
507 }
508
509 ComputeSDFParams params;
510 params.cells = (float *)cells_data.ptrw();
511 params.size = sdf_size;
512 params.cell_size = cell_size;
513 params.cell_offset = aabb.position + Vector3(cell_size * 0.5, cell_size * 0.5, cell_size * 0.5);
514 params.bvh = bvh.ptr();
515 params.triangles = faces.ptr();
516 params.thickness = th;
517 _compute_sdf(&params);
518
519 Ref<Image> ret = Image::create_from_data(sdf_size.x, sdf_size.y * sdf_size.z, false, Image::FORMAT_RF, cells_data);
520 ret->convert(Image::FORMAT_RH); //convert to half, save space
521 ret->set_meta("depth", sdf_size.z); //hack, make sure to add to the docs of this function
522
523 if (bake_end_function) {
524 bake_end_function();
525 }
526
527 return ret;
528}
529
530PackedStringArray GPUParticlesCollisionSDF3D::get_configuration_warnings() const {
531 PackedStringArray warnings = Node::get_configuration_warnings();
532
533 if (bake_mask == 0) {
534 warnings.push_back(RTR("The Bake Mask has no bits enabled, which means baking will not produce any collision for this GPUParticlesCollisionSDF3D.\nTo resolve this, enable at least one bit in the Bake Mask property."));
535 }
536
537 return warnings;
538}
539
540void GPUParticlesCollisionSDF3D::_bind_methods() {
541 ClassDB::bind_method(D_METHOD("set_size", "size"), &GPUParticlesCollisionSDF3D::set_size);
542 ClassDB::bind_method(D_METHOD("get_size"), &GPUParticlesCollisionSDF3D::get_size);
543
544 ClassDB::bind_method(D_METHOD("set_resolution", "resolution"), &GPUParticlesCollisionSDF3D::set_resolution);
545 ClassDB::bind_method(D_METHOD("get_resolution"), &GPUParticlesCollisionSDF3D::get_resolution);
546
547 ClassDB::bind_method(D_METHOD("set_texture", "texture"), &GPUParticlesCollisionSDF3D::set_texture);
548 ClassDB::bind_method(D_METHOD("get_texture"), &GPUParticlesCollisionSDF3D::get_texture);
549
550 ClassDB::bind_method(D_METHOD("set_thickness", "thickness"), &GPUParticlesCollisionSDF3D::set_thickness);
551 ClassDB::bind_method(D_METHOD("get_thickness"), &GPUParticlesCollisionSDF3D::get_thickness);
552
553 ClassDB::bind_method(D_METHOD("set_bake_mask", "mask"), &GPUParticlesCollisionSDF3D::set_bake_mask);
554 ClassDB::bind_method(D_METHOD("get_bake_mask"), &GPUParticlesCollisionSDF3D::get_bake_mask);
555 ClassDB::bind_method(D_METHOD("set_bake_mask_value", "layer_number", "value"), &GPUParticlesCollisionSDF3D::set_bake_mask_value);
556 ClassDB::bind_method(D_METHOD("get_bake_mask_value", "layer_number"), &GPUParticlesCollisionSDF3D::get_bake_mask_value);
557
558 ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "size", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater,suffix:m"), "set_size", "get_size");
559 ADD_PROPERTY(PropertyInfo(Variant::INT, "resolution", PROPERTY_HINT_ENUM, "16,32,64,128,256,512"), "set_resolution", "get_resolution");
560 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "thickness", PROPERTY_HINT_RANGE, "0.0,2.0,0.01,suffix:m"), "set_thickness", "get_thickness");
561 ADD_PROPERTY(PropertyInfo(Variant::INT, "bake_mask", PROPERTY_HINT_LAYERS_3D_RENDER), "set_bake_mask", "get_bake_mask");
562 ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture3D"), "set_texture", "get_texture");
563
564 BIND_ENUM_CONSTANT(RESOLUTION_16);
565 BIND_ENUM_CONSTANT(RESOLUTION_32);
566 BIND_ENUM_CONSTANT(RESOLUTION_64);
567 BIND_ENUM_CONSTANT(RESOLUTION_128);
568 BIND_ENUM_CONSTANT(RESOLUTION_256);
569 BIND_ENUM_CONSTANT(RESOLUTION_512);
570 BIND_ENUM_CONSTANT(RESOLUTION_MAX);
571}
572
573#ifndef DISABLE_DEPRECATED
574bool GPUParticlesCollisionSDF3D::_set(const StringName &p_name, const Variant &p_value) {
575 if (p_name == "extents") { // Compatibility with Godot 3.x.
576 set_size((Vector3)p_value * 2);
577 return true;
578 }
579 return false;
580}
581
582bool GPUParticlesCollisionSDF3D::_get(const StringName &p_name, Variant &r_property) const {
583 if (p_name == "extents") { // Compatibility with Godot 3.x.
584 r_property = size / 2;
585 return true;
586 }
587 return false;
588}
589#endif // DISABLE_DEPRECATED
590
591void GPUParticlesCollisionSDF3D::set_thickness(float p_thickness) {
592 thickness = p_thickness;
593}
594
595float GPUParticlesCollisionSDF3D::get_thickness() const {
596 return thickness;
597}
598
599void GPUParticlesCollisionSDF3D::set_size(const Vector3 &p_size) {
600 size = p_size;
601 RS::get_singleton()->particles_collision_set_box_extents(_get_collision(), size / 2);
602 update_gizmos();
603}
604
605Vector3 GPUParticlesCollisionSDF3D::get_size() const {
606 return size;
607}
608
609void GPUParticlesCollisionSDF3D::set_resolution(Resolution p_resolution) {
610 resolution = p_resolution;
611 update_gizmos();
612}
613
614GPUParticlesCollisionSDF3D::Resolution GPUParticlesCollisionSDF3D::get_resolution() const {
615 return resolution;
616}
617
618void GPUParticlesCollisionSDF3D::set_bake_mask(uint32_t p_mask) {
619 bake_mask = p_mask;
620 update_configuration_warnings();
621}
622
623uint32_t GPUParticlesCollisionSDF3D::get_bake_mask() const {
624 return bake_mask;
625}
626
627void GPUParticlesCollisionSDF3D::set_bake_mask_value(int p_layer_number, bool p_value) {
628 ERR_FAIL_COND_MSG(p_layer_number < 1 || p_layer_number > 20, vformat("The render layer number (%d) must be between 1 and 20 (inclusive).", p_layer_number));
629 uint32_t mask = get_bake_mask();
630 if (p_value) {
631 mask |= 1 << (p_layer_number - 1);
632 } else {
633 mask &= ~(1 << (p_layer_number - 1));
634 }
635 set_bake_mask(mask);
636}
637
638bool GPUParticlesCollisionSDF3D::get_bake_mask_value(int p_layer_number) const {
639 ERR_FAIL_COND_V_MSG(p_layer_number < 1 || p_layer_number > 20, false, vformat("The render layer number (%d) must be between 1 and 20 (inclusive).", p_layer_number));
640 return bake_mask & (1 << (p_layer_number - 1));
641}
642
643void GPUParticlesCollisionSDF3D::set_texture(const Ref<Texture3D> &p_texture) {
644 texture = p_texture;
645 RID tex = texture.is_valid() ? texture->get_rid() : RID();
646 RS::get_singleton()->particles_collision_set_field_texture(_get_collision(), tex);
647}
648
649Ref<Texture3D> GPUParticlesCollisionSDF3D::get_texture() const {
650 return texture;
651}
652
653AABB GPUParticlesCollisionSDF3D::get_aabb() const {
654 return AABB(-size / 2, size);
655}
656
657GPUParticlesCollisionSDF3D::BakeBeginFunc GPUParticlesCollisionSDF3D::bake_begin_function = nullptr;
658GPUParticlesCollisionSDF3D::BakeStepFunc GPUParticlesCollisionSDF3D::bake_step_function = nullptr;
659GPUParticlesCollisionSDF3D::BakeEndFunc GPUParticlesCollisionSDF3D::bake_end_function = nullptr;
660
661GPUParticlesCollisionSDF3D::GPUParticlesCollisionSDF3D() :
662 GPUParticlesCollision3D(RS::PARTICLES_COLLISION_TYPE_SDF_COLLIDE) {
663}
664
665GPUParticlesCollisionSDF3D::~GPUParticlesCollisionSDF3D() {
666}
667
668////////////////////////////
669////////////////////////////
670
671void GPUParticlesCollisionHeightField3D::_notification(int p_what) {
672 switch (p_what) {
673 case NOTIFICATION_INTERNAL_PROCESS: {
674 if (update_mode == UPDATE_MODE_ALWAYS) {
675 RS::get_singleton()->particles_collision_height_field_update(_get_collision());
676 }
677
678 if (follow_camera_mode && get_viewport()) {
679 Camera3D *cam = get_viewport()->get_camera_3d();
680 if (cam) {
681 Transform3D xform = get_global_transform();
682 Vector3 x_axis = xform.basis.get_column(Vector3::AXIS_X).normalized();
683 Vector3 z_axis = xform.basis.get_column(Vector3::AXIS_Z).normalized();
684 float x_len = xform.basis.get_scale().x;
685 float z_len = xform.basis.get_scale().z;
686
687 Vector3 cam_pos = cam->get_global_transform().origin;
688 Transform3D new_xform = xform;
689
690 while (x_axis.dot(cam_pos - new_xform.origin) > x_len) {
691 new_xform.origin += x_axis * x_len;
692 }
693 while (x_axis.dot(cam_pos - new_xform.origin) < -x_len) {
694 new_xform.origin -= x_axis * x_len;
695 }
696
697 while (z_axis.dot(cam_pos - new_xform.origin) > z_len) {
698 new_xform.origin += z_axis * z_len;
699 }
700 while (z_axis.dot(cam_pos - new_xform.origin) < -z_len) {
701 new_xform.origin -= z_axis * z_len;
702 }
703
704 if (new_xform != xform) {
705 set_global_transform(new_xform);
706 RS::get_singleton()->particles_collision_height_field_update(_get_collision());
707 }
708 }
709 }
710 } break;
711
712 case NOTIFICATION_TRANSFORM_CHANGED: {
713 RS::get_singleton()->particles_collision_height_field_update(_get_collision());
714 } break;
715 }
716}
717
718void GPUParticlesCollisionHeightField3D::_bind_methods() {
719 ClassDB::bind_method(D_METHOD("set_size", "size"), &GPUParticlesCollisionHeightField3D::set_size);
720 ClassDB::bind_method(D_METHOD("get_size"), &GPUParticlesCollisionHeightField3D::get_size);
721
722 ClassDB::bind_method(D_METHOD("set_resolution", "resolution"), &GPUParticlesCollisionHeightField3D::set_resolution);
723 ClassDB::bind_method(D_METHOD("get_resolution"), &GPUParticlesCollisionHeightField3D::get_resolution);
724
725 ClassDB::bind_method(D_METHOD("set_update_mode", "update_mode"), &GPUParticlesCollisionHeightField3D::set_update_mode);
726 ClassDB::bind_method(D_METHOD("get_update_mode"), &GPUParticlesCollisionHeightField3D::get_update_mode);
727
728 ClassDB::bind_method(D_METHOD("set_follow_camera_enabled", "enabled"), &GPUParticlesCollisionHeightField3D::set_follow_camera_enabled);
729 ClassDB::bind_method(D_METHOD("is_follow_camera_enabled"), &GPUParticlesCollisionHeightField3D::is_follow_camera_enabled);
730
731 ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "size", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater,suffix:m"), "set_size", "get_size");
732 ADD_PROPERTY(PropertyInfo(Variant::INT, "resolution", PROPERTY_HINT_ENUM, "256 (Fastest),512 (Fast),1024 (Average),2048 (Slow),4096 (Slower),8192 (Slowest)"), "set_resolution", "get_resolution");
733 ADD_PROPERTY(PropertyInfo(Variant::INT, "update_mode", PROPERTY_HINT_ENUM, "When Moved (Fast),Always (Slow)"), "set_update_mode", "get_update_mode");
734 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "follow_camera_enabled"), "set_follow_camera_enabled", "is_follow_camera_enabled");
735
736 BIND_ENUM_CONSTANT(RESOLUTION_256);
737 BIND_ENUM_CONSTANT(RESOLUTION_512);
738 BIND_ENUM_CONSTANT(RESOLUTION_1024);
739 BIND_ENUM_CONSTANT(RESOLUTION_2048);
740 BIND_ENUM_CONSTANT(RESOLUTION_4096);
741 BIND_ENUM_CONSTANT(RESOLUTION_8192);
742 BIND_ENUM_CONSTANT(RESOLUTION_MAX);
743
744 BIND_ENUM_CONSTANT(UPDATE_MODE_WHEN_MOVED);
745 BIND_ENUM_CONSTANT(UPDATE_MODE_ALWAYS);
746}
747
748#ifndef DISABLE_DEPRECATED
749bool GPUParticlesCollisionHeightField3D::_set(const StringName &p_name, const Variant &p_value) {
750 if (p_name == "extents") { // Compatibility with Godot 3.x.
751 set_size((Vector3)p_value * 2);
752 return true;
753 }
754 return false;
755}
756
757bool GPUParticlesCollisionHeightField3D::_get(const StringName &p_name, Variant &r_property) const {
758 if (p_name == "extents") { // Compatibility with Godot 3.x.
759 r_property = size / 2;
760 return true;
761 }
762 return false;
763}
764#endif // DISABLE_DEPRECATED
765
766void GPUParticlesCollisionHeightField3D::set_size(const Vector3 &p_size) {
767 size = p_size;
768 RS::get_singleton()->particles_collision_set_box_extents(_get_collision(), size / 2);
769 update_gizmos();
770 RS::get_singleton()->particles_collision_height_field_update(_get_collision());
771}
772
773Vector3 GPUParticlesCollisionHeightField3D::get_size() const {
774 return size;
775}
776
777void GPUParticlesCollisionHeightField3D::set_resolution(Resolution p_resolution) {
778 resolution = p_resolution;
779 RS::get_singleton()->particles_collision_set_height_field_resolution(_get_collision(), RS::ParticlesCollisionHeightfieldResolution(resolution));
780 update_gizmos();
781 RS::get_singleton()->particles_collision_height_field_update(_get_collision());
782}
783
784GPUParticlesCollisionHeightField3D::Resolution GPUParticlesCollisionHeightField3D::get_resolution() const {
785 return resolution;
786}
787
788void GPUParticlesCollisionHeightField3D::set_update_mode(UpdateMode p_update_mode) {
789 update_mode = p_update_mode;
790 set_process_internal(follow_camera_mode || update_mode == UPDATE_MODE_ALWAYS);
791}
792
793GPUParticlesCollisionHeightField3D::UpdateMode GPUParticlesCollisionHeightField3D::get_update_mode() const {
794 return update_mode;
795}
796
797void GPUParticlesCollisionHeightField3D::set_follow_camera_enabled(bool p_enabled) {
798 follow_camera_mode = p_enabled;
799 set_process_internal(follow_camera_mode || update_mode == UPDATE_MODE_ALWAYS);
800}
801
802bool GPUParticlesCollisionHeightField3D::is_follow_camera_enabled() const {
803 return follow_camera_mode;
804}
805
806AABB GPUParticlesCollisionHeightField3D::get_aabb() const {
807 return AABB(-size / 2, size);
808}
809
810GPUParticlesCollisionHeightField3D::GPUParticlesCollisionHeightField3D() :
811 GPUParticlesCollision3D(RS::PARTICLES_COLLISION_TYPE_HEIGHTFIELD_COLLIDE) {
812}
813
814GPUParticlesCollisionHeightField3D::~GPUParticlesCollisionHeightField3D() {
815}
816
817////////////////////////////
818////////////////////////////
819
820void GPUParticlesAttractor3D::set_cull_mask(uint32_t p_cull_mask) {
821 cull_mask = p_cull_mask;
822 RS::get_singleton()->particles_collision_set_cull_mask(collision, p_cull_mask);
823}
824
825uint32_t GPUParticlesAttractor3D::get_cull_mask() const {
826 return cull_mask;
827}
828
829void GPUParticlesAttractor3D::set_strength(real_t p_strength) {
830 strength = p_strength;
831 RS::get_singleton()->particles_collision_set_attractor_strength(collision, p_strength);
832}
833
834real_t GPUParticlesAttractor3D::get_strength() const {
835 return strength;
836}
837
838void GPUParticlesAttractor3D::set_attenuation(real_t p_attenuation) {
839 attenuation = p_attenuation;
840 RS::get_singleton()->particles_collision_set_attractor_attenuation(collision, p_attenuation);
841}
842
843real_t GPUParticlesAttractor3D::get_attenuation() const {
844 return attenuation;
845}
846
847void GPUParticlesAttractor3D::set_directionality(real_t p_directionality) {
848 directionality = p_directionality;
849 RS::get_singleton()->particles_collision_set_attractor_directionality(collision, p_directionality);
850 update_gizmos();
851}
852
853real_t GPUParticlesAttractor3D::get_directionality() const {
854 return directionality;
855}
856
857void GPUParticlesAttractor3D::_bind_methods() {
858 ClassDB::bind_method(D_METHOD("set_cull_mask", "mask"), &GPUParticlesAttractor3D::set_cull_mask);
859 ClassDB::bind_method(D_METHOD("get_cull_mask"), &GPUParticlesAttractor3D::get_cull_mask);
860
861 ClassDB::bind_method(D_METHOD("set_strength", "strength"), &GPUParticlesAttractor3D::set_strength);
862 ClassDB::bind_method(D_METHOD("get_strength"), &GPUParticlesAttractor3D::get_strength);
863
864 ClassDB::bind_method(D_METHOD("set_attenuation", "attenuation"), &GPUParticlesAttractor3D::set_attenuation);
865 ClassDB::bind_method(D_METHOD("get_attenuation"), &GPUParticlesAttractor3D::get_attenuation);
866
867 ClassDB::bind_method(D_METHOD("set_directionality", "amount"), &GPUParticlesAttractor3D::set_directionality);
868 ClassDB::bind_method(D_METHOD("get_directionality"), &GPUParticlesAttractor3D::get_directionality);
869
870 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "strength", PROPERTY_HINT_RANGE, "-128,128,0.01,or_greater,or_less"), "set_strength", "get_strength");
871 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "attenuation", PROPERTY_HINT_EXP_EASING, "0,8,0.01"), "set_attenuation", "get_attenuation");
872 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "directionality", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_directionality", "get_directionality");
873 ADD_PROPERTY(PropertyInfo(Variant::INT, "cull_mask", PROPERTY_HINT_LAYERS_3D_RENDER), "set_cull_mask", "get_cull_mask");
874}
875
876GPUParticlesAttractor3D::GPUParticlesAttractor3D(RS::ParticlesCollisionType p_type) {
877 collision = RS::get_singleton()->particles_collision_create();
878 RS::get_singleton()->particles_collision_set_collision_type(collision, p_type);
879 set_base(collision);
880}
881GPUParticlesAttractor3D::~GPUParticlesAttractor3D() {
882 ERR_FAIL_NULL(RenderingServer::get_singleton());
883 RS::get_singleton()->free(collision);
884}
885
886/////////////////////////////////
887
888void GPUParticlesAttractorSphere3D::_bind_methods() {
889 ClassDB::bind_method(D_METHOD("set_radius", "radius"), &GPUParticlesAttractorSphere3D::set_radius);
890 ClassDB::bind_method(D_METHOD("get_radius"), &GPUParticlesAttractorSphere3D::get_radius);
891
892 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater,suffix:m"), "set_radius", "get_radius");
893}
894
895void GPUParticlesAttractorSphere3D::set_radius(real_t p_radius) {
896 radius = p_radius;
897 RS::get_singleton()->particles_collision_set_sphere_radius(_get_collision(), radius);
898 update_gizmos();
899}
900
901real_t GPUParticlesAttractorSphere3D::get_radius() const {
902 return radius;
903}
904
905AABB GPUParticlesAttractorSphere3D::get_aabb() const {
906 return AABB(Vector3(-radius, -radius, -radius), Vector3(radius * 2, radius * 2, radius * 2));
907}
908
909GPUParticlesAttractorSphere3D::GPUParticlesAttractorSphere3D() :
910 GPUParticlesAttractor3D(RS::PARTICLES_COLLISION_TYPE_SPHERE_ATTRACT) {
911}
912
913GPUParticlesAttractorSphere3D::~GPUParticlesAttractorSphere3D() {
914}
915
916///////////////////////////
917
918void GPUParticlesAttractorBox3D::_bind_methods() {
919 ClassDB::bind_method(D_METHOD("set_size", "size"), &GPUParticlesAttractorBox3D::set_size);
920 ClassDB::bind_method(D_METHOD("get_size"), &GPUParticlesAttractorBox3D::get_size);
921
922 ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "size", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater,suffix:m"), "set_size", "get_size");
923}
924
925#ifndef DISABLE_DEPRECATED
926bool GPUParticlesAttractorBox3D::_set(const StringName &p_name, const Variant &p_value) {
927 if (p_name == "extents") { // Compatibility with Godot 3.x.
928 set_size((Vector3)p_value * 2);
929 return true;
930 }
931 return false;
932}
933
934bool GPUParticlesAttractorBox3D::_get(const StringName &p_name, Variant &r_property) const {
935 if (p_name == "extents") { // Compatibility with Godot 3.x.
936 r_property = size / 2;
937 return true;
938 }
939 return false;
940}
941#endif // DISABLE_DEPRECATED
942
943void GPUParticlesAttractorBox3D::set_size(const Vector3 &p_size) {
944 size = p_size;
945 RS::get_singleton()->particles_collision_set_box_extents(_get_collision(), size / 2);
946 update_gizmos();
947}
948
949Vector3 GPUParticlesAttractorBox3D::get_size() const {
950 return size;
951}
952
953AABB GPUParticlesAttractorBox3D::get_aabb() const {
954 return AABB(-size / 2, size);
955}
956
957GPUParticlesAttractorBox3D::GPUParticlesAttractorBox3D() :
958 GPUParticlesAttractor3D(RS::PARTICLES_COLLISION_TYPE_BOX_ATTRACT) {
959}
960
961GPUParticlesAttractorBox3D::~GPUParticlesAttractorBox3D() {
962}
963
964///////////////////////////
965
966void GPUParticlesAttractorVectorField3D::_bind_methods() {
967 ClassDB::bind_method(D_METHOD("set_size", "size"), &GPUParticlesAttractorVectorField3D::set_size);
968 ClassDB::bind_method(D_METHOD("get_size"), &GPUParticlesAttractorVectorField3D::get_size);
969
970 ClassDB::bind_method(D_METHOD("set_texture", "texture"), &GPUParticlesAttractorVectorField3D::set_texture);
971 ClassDB::bind_method(D_METHOD("get_texture"), &GPUParticlesAttractorVectorField3D::get_texture);
972
973 ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "size", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater,suffix:m"), "set_size", "get_size");
974 ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture3D"), "set_texture", "get_texture");
975}
976
977#ifndef DISABLE_DEPRECATED
978bool GPUParticlesAttractorVectorField3D::_set(const StringName &p_name, const Variant &p_value) {
979 if (p_name == "extents") { // Compatibility with Godot 3.x.
980 set_size((Vector3)p_value * 2);
981 return true;
982 }
983 return false;
984}
985
986bool GPUParticlesAttractorVectorField3D::_get(const StringName &p_name, Variant &r_property) const {
987 if (p_name == "extents") { // Compatibility with Godot 3.x.
988 r_property = size / 2;
989 return true;
990 }
991 return false;
992}
993#endif // DISABLE_DEPRECATED
994
995void GPUParticlesAttractorVectorField3D::set_size(const Vector3 &p_size) {
996 size = p_size;
997 RS::get_singleton()->particles_collision_set_box_extents(_get_collision(), size / 2);
998 update_gizmos();
999}
1000
1001Vector3 GPUParticlesAttractorVectorField3D::get_size() const {
1002 return size;
1003}
1004
1005void GPUParticlesAttractorVectorField3D::set_texture(const Ref<Texture3D> &p_texture) {
1006 texture = p_texture;
1007 RID tex = texture.is_valid() ? texture->get_rid() : RID();
1008 RS::get_singleton()->particles_collision_set_field_texture(_get_collision(), tex);
1009}
1010
1011Ref<Texture3D> GPUParticlesAttractorVectorField3D::get_texture() const {
1012 return texture;
1013}
1014
1015AABB GPUParticlesAttractorVectorField3D::get_aabb() const {
1016 return AABB(-size / 2, size);
1017}
1018
1019GPUParticlesAttractorVectorField3D::GPUParticlesAttractorVectorField3D() :
1020 GPUParticlesAttractor3D(RS::PARTICLES_COLLISION_TYPE_VECTOR_FIELD_ATTRACT) {
1021}
1022
1023GPUParticlesAttractorVectorField3D::~GPUParticlesAttractorVectorField3D() {
1024}
1025