Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding Raycast3D custom debug shape thickness and color #46529

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions doc/classes/RayCast3D.xml
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,13 @@
<member name="collision_mask" type="int" setter="set_collision_mask" getter="get_collision_mask" default="1">
The ray's collision mask. Only objects in at least one collision layer enabled in the mask will be detected. See [url=https://docs.godotengine.org/en/latest/tutorials/physics/physics_introduction.html#collision-layers-and-masks]Collision layers and masks[/url] in the documentation for more information.
</member>
<member name="debug_shape_custom_color" type="Color" setter="set_debug_shape__custom_color" getter="get_debug_shape_custom_color" default="Color( 0.0, 0.0, 0.0 )">
The custom color to use to draw the shape in the editor and at run-time if [b]Visible Collision Shapes[/b] is enabled in the [b]Debug[/b] menu. This color will be highlighted at run-time if the [RayCast3D] is colliding with something.
If set to [code]Color(0.0, 0.0, 0.0)[/code] (by default), the color set in [member ProjectSettings.debug/shapes/collision/shape_color] is used.
</member>
<member name="debug_shape_thickness" type="int" setter="set_debug_shape_thickness" getter="get_debug_shape_thickness" default="1">
If set to [code]1[/code], a line is used as the debug shape. Otherwise, a truncated pyramid is drawn to represent the [RayCast3D]. Requires [b]Visible Collision Shapes[/b] to be enabled in the [b]Debug[/b] menu for the debug shape to be visible at run-time.
</member>
<member name="enabled" type="bool" setter="set_enabled" getter="is_enabled" default="true">
If [code]true[/code], collisions will be reported.
</member>
Expand Down
31 changes: 17 additions & 14 deletions editor/node_3d_editor_gizmos.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,11 @@ void EditorNode3DGizmo::add_mesh(const Ref<ArrayMesh> &p_mesh, bool p_billboard,
}

