/**************************************************************************/ /* collision_shape_3d_gizmo_plugin.cpp */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /**************************************************************************/ /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ /* */ /* 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 "collision_shape_3d_gizmo_plugin.h" #include "core/math/convex_hull.h" #include "core/math/geometry_3d.h" #include "editor/editor_settings.h" #include "editor/editor_undo_redo_manager.h" #include "editor/plugins/gizmos/gizmo_3d_helper.h" #include "editor/plugins/node_3d_editor_plugin.h" #include "scene/3d/collision_shape_3d.h" #include "scene/resources/box_shape_3d.h" #include "scene/resources/capsule_shape_3d.h" #include "scene/resources/concave_polygon_shape_3d.h" #include "scene/resources/convex_polygon_shape_3d.h" #include "scene/resources/cylinder_shape_3d.h" #include "scene/resources/height_map_shape_3d.h" #include "scene/resources/separation_ray_shape_3d.h" #include "scene/resources/sphere_shape_3d.h" #include "scene/resources/world_boundary_shape_3d.h" CollisionShape3DGizmoPlugin::CollisionShape3DGizmoPlugin() { helper.instantiate(); const Color gizmo_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/shape"); create_material("shape_material", gizmo_color); const float gizmo_value = gizmo_color.get_v(); const Color gizmo_color_disabled = Color(gizmo_value, gizmo_value, gizmo_value, 0.65); create_material("shape_material_disabled", gizmo_color_disabled); create_handle_material("handles"); } CollisionShape3DGizmoPlugin::~CollisionShape3DGizmoPlugin() { } bool CollisionShape3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { return Object::cast_to(p_spatial) != nullptr; } String CollisionShape3DGizmoPlugin::get_gizmo_name() const { return "CollisionShape3D"; } int CollisionShape3DGizmoPlugin::get_priority() const { return -1; } String CollisionShape3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { const CollisionShape3D *cs = Object::cast_to(p_gizmo->get_node_3d()); Ref s = cs->get_shape(); if (s.is_null()) { return ""; } if (Object::cast_to(*s)) { return "Radius"; } if (Object::cast_to(*s)) { return helper->box_get_handle_name(p_id); } if (Object::cast_to(*s)) { return p_id == 0 ? "Radius" : "Height"; } if (Object::cast_to(*s)) { return p_id == 0 ? "Radius" : "Height"; } if (Object::cast_to(*s)) { return "Length"; } return ""; } Variant CollisionShape3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { CollisionShape3D *cs = Object::cast_to(p_gizmo->get_node_3d()); Ref s = cs->get_shape(); if (s.is_null()) { return Variant(); } if (Object::cast_to(*s)) { Ref ss = s; return ss->get_radius(); } if (Object::cast_to(*s)) { Ref bs = s; return bs->get_size(); } if (Object::cast_to(*s)) { Ref cs2 = s; return Vector2(cs2->get_radius(), cs2->get_height()); } if (Object::cast_to(*s)) { Ref cs2 = s; return p_id == 0 ? cs2->get_radius() : cs2->get_height(); } if (Object::cast_to(*s)) { Ref cs2 = s; return cs2->get_length(); } return Variant(); } void CollisionShape3DGizmoPlugin::begin_handle_action(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) { helper->initialize_handle_action(get_handle_value(p_gizmo, p_id, p_secondary), p_gizmo->get_node_3d()->get_global_transform()); } void CollisionShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { CollisionShape3D *cs = Object::cast_to(p_gizmo->get_node_3d()); Ref s = cs->get_shape(); if (s.is_null()) { return; } Vector3 sg[2]; helper->get_segment(p_camera, p_point, sg); if (Object::cast_to(*s)) { Ref ss = s; 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::snapped(d, Node3DEditor::get_singleton()->get_translate_snap()); } if (d < 0.001) { d = 0.001; } ss->set_radius(d); } if (Object::cast_to(*s)) { Ref rs = s; Vector3 ra, rb; Geometry3D::get_closest_points_between_segments(Vector3(), Vector3(0, 0, 4096), sg[0], sg[1], ra, rb); float d = ra.z; if (Node3DEditor::get_singleton()->is_snap_enabled()) { d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap()); } if (d < 0.001) { d = 0.001; } rs->set_length(d); } if (Object::cast_to(*s)) { Ref bs = s; Vector3 size = bs->get_size(); Vector3 position; helper->box_set_handle(sg, p_id, size, position); bs->set_size(size); cs->set_global_position(position); } if (Object::cast_to(*s)) { Vector3 axis; axis[p_id == 0 ? 0 : 1] = 1.0; Ref cs2 = s; Vector3 ra, rb; Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb); float d = axis.dot(ra); if (Node3DEditor::get_singleton()->is_snap_enabled()) { d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap()); } if (d < 0.001) { d = 0.001; } if (p_id == 0) { cs2->set_radius(d); } else if (p_id == 1) { cs2->set_height(d * 2.0); } } if (Object::cast_to(*s)) { Vector3 axis; axis[p_id == 0 ? 0 : 1] = 1.0; Ref cs2 = s; Vector3 ra, rb; Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb); float d = axis.dot(ra); if (Node3DEditor::get_singleton()->is_snap_enabled()) { d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap()); } if (d < 0.001) { d = 0.001; } if (p_id == 0) { cs2->set_radius(d); } else if (p_id == 1) { cs2->set_height(d * 2.0); } } } void CollisionShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { CollisionShape3D *cs = Object::cast_to(p_gizmo->get_node_3d()); Ref s = cs->get_shape(); if (s.is_null()) { return; } if (Object::cast_to(*s)) { Ref ss = s; if (p_cancel) { ss->set_radius(p_restore); return; } EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Change Sphere Shape Radius")); ur->add_do_method(ss.ptr(), "set_radius", ss->get_radius()); ur->add_undo_method(ss.ptr(), "set_radius", p_restore); ur->commit_action(); } if (Object::cast_to(*s)) { helper->box_commit_handle(TTR("Change Box Shape Size"), p_cancel, cs, s.ptr()); } if (Object::cast_to(*s)) { Ref ss = s; Vector2 values = p_restore; if (p_cancel) { ss->set_radius(values[0]); ss->set_height(values[1]); return; } EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); if (p_id == 0) { ur->create_action(TTR("Change Capsule Shape Radius")); ur->add_do_method(ss.ptr(), "set_radius", ss->get_radius()); } else { ur->create_action(TTR("Change Capsule Shape Height")); ur->add_do_method(ss.ptr(), "set_height", ss->get_height()); } ur->add_undo_method(ss.ptr(), "set_radius", values[0]); ur->add_undo_method(ss.ptr(), "set_height", values[1]); ur->commit_action(); } if (Object::cast_to(*s)) { Ref ss = s; if (p_cancel) { if (p_id == 0) { ss->set_radius(p_restore); } else { ss->set_height(p_restore); } return; } EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); if (p_id == 0) { ur->create_action(TTR("Change Cylinder Shape Radius")); ur->add_do_method(ss.ptr(), "set_radius", ss->get_radius()); ur->add_undo_method(ss.ptr(), "set_radius", p_restore); } else { ur->create_action( /// //////// TTR("Change Cylinder Shape Height")); ur->add_do_method(ss.ptr(), "set_height", ss->get_height()); ur->add_undo_method(ss.ptr(), "set_height", p_restore); } ur->commit_action(); } if (Object::cast_to(*s)) { Ref ss = s; if (p_cancel) { ss->set_length(p_restore); return; } EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Change Separation Ray Shape Length")); ur->add_do_method(ss.ptr(), "set_length", ss->get_length()); ur->add_undo_method(ss.ptr(), "set_length", p_restore); ur->commit_action(); } } void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { CollisionShape3D *cs = Object::cast_to(p_gizmo->get_node_3d()); p_gizmo->clear(); Ref s = cs->get_shape(); if (s.is_null()) { return; } const Ref material = get_material(!cs->is_disabled() ? "shape_material" : "shape_material_disabled", p_gizmo); Ref handles_material = get_material("handles"); if (Object::cast_to(*s)) { Ref sp = s; float r = sp->get_radius(); Vector points; for (int i = 0; i <= 360; i++) { float ra = Math::deg_to_rad((float)i); float rb = Math::deg_to_rad((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 collision_segments; for (int i = 0; i < 64; i++) { float ra = i * (Math_TAU / 64.0); float rb = (i + 1) * (Math_TAU / 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 handles; handles.push_back(Vector3(r, 0, 0)); p_gizmo->add_handles(handles, handles_material); } if (Object::cast_to(*s)) { Ref bs = s; Vector lines; AABB aabb; aabb.position = -bs->get_size() / 2; aabb.size = bs->get_size(); for (int i = 0; i < 12; i++) { Vector3 a, b; aabb.get_edge(i, a, b); lines.push_back(a); lines.push_back(b); } const Vector handles = helper->box_get_handles(bs->get_size()); p_gizmo->add_lines(lines, material); p_gizmo->add_collision_segments(lines); p_gizmo->add_handles(handles, handles_material); } if (Object::cast_to(*s)) { Ref cs2 = s; float radius = cs2->get_radius(); float height = cs2->get_height(); Vector points; Vector3 d(0, height * 0.5 - radius, 0); for (int i = 0; i < 360; i++) { float ra = Math::deg_to_rad((float)i); float rb = Math::deg_to_rad((float)i + 1); Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * radius; Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * radius; points.push_back(Vector3(a.x, 0, a.y) + d); points.push_back(Vector3(b.x, 0, b.y) + d); points.push_back(Vector3(a.x, 0, a.y) - d); points.push_back(Vector3(b.x, 0, b.y) - d); if (i % 90 == 0) { points.push_back(Vector3(a.x, 0, a.y) + d); points.push_back(Vector3(a.x, 0, a.y) - d); } Vector3 dud = i < 180 ? d : -d; points.push_back(Vector3(0, a.x, a.y) + dud); points.push_back(Vector3(0, b.x, b.y) + dud); points.push_back(Vector3(a.y, a.x, 0) + dud); points.push_back(Vector3(b.y, b.x, 0) + dud); } p_gizmo->add_lines(points, material); Vector collision_segments; for (int i = 0; i < 64; i++) { float ra = i * (Math_TAU / 64.0); float rb = (i + 1) * (Math_TAU / 64.0); Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * radius; Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * radius; collision_segments.push_back(Vector3(a.x, 0, a.y) + d); collision_segments.push_back(Vector3(b.x, 0, b.y) + d); collision_segments.push_back(Vector3(a.x, 0, a.y) - d); collision_segments.push_back(Vector3(b.x, 0, b.y) - d); if (i % 16 == 0) { collision_segments.push_back(Vector3(a.x, 0, a.y) + d); collision_segments.push_back(Vector3(a.x, 0, a.y) - d); } Vector3 dud = i < 32 ? d : -d; collision_segments.push_back(Vector3(0, a.x, a.y) + dud); collision_segments.push_back(Vector3(0, b.x, b.y) + dud); collision_segments.push_back(Vector3(a.y, a.x, 0) + dud); collision_segments.push_back(Vector3(b.y, b.x, 0) + dud); } p_gizmo->add_collision_segments(collision_segments); Vector handles = { Vector3(cs2->get_radius(), 0, 0), Vector3(0, cs2->get_height() * 0.5, 0) }; p_gizmo->add_handles(handles, handles_material); } if (Object::cast_to(*s)) { Ref cs2 = s; float radius = cs2->get_radius(); float height = cs2->get_height(); Vector points; Vector3 d(0, height * 0.5, 0); for (int i = 0; i < 360; i++) { float ra = Math::deg_to_rad((float)i); float rb = Math::deg_to_rad((float)i + 1); Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * radius; Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * radius; points.push_back(Vector3(a.x, 0, a.y) + d); points.push_back(Vector3(b.x, 0, b.y) + d); points.push_back(Vector3(a.x, 0, a.y) - d); points.push_back(Vector3(b.x, 0, b.y) - d); if (i % 90 == 0) { points.push_back(Vector3(a.x, 0, a.y) + d); points.push_back(Vector3(a.x, 0, a.y) - d); } } p_gizmo->add_lines(points, material); Vector collision_segments; for (int i = 0; i < 64; i++) { float ra = i * (Math_TAU / 64.0); float rb = (i + 1) * (Math_TAU / 64.0); Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * radius; Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * radius; collision_segments.push_back(Vector3(a.x, 0, a.y) + d); collision_segments.push_back(Vector3(b.x, 0, b.y) + d); collision_segments.push_back(Vector3(a.x, 0, a.y) - d); collision_segments.push_back(Vector3(b.x, 0, b.y) - d); if (i % 16 == 0) { collision_segments.push_back(Vector3(a.x, 0, a.y) + d); collision_segments.push_back(Vector3(a.x, 0, a.y) - d); } } p_gizmo->add_collision_segments(collision_segments); Vector handles = { Vector3(cs2->get_radius(), 0, 0), Vector3(0, cs2->get_height() * 0.5, 0) }; p_gizmo->add_handles(handles, handles_material); } if (Object::cast_to(*s)) { Ref wbs = s; const Plane &p = wbs->get_plane(); Vector3 n1 = p.get_any_perpendicular_normal(); Vector3 n2 = p.normal.cross(n1).normalized(); Vector3 pface[4] = { p.normal * p.d + n1 * 10.0 + n2 * 10.0, p.normal * p.d + n1 * 10.0 + n2 * -10.0, p.normal * p.d + n1 * -10.0 + n2 * -10.0, p.normal * p.d + n1 * -10.0 + n2 * 10.0, }; Vector points = { pface[0], pface[1], pface[1], pface[2], pface[2], pface[3], pface[3], pface[0], p.normal * p.d, p.normal * p.d + p.normal * 3 }; p_gizmo->add_lines(points, material); p_gizmo->add_collision_segments(points); } if (Object::cast_to(*s)) { Vector points = Object::cast_to(*s)->get_points(); if (points.size() > 3) { Vector varr = Variant(points); Geometry3D::MeshData md; Error err = ConvexHullComputer::convex_hull(varr, md); if (err == OK) { Vector points2; points2.resize(md.edges.size() * 2); for (uint32_t i = 0; i < md.edges.size(); i++) { points2.write[i * 2 + 0] = md.vertices[md.edges[i].vertex_a]; points2.write[i * 2 + 1] = md.vertices[md.edges[i].vertex_b]; } p_gizmo->add_lines(points2, material); p_gizmo->add_collision_segments(points2); } } } if (Object::cast_to(*s)) { Ref cs2 = s; Ref mesh = cs2->get_debug_mesh(); p_gizmo->add_mesh(mesh, material); p_gizmo->add_collision_segments(cs2->get_debug_mesh_lines()); } if (Object::cast_to(*s)) { Ref rs = s; Vector points = { Vector3(), Vector3(0, 0, rs->get_length()) }; p_gizmo->add_lines(points, material); p_gizmo->add_collision_segments(points); Vector handles; handles.push_back(Vector3(0, 0, rs->get_length())); p_gizmo->add_handles(handles, handles_material); } if (Object::cast_to(*s)) { Ref hms = s; Ref mesh = hms->get_debug_mesh(); p_gizmo->add_mesh(mesh, material); } }