void EditorNode3DGizmo::add_lines(const Vector<Vector3> &p_lines, const Ref<Material> &p_material, bool p_billboard, const Color &p_modulate) {
if (p_lines.is_empty()) {
add_vertices(p_lines, p_material, Mesh::PRIMITIVE_LINES, p_billboard, p_modulate);
}

void EditorNode3DGizmo::add_vertices(const Vector<Vector3> &p_vertices, const Ref<Material> &p_material, Mesh::PrimitiveType p_primitive_type, bool p_billboard, const Color &p_modulate) {
if (p_vertices.is_empty()) {
return;
}

Expand All @@ -209,13 +213,13 @@ void EditorNode3DGizmo::add_lines(const Vector<Vector3> &p_lines, const Ref<Mate
Array a;
a.resize(Mesh::ARRAY_MAX);

a[Mesh::ARRAY_VERTEX] = p_lines;
a[Mesh::ARRAY_VERTEX] = p_vertices;

Vector<Color> color;
color.resize(p_lines.size());
color.resize(p_vertices.size());
{
Color *w = color.ptrw();
for (int i = 0; i < p_lines.size(); i++) {
for (int i = 0; i < p_vertices.size(); i++) {
if (is_selected()) {
w[i] = Color(1, 1, 1, 0.8) * p_modulate;
} else {
Expand All @@ -226,13 +230,13 @@ void EditorNode3DGizmo::add_lines(const Vector<Vector3> &p_lines, const Ref<Mate

a[Mesh::ARRAY_COLOR] = color;

mesh->add_surface_from_arrays(Mesh::PRIMITIVE_LINES, a);
mesh->add_surface_from_arrays(p_primitive_type, a);
mesh->surface_set_material(0, p_material);

if (p_billboard) {
float md = 0;
for (int i = 0; i < p_lines.size(); i++) {
md = MAX(0, p_lines[i].length());
for (int i = 0; i < p_vertices.size(); i++) {
md = MAX(0, p_vertices[i].length());
}
if (md) {
mesh->set_custom_aabb(AABB(Vector3(-md, -md, -md), Vector3(md, md, md) * 2.0));
Expand Down Expand Up @@ -1906,16 +1910,15 @@ void RayCast3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {

p_gizmo->clear();

Vector<Vector3> lines;
const Ref<StandardMaterial3D> material = raycast->is_enabled() ? raycast->get_debug_material() : get_material("shape_material_disabled");

lines.push_back(Vector3());
lines.push_back(raycast->get_target_position());
p_gizmo->add_lines(raycast->get_debug_line_vertices(), material);

const Ref<StandardMaterial3D> material =
get_material(raycast->is_enabled() ? "shape_material" : "shape_material_disabled", p_gizmo);
if (raycast->get_debug_shape_thickness() > 1) {
p_gizmo->add_vertices(raycast->get_debug_shape_vertices(), material, Mesh::PRIMITIVE_TRIANGLE_STRIP);
}

p_gizmo->add_lines(lines, material);
p_gizmo->add_collision_segments(lines);
p_gizmo->add_collision_segments(raycast->get_debug_line_vertices());
}

/////
Expand Down
1 change: 1 addition & 0 deletions editor/plugins/node_3d_editor_plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ class EditorNode3DGizmo : public Node3DGizmo {

public:
void add_lines(const Vector<Vector3> &p_lines, const Ref<Material> &p_material, bool p_billboard = false, const Color &p_modulate = Color(1, 1, 1));
void add_vertices(const Vector<Vector3> &p_vertices, const Ref<Material> &p_material, Mesh::PrimitiveType p_primitive_type, bool p_billboard = false, const Color &p_modulate = Color(1, 1, 1));
void add_mesh(const Ref<ArrayMesh> &p_mesh, bool p_billboard = false, const Ref<SkinReference> &p_skin_reference = Ref<SkinReference>(), const Ref<Material> &p_material = Ref<Material>());
void add_collision_segments(const Vector<Vector3> &p_lines);
void add_collision_triangles(const Ref<TriangleMesh> &p_tmesh);
Expand Down
162 changes: 133 additions & 29 deletions scene/3d/ray_cast_3d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,13 @@

void RayCast3D::set_target_position(const Vector3 &p_point) {
target_position = p_point;
if (is_inside_tree() && (Engine::get_singleton()->is_editor_hint() || get_tree()->is_debugging_collisions_hint())) {
update_gizmo();
}
if (is_inside_tree() && get_tree()->is_debugging_collisions_hint()) {
update_gizmo();

if (Engine::get_singleton()->is_editor_hint()) {
if (is_inside_tree()) {
_update_debug_shape_vertices();
}
} else if (debug_shape) {
_update_debug_shape();
}
}
Expand Down Expand Up @@ -146,6 +149,9 @@ bool RayCast3D::get_exclude_parent_body() const {
void RayCast3D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
if (Engine::get_singleton()->is_editor_hint()) {
_update_debug_shape_vertices();
}
if (enabled && !Engine::get_singleton()->is_editor_hint()) {
set_physics_process_internal(true);
} else {
Expand Down Expand Up @@ -183,10 +189,7 @@ void RayCast3D::_notification(int p_what) {
bool prev_collision_state = collided;
_update_raycast_state();
if (prev_collision_state != collided && get_tree()->is_debugging_collisions_hint()) {
if (debug_material.is_valid()) {
Ref<StandardMaterial3D> line_material = static_cast<Ref<StandardMaterial3D>>(debug_material);
line_material->set_albedo(collided ? Color(1.0, 0, 0) : Color(1.0, 0.8, 0.6));
}
_update_debug_shape_material(true);
}

} break;
Expand Down Expand Up @@ -310,6 +313,12 @@ void RayCast3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_collide_with_bodies", "enable"), &RayCast3D::set_collide_with_bodies);
ClassDB::bind_method(D_METHOD("is_collide_with_bodies_enabled"), &RayCast3D::is_collide_with_bodies_enabled);

ClassDB::bind_method(D_METHOD("set_debug_shape_custom_color", "debug_shape_custom_color"), &RayCast3D::set_debug_shape_custom_color);
ClassDB::bind_method(D_METHOD("get_debug_shape_custom_color"), &RayCast3D::get_debug_shape_custom_color);

ClassDB::bind_method(D_METHOD("set_debug_shape_thickness", "debug_shape_thickness"), &RayCast3D::set_debug_shape_thickness);
ClassDB::bind_method(D_METHOD("get_debug_shape_thickness"), &RayCast3D::get_debug_shape_thickness);

ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "exclude_parent"), "set_exclude_parent_body", "get_exclude_parent_body");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "target_position"), "set_target_position", "get_target_position");
Expand All @@ -318,16 +327,80 @@ void RayCast3D::_bind_methods() {
ADD_GROUP("Collide With", "collide_with");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collide_with_areas", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collide_with_areas", "is_collide_with_areas_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collide_with_bodies", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collide_with_bodies", "is_collide_with_bodies_enabled");

ADD_GROUP("Debug Shape", "debug_shape");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "debug_shape_custom_color"), "set_debug_shape_custom_color", "get_debug_shape_custom_color");
ADD_PROPERTY(PropertyInfo(Variant::INT, "debug_shape_thickness", PROPERTY_HINT_RANGE, "1,5"), "set_debug_shape_thickness", "get_debug_shape_thickness");
}

void RayCast3D::_create_debug_shape() {
if (!debug_material.is_valid()) {
debug_material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D));
float RayCast3D::get_debug_shape_thickness() const {
return debug_shape_thickness;
}

void RayCast3D::_update_debug_shape_vertices() {
debug_shape_vertices.clear();
debug_line_vertices.clear();

if (target_position == Vector3()) {
return;
}

debug_line_vertices.push_back(Vector3());
debug_line_vertices.push_back(target_position);

if (debug_shape_thickness > 1) {
float scale_factor = 100.0;
Vector3 dir = Vector3(target_position).normalized();
// Draw truncated pyramid
Vector3 normal = (fabs(dir.x) + fabs(dir.y) > CMP_EPSILON) ? Vector3(-dir.y, dir.x, 0).normalized() : Vector3(0, -dir.z, dir.y).normalized();
normal *= debug_shape_thickness / scale_factor;
int vertices_strip_order[14] = { 4, 5, 0, 1, 2, 5, 6, 4, 7, 0, 3, 2, 7, 6 };
for (int v = 0; v < 14; v++) {
Vector3 vertex = vertices_strip_order[v] < 4 ? normal : normal / 3.0 + target_position;
debug_shape_vertices.push_back(vertex.rotated(dir, Math_PI * (0.5 * (vertices_strip_order[v] % 4) + 0.25)));
}
}
}

void RayCast3D::set_debug_shape_thickness(const float p_debug_shape_thickness) {
debug_shape_thickness = p_debug_shape_thickness;
update_gizmo();

if (Engine::get_singleton()->is_editor_hint()) {
if (is_inside_tree()) {
_update_debug_shape_vertices();
}
} else if (debug_shape) {
_update_debug_shape();
}
}

const Vector<Vector3> &RayCast3D::get_debug_shape_vertices() const {
return debug_shape_vertices;
}

Ref<StandardMaterial3D> line_material = static_cast<Ref<StandardMaterial3D>>(debug_material);
line_material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
line_material->set_albedo(Color(1.0, 0.8, 0.6));
const Vector<Vector3> &RayCast3D::get_debug_line_vertices() const {
return debug_line_vertices;
}

void RayCast3D::set_debug_shape_custom_color(const Color &p_color) {
debug_shape_custom_color = p_color;
if (debug_material.is_valid()) {
_update_debug_shape_material();
}
}

Ref<StandardMaterial3D> RayCast3D::get_debug_material() {
_update_debug_shape_material();
return debug_material;
}

const Color &RayCast3D::get_debug_shape_custom_color() const {
return debug_shape_custom_color;
}

void RayCast3D::_create_debug_shape() {
_update_debug_shape_material();

Ref<ArrayMesh> mesh = memnew(ArrayMesh);

Expand All @@ -338,6 +411,35 @@ void RayCast3D::_create_debug_shape() {
debug_shape = mi;
}

void RayCast3D::_update_debug_shape_material(bool p_check_collision) {
if (!debug_material.is_valid()) {
Ref<StandardMaterial3D> material = memnew(StandardMaterial3D);
debug_material = material;

material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
material->set_transparency(BaseMaterial3D::TRANSPARENCY_ALPHA);
}

Color color = debug_shape_custom_color;
if (color == Color(0.0, 0.0, 0.0)) {
// Use the default debug shape color defined in the Project Settings.
color = get_tree()->get_debug_collisions_color();
}

if (p_check_collision) {
if ((color.get_h() < 0.055 || color.get_h() > 0.945) && color.get_s() > 0.5 && color.get_v() > 0.5) {
// If base color is already quite reddish, hightlight collision with green color
color = Color(0.0, 1.0, 0.0, color.a);
} else {
// Else, hightlight collision with red color
color = Color(1.0, 0, 0, color.a);
}
}

Ref<StandardMaterial3D> material = static_cast<Ref<StandardMaterial3D>>(debug_material);
material->set_albedo(color);
}

void RayCast3D::_update_debug_shape() {
if (!enabled) {
return;
Expand All @@ -353,26 +455,28 @@ void RayCast3D::_update_debug_shape() {
return;
}

Vector<Vector3> verts;
verts.push_back(Vector3());
verts.push_back(target_position);
_update_debug_shape_vertices();

if (mesh->get_surface_count() == 0) {
Array a;
a.resize(Mesh::ARRAY_MAX);
a[Mesh::ARRAY_VERTEX] = verts;
mesh->clear_surfaces();

uint32_t flags = Mesh::ARRAY_FLAG_USE_DYNAMIC_UPDATE;
Array a;
a.resize(Mesh::ARRAY_MAX);

uint32_t flags = 0;
int surface_count = 0;

if (!debug_line_vertices.is_empty()) {
a[Mesh::ARRAY_VERTEX] = debug_line_vertices;
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_LINES, a, Array(), Dictionary(), flags);
mesh->surface_set_material(0, debug_material);
} else {
Vector<uint8_t> byte_array;
int array_size = sizeof(Vector3) * verts.size();
byte_array.resize(array_size);
copymem(byte_array.ptrw(), verts.ptr(), array_size);
mesh->surface_set_material(surface_count, debug_material);
++surface_count;
}

RS::get_singleton()->mesh_surface_update_region(mesh->get_rid(), 0, 0, byte_array);
if (!debug_shape_vertices.is_empty()) {
a[Mesh::ARRAY_VERTEX] = debug_shape_vertices;
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLE_STRIP, a, Array(), Dictionary(), flags);
mesh->surface_set_material(surface_count, debug_material);
++surface_count;
}
}

Expand Down
17 changes: 17 additions & 0 deletions scene/3d/ray_cast_3d.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,15 @@ class RayCast3D : public Node3D {

Node *debug_shape = nullptr;
Ref<Material> debug_material;
Color debug_shape_custom_color = Color(0.0, 0.0, 0.0);
int debug_shape_thickness = 2;
Vector<Vector3> debug_shape_vertices;
Vector<Vector3> debug_line_vertices;

void _create_debug_shape();
void _update_debug_shape();
void _update_debug_shape_material(bool p_check_collision = false);
void _update_debug_shape_vertices();
void _clear_debug_shape();

bool collide_with_areas = false;
Expand Down Expand Up @@ -86,6 +92,17 @@ class RayCast3D : public Node3D {
void set_exclude_parent_body(bool p_exclude_parent_body);
bool get_exclude_parent_body() const;

const Color &get_debug_shape_custom_color() const;
void set_debug_shape_custom_color(const Color &p_color);

const Vector<Vector3> &get_debug_shape_vertices() const;
const Vector<Vector3> &get_debug_line_vertices() const;

Ref<StandardMaterial3D> get_debug_material();

float get_debug_shape_thickness() const;
void set_debug_shape_thickness(const float p_debug_thickness);

void force_raycast_update();
bool is_colliding() const;
Object *get_collider() const;
Expand Down