/**************************************************************************/ /* node_3d_editor_gizmos.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 "node_3d_editor_gizmos.h" #include "core/math/geometry_2d.h" #include "core/math/geometry_3d.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" #include "editor/editor_string_names.h" #include "editor/plugins/node_3d_editor_plugin.h" #include "scene/resources/primitive_meshes.h" #define HANDLE_HALF_SIZE 9.5 bool EditorNode3DGizmo::is_editable() const { ERR_FAIL_NULL_V(spatial_node, false); Node *edited_root = spatial_node->get_tree()->get_edited_scene_root(); if (spatial_node == edited_root) { return true; } if (spatial_node->get_owner() == edited_root) { return true; } if (edited_root->is_editable_instance(spatial_node->get_owner())) { return true; } return false; } void EditorNode3DGizmo::clear() { ERR_FAIL_NULL(RenderingServer::get_singleton()); for (int i = 0; i < instances.size(); i++) { if (instances[i].instance.is_valid()) { RS::get_singleton()->free(instances[i].instance); } } billboard_handle = false; collision_segments.clear(); collision_mesh = Ref(); instances.clear(); handles.clear(); handle_ids.clear(); secondary_handles.clear(); secondary_handle_ids.clear(); } void EditorNode3DGizmo::redraw() { if (!GDVIRTUAL_CALL(_redraw)) { ERR_FAIL_NULL(gizmo_plugin); gizmo_plugin->redraw(this); } if (Node3DEditor::get_singleton()->is_current_selected_gizmo(this)) { Node3DEditor::get_singleton()->update_transform_gizmo(); } } String EditorNode3DGizmo::get_handle_name(int p_id, bool p_secondary) const { String ret; if (GDVIRTUAL_CALL(_get_handle_name, p_id, p_secondary, ret)) { return ret; } ERR_FAIL_NULL_V(gizmo_plugin, ""); return gizmo_plugin->get_handle_name(this, p_id, p_secondary); } bool EditorNode3DGizmo::is_handle_highlighted(int p_id, bool p_secondary) const { bool success; if (GDVIRTUAL_CALL(_is_handle_highlighted, p_id, p_secondary, success)) { return success; } ERR_FAIL_NULL_V(gizmo_plugin, false); return gizmo_plugin->is_handle_highlighted(this, p_id, p_secondary); } Variant EditorNode3DGizmo::get_handle_value(int p_id, bool p_secondary) const { Variant value; if (GDVIRTUAL_CALL(_get_handle_value, p_id, p_secondary, value)) { return value; } ERR_FAIL_NULL_V(gizmo_plugin, Variant()); return gizmo_plugin->get_handle_value(this, p_id, p_secondary); } void EditorNode3DGizmo::begin_handle_action(int p_id, bool p_secondary) { if (GDVIRTUAL_CALL(_begin_handle_action, p_id, p_secondary)) { return; } ERR_FAIL_NULL(gizmo_plugin); gizmo_plugin->begin_handle_action(this, p_id, p_secondary); } void EditorNode3DGizmo::set_handle(int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { if (GDVIRTUAL_CALL(_set_handle, p_id, p_secondary, p_camera, p_point)) { return; } ERR_FAIL_NULL(gizmo_plugin); gizmo_plugin->set_handle(this, p_id, p_secondary, p_camera, p_point); } void EditorNode3DGizmo::commit_handle(int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { if (GDVIRTUAL_CALL(_commit_handle, p_id, p_secondary, p_restore, p_cancel)) { return; } ERR_FAIL_NULL(gizmo_plugin); gizmo_plugin->commit_handle(this, p_id, p_secondary, p_restore, p_cancel); } int EditorNode3DGizmo::subgizmos_intersect_ray(Camera3D *p_camera, const Vector2 &p_point) const { int id; if (GDVIRTUAL_CALL(_subgizmos_intersect_ray, p_camera, p_point, id)) { return id; } ERR_FAIL_NULL_V(gizmo_plugin, -1); return gizmo_plugin->subgizmos_intersect_ray(this, p_camera, p_point); } Vector EditorNode3DGizmo::subgizmos_intersect_frustum(const Camera3D *p_camera, const Vector &p_frustum) const { TypedArray frustum; frustum.resize(p_frustum.size()); for (int i = 0; i < p_frustum.size(); i++) { frustum[i] = p_frustum[i]; } Vector ret; if (GDVIRTUAL_CALL(_subgizmos_intersect_frustum, p_camera, frustum, ret)) { return ret; } ERR_FAIL_NULL_V(gizmo_plugin, Vector()); return gizmo_plugin->subgizmos_intersect_frustum(this, p_camera, p_frustum); } Transform3D EditorNode3DGizmo::get_subgizmo_transform(int p_id) const { Transform3D ret; if (GDVIRTUAL_CALL(_get_subgizmo_transform, p_id, ret)) { return ret; } ERR_FAIL_NULL_V(gizmo_plugin, Transform3D()); return gizmo_plugin->get_subgizmo_transform(this, p_id); } void EditorNode3DGizmo::set_subgizmo_transform(int p_id, Transform3D p_transform) { if (GDVIRTUAL_CALL(_set_subgizmo_transform, p_id, p_transform)) { return; } ERR_FAIL_NULL(gizmo_plugin); gizmo_plugin->set_subgizmo_transform(this, p_id, p_transform); } void EditorNode3DGizmo::commit_subgizmos(const Vector &p_ids, const Vector &p_restore, bool p_cancel) { TypedArray restore; restore.resize(p_restore.size()); for (int i = 0; i < p_restore.size(); i++) { restore[i] = p_restore[i]; } if (GDVIRTUAL_CALL(_commit_subgizmos, p_ids, restore, p_cancel)) { return; } ERR_FAIL_NULL(gizmo_plugin); gizmo_plugin->commit_subgizmos(this, p_ids, p_restore, p_cancel); } void EditorNode3DGizmo::set_node_3d(Node3D *p_node) { ERR_FAIL_NULL(p_node); spatial_node = p_node; } void EditorNode3DGizmo::Instance::create_instance(Node3D *p_base, bool p_hidden) { instance = RS::get_singleton()->instance_create2(mesh->get_rid(), p_base->get_world_3d()->get_scenario()); RS::get_singleton()->instance_attach_object_instance_id(instance, p_base->get_instance_id()); if (skin_reference.is_valid()) { RS::get_singleton()->instance_attach_skeleton(instance, skin_reference->get_skeleton()); } if (extra_margin) { RS::get_singleton()->instance_set_extra_visibility_margin(instance, 1); } RS::get_singleton()->instance_geometry_set_cast_shadows_setting(instance, RS::SHADOW_CASTING_SETTING_OFF); int layer = p_hidden ? 0 : 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER; RS::get_singleton()->instance_set_layer_mask(instance, layer); //gizmos are 26 RS::get_singleton()->instance_geometry_set_flag(instance, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); RS::get_singleton()->instance_geometry_set_flag(instance, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false); } void EditorNode3DGizmo::add_mesh(const Ref &p_mesh, const Ref &p_material, const Transform3D &p_xform, const Ref &p_skin_reference) { ERR_FAIL_NULL(spatial_node); ERR_FAIL_COND_MSG(!p_mesh.is_valid(), "EditorNode3DGizmo.add_mesh() requires a valid Mesh resource."); Instance ins; ins.mesh = p_mesh; ins.skin_reference = p_skin_reference; ins.material = p_material; ins.xform = p_xform; if (valid) { ins.create_instance(spatial_node, hidden); RS::get_singleton()->instance_set_transform(ins.instance, spatial_node->get_global_transform() * ins.xform); if (ins.material.is_valid()) { RS::get_singleton()->instance_geometry_set_material_override(ins.instance, p_material->get_rid()); } } instances.push_back(ins); } void EditorNode3DGizmo::add_lines(const Vector &p_lines, const Ref &p_material, bool p_billboard, const Color &p_modulate) { add_vertices(p_lines, p_material, Mesh::PRIMITIVE_LINES, p_billboard, p_modulate); } void EditorNode3DGizmo::add_vertices(const Vector &p_vertices, const Ref &p_material, Mesh::PrimitiveType p_primitive_type, bool p_billboard, const Color &p_modulate) { if (p_vertices.is_empty()) { return; } ERR_FAIL_NULL(spatial_node); Instance ins; Ref mesh = memnew(ArrayMesh); Array a; a.resize(Mesh::ARRAY_MAX); a[Mesh::ARRAY_VERTEX] = p_vertices; Vector color; color.resize(p_vertices.size()); { Color *w = color.ptrw(); for (int i = 0; i < p_vertices.size(); i++) { if (is_selected()) { w[i] = Color(1, 1, 1, 0.8) * p_modulate; } else { w[i] = Color(1, 1, 1, 0.2) * p_modulate; } } } a[Mesh::ARRAY_COLOR] = color; 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_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)); } } ins.mesh = mesh; if (valid) { ins.create_instance(spatial_node, hidden); RS::get_singleton()->instance_set_transform(ins.instance, spatial_node->get_global_transform()); } instances.push_back(ins); } void EditorNode3DGizmo::add_unscaled_billboard(const Ref &p_material, real_t p_scale, const Color &p_modulate) { ERR_FAIL_NULL(spatial_node); Instance ins; Vector vs = { Vector3(-p_scale, p_scale, 0), Vector3(p_scale, p_scale, 0), Vector3(p_scale, -p_scale, 0), Vector3(-p_scale, -p_scale, 0) }; Vector uv = { Vector2(0, 0), Vector2(1, 0), Vector2(1, 1), Vector2(0, 1) }; Vector colors = { p_modulate, p_modulate, p_modulate, p_modulate }; Vector indices = { 0, 1, 2, 0, 2, 3 }; Ref mesh = memnew(ArrayMesh); Array a; a.resize(Mesh::ARRAY_MAX); a[Mesh::ARRAY_VERTEX] = vs; a[Mesh::ARRAY_TEX_UV] = uv; a[Mesh::ARRAY_INDEX] = indices; a[Mesh::ARRAY_COLOR] = colors; mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, a); mesh->surface_set_material(0, p_material); float md = 0; for (int i = 0; i < vs.size(); i++) { md = MAX(0, vs[i].length()); } if (md) { mesh->set_custom_aabb(AABB(Vector3(-md, -md, -md), Vector3(md, md, md) * 2.0)); } selectable_icon_size = p_scale; mesh->set_custom_aabb(AABB(Vector3(-selectable_icon_size, -selectable_icon_size, -selectable_icon_size) * 100.0f, Vector3(selectable_icon_size, selectable_icon_size, selectable_icon_size) * 200.0f)); ins.mesh = mesh; if (valid) { ins.create_instance(spatial_node, hidden); RS::get_singleton()->instance_set_transform(ins.instance, spatial_node->get_global_transform()); } selectable_icon_size = p_scale; instances.push_back(ins); } void EditorNode3DGizmo::add_collision_triangles(const Ref &p_tmesh) { collision_mesh = p_tmesh; } void EditorNode3DGizmo::add_collision_segments(const Vector &p_lines) { int from = collision_segments.size(); collision_segments.resize(from + p_lines.size()); for (int i = 0; i < p_lines.size(); i++) { collision_segments.write[from + i] = p_lines[i]; } } void EditorNode3DGizmo::add_handles(const Vector &p_handles, const Ref &p_material, const Vector &p_ids, bool p_billboard, bool p_secondary) { billboard_handle = p_billboard; if (!is_selected() || !is_editable()) { return; } ERR_FAIL_NULL(spatial_node); Vector &handle_list = p_secondary ? secondary_handles : handles; Vector &id_list = p_secondary ? secondary_handle_ids : handle_ids; if (p_ids.is_empty()) { ERR_FAIL_COND_MSG(!id_list.is_empty(), "IDs must be provided for all handles, as handles with IDs already exist."); } else { ERR_FAIL_COND_MSG(p_handles.size() != p_ids.size(), "The number of IDs should be the same as the number of handles."); } bool is_current_hover_gizmo = Node3DEditor::get_singleton()->get_current_hover_gizmo() == this; bool current_hover_handle_secondary; int current_hover_handle = Node3DEditor::get_singleton()->get_current_hover_gizmo_handle(current_hover_handle_secondary); Instance ins; Ref mesh = memnew(ArrayMesh); Array a; a.resize(RS::ARRAY_MAX); a[RS::ARRAY_VERTEX] = p_handles; Vector colors; { colors.resize(p_handles.size()); Color *w = colors.ptrw(); for (int i = 0; i < p_handles.size(); i++) { Color col(1, 1, 1, 1); if (is_handle_highlighted(i, p_secondary)) { col = Color(0, 0, 1, 0.9); } int id = p_ids.is_empty() ? i : p_ids[i]; if (!is_current_hover_gizmo || current_hover_handle != id || p_secondary != current_hover_handle_secondary) { col.a = 0.8; } w[i] = col; } } a[RS::ARRAY_COLOR] = colors; mesh->add_surface_from_arrays(Mesh::PRIMITIVE_POINTS, a); mesh->surface_set_material(0, p_material); if (p_billboard) { float md = 0; for (int i = 0; i < p_handles.size(); i++) { md = MAX(0, p_handles[i].length()); } if (md) { mesh->set_custom_aabb(AABB(Vector3(-md, -md, -md), Vector3(md, md, md) * 2.0)); } } ins.mesh = mesh; ins.extra_margin = true; if (valid) { ins.create_instance(spatial_node, hidden); RS::get_singleton()->instance_set_transform(ins.instance, spatial_node->get_global_transform()); } instances.push_back(ins); int current_size = handle_list.size(); handle_list.resize(current_size + p_handles.size()); for (int i = 0; i < p_handles.size(); i++) { handle_list.write[current_size + i] = p_handles[i]; } if (!p_ids.is_empty()) { current_size = id_list.size(); id_list.resize(current_size + p_ids.size()); for (int i = 0; i < p_ids.size(); i++) { id_list.write[current_size + i] = p_ids[i]; } } } void EditorNode3DGizmo::add_solid_box(const Ref &p_material, Vector3 p_size, Vector3 p_position, const Transform3D &p_xform) { ERR_FAIL_NULL(spatial_node); BoxMesh box_mesh; box_mesh.set_size(p_size); Array arrays = box_mesh.surface_get_arrays(0); PackedVector3Array vertex = arrays[RS::ARRAY_VERTEX]; Vector3 *w = vertex.ptrw(); for (int i = 0; i < vertex.size(); ++i) { w[i] += p_position; } arrays[RS::ARRAY_VERTEX] = vertex; Ref m = memnew(ArrayMesh); m->add_surface_from_arrays(box_mesh.surface_get_primitive_type(0), arrays); add_mesh(m, p_material, p_xform); } bool EditorNode3DGizmo::intersect_frustum(const Camera3D *p_camera, const Vector &p_frustum) { ERR_FAIL_NULL_V(spatial_node, false); ERR_FAIL_COND_V(!valid, false); if (hidden && !gizmo_plugin->is_selectable_when_hidden()) { return false; } if (selectable_icon_size > 0.0f) { Vector3 origin = spatial_node->get_global_transform().get_origin(); const Plane *p = p_frustum.ptr(); int fc = p_frustum.size(); bool any_out = false; for (int j = 0; j < fc; j++) { if (p[j].is_point_over(origin)) { any_out = true; break; } } return !any_out; } if (collision_segments.size()) { const Plane *p = p_frustum.ptr(); int fc = p_frustum.size(); int vc = collision_segments.size(); const Vector3 *vptr = collision_segments.ptr(); Transform3D t = spatial_node->get_global_transform(); bool any_out = false; for (int j = 0; j < fc; j++) { for (int i = 0; i < vc; i++) { Vector3 v = t.xform(vptr[i]); if (p[j].is_point_over(v)) { any_out = true; break; } } if (any_out) { break; } } if (!any_out) { return true; } } if (collision_mesh.is_valid()) { Transform3D t = spatial_node->get_global_transform(); Vector3 mesh_scale = t.get_basis().get_scale(); t.orthonormalize(); Transform3D it = t.affine_inverse(); Vector transformed_frustum; int plane_count = p_frustum.size(); transformed_frustum.resize(plane_count); for (int i = 0; i < plane_count; i++) { transformed_frustum.write[i] = it.xform(p_frustum[i]); } Vector convex_points = Geometry3D::compute_convex_mesh_points(transformed_frustum.ptr(), plane_count); if (collision_mesh->inside_convex_shape(transformed_frustum.ptr(), plane_count, convex_points.ptr(), convex_points.size(), mesh_scale)) { return true; } } return false; } void EditorNode3DGizmo::handles_intersect_ray(Camera3D *p_camera, const Vector2 &p_point, bool p_shift_pressed, int &r_id, bool &r_secondary) { r_id = -1; r_secondary = false; ERR_FAIL_NULL(spatial_node); ERR_FAIL_COND(!valid); if (hidden) { return; } Transform3D camera_xform = p_camera->get_global_transform(); Transform3D t = spatial_node->get_global_transform(); if (billboard_handle) { t.set_look_at(t.origin, t.origin - camera_xform.basis.get_column(2), camera_xform.basis.get_column(1)); } float min_d = 1e20; for (int i = 0; i < secondary_handles.size(); i++) { Vector3 hpos = t.xform(secondary_handles[i]); Vector2 p = p_camera->unproject_position(hpos); if (p.distance_to(p_point) < HANDLE_HALF_SIZE) { real_t dp = p_camera->get_transform().origin.distance_to(hpos); if (dp < min_d) { min_d = dp; if (secondary_handle_ids.is_empty()) { r_id = i; } else { r_id = secondary_handle_ids[i]; } r_secondary = true; } } } if (r_id != -1 && p_shift_pressed) { return; } min_d = 1e20; for (int i = 0; i < handles.size(); i++) { Vector3 hpos = t.xform(handles[i]); Vector2 p = p_camera->unproject_position(hpos); if (p.distance_to(p_point) < HANDLE_HALF_SIZE) { real_t dp = p_camera->get_transform().origin.distance_to(hpos); if (dp < min_d) { min_d = dp; if (handle_ids.is_empty()) { r_id = i; } else { r_id = handle_ids[i]; } r_secondary = false; } } } } bool EditorNode3DGizmo::intersect_ray(Camera3D *p_camera, const Point2 &p_point, Vector3 &r_pos, Vector3 &r_normal) { ERR_FAIL_NULL_V(spatial_node, false); ERR_FAIL_COND_V(!valid, false); if (hidden && !gizmo_plugin->is_selectable_when_hidden()) { return false; } if (selectable_icon_size > 0.0f) { Transform3D t = spatial_node->get_global_transform(); Vector3 camera_position = p_camera->get_camera_transform().origin; if (!camera_position.is_equal_approx(t.origin)) { t.set_look_at(t.origin, camera_position); } float scale = t.origin.distance_to(p_camera->get_camera_transform().origin); if (p_camera->get_projection() == Camera3D::PROJECTION_ORTHOGONAL) { float aspect = p_camera->get_viewport()->get_visible_rect().size.aspect(); float size = p_camera->get_size(); scale = size / aspect; } Point2 center = p_camera->unproject_position(t.origin); Transform3D orig_camera_transform = p_camera->get_camera_transform(); if (!orig_camera_transform.origin.is_equal_approx(t.origin) && ABS(orig_camera_transform.basis.get_column(Vector3::AXIS_Z).dot(Vector3(0, 1, 0))) < 0.99) { p_camera->look_at(t.origin); } Vector3 c0 = t.xform(Vector3(selectable_icon_size, selectable_icon_size, 0) * scale); Vector3 c1 = t.xform(Vector3(-selectable_icon_size, -selectable_icon_size, 0) * scale); Point2 p0 = p_camera->unproject_position(c0); Point2 p1 = p_camera->unproject_position(c1); p_camera->set_global_transform(orig_camera_transform); Rect2 rect(p0, (p1 - p0).abs()); rect.set_position(center - rect.get_size() / 2.0); if (rect.has_point(p_point)) { r_pos = t.origin; r_normal = -p_camera->project_ray_normal(p_point); return true; } } if (collision_segments.size()) { Plane camp(-p_camera->get_transform().basis.get_column(2).normalized(), p_camera->get_transform().origin); int vc = collision_segments.size(); const Vector3 *vptr = collision_segments.ptr(); Transform3D t = spatial_node->get_global_transform(); if (billboard_handle) { t.set_look_at(t.origin, t.origin - p_camera->get_transform().basis.get_column(2), p_camera->get_transform().basis.get_column(1)); } Vector3 cp; float cpd = 1e20; for (int i = 0; i < vc / 2; i++) { Vector3 a = t.xform(vptr[i * 2 + 0]); Vector3 b = t.xform(vptr[i * 2 + 1]); Vector2 s[2]; s[0] = p_camera->unproject_position(a); s[1] = p_camera->unproject_position(b); Vector2 p = Geometry2D::get_closest_point_to_segment(p_point, s); float pd = p.distance_to(p_point); if (pd < cpd) { float d = s[0].distance_to(s[1]); Vector3 tcp; if (d > 0) { float d2 = s[0].distance_to(p) / d; tcp = a + (b - a) * d2; } else { tcp = a; } if (camp.distance_to(tcp) < p_camera->get_near()) { continue; } cp = tcp; cpd = pd; } } if (cpd < 8) { r_pos = cp; r_normal = -p_camera->project_ray_normal(p_point); return true; } } if (collision_mesh.is_valid()) { Transform3D gt = spatial_node->get_global_transform(); if (billboard_handle) { gt.set_look_at(gt.origin, gt.origin - p_camera->get_transform().basis.get_column(2), p_camera->get_transform().basis.get_column(1)); } Transform3D ai = gt.affine_inverse(); Vector3 ray_from = ai.xform(p_camera->project_ray_origin(p_point)); Vector3 ray_dir = ai.basis.xform(p_camera->project_ray_normal(p_point)).normalized(); Vector3 rpos, rnorm; if (collision_mesh->intersect_ray(ray_from, ray_dir, rpos, rnorm)) { r_pos = gt.xform(rpos); r_normal = gt.basis.xform(rnorm).normalized(); return true; } } return false; } bool EditorNode3DGizmo::is_subgizmo_selected(int p_id) const { Node3DEditor *ed = Node3DEditor::get_singleton(); ERR_FAIL_NULL_V(ed, false); return ed->is_current_selected_gizmo(this) && ed->is_subgizmo_selected(p_id); } Vector EditorNode3DGizmo::get_subgizmo_selection() const { Vector ret; Node3DEditor *ed = Node3DEditor::get_singleton(); ERR_FAIL_NULL_V(ed, ret); if (ed->is_current_selected_gizmo(this)) { ret = ed->get_subgizmo_selection(); } return ret; } void EditorNode3DGizmo::create() { ERR_FAIL_NULL(spatial_node); ERR_FAIL_COND(valid); valid = true; for (int i = 0; i < instances.size(); i++) { instances.write[i].create_instance(spatial_node, hidden); } transform(); } void EditorNode3DGizmo::transform() { ERR_FAIL_NULL(spatial_node); ERR_FAIL_COND(!valid); for (int i = 0; i < instances.size(); i++) { RS::get_singleton()->instance_set_transform(instances[i].instance, spatial_node->get_global_transform() * instances[i].xform); } } void EditorNode3DGizmo::free() { ERR_FAIL_NULL(RenderingServer::get_singleton()); ERR_FAIL_NULL(spatial_node); ERR_FAIL_COND(!valid); for (int i = 0; i < instances.size(); i++) { if (instances[i].instance.is_valid()) { RS::get_singleton()->free(instances[i].instance); } instances.write[i].instance = RID(); } clear(); valid = false; } void EditorNode3DGizmo::set_hidden(bool p_hidden) { hidden = p_hidden; int layer = hidden ? 0 : 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER; for (int i = 0; i < instances.size(); ++i) { RS::get_singleton()->instance_set_layer_mask(instances[i].instance, layer); } } void EditorNode3DGizmo::set_plugin(EditorNode3DGizmoPlugin *p_plugin) { gizmo_plugin = p_plugin; } void EditorNode3DGizmo::_bind_methods() { ClassDB::bind_method(D_METHOD("add_lines", "lines", "material", "billboard", "modulate"), &EditorNode3DGizmo::add_lines, DEFVAL(false), DEFVAL(Color(1, 1, 1))); ClassDB::bind_method(D_METHOD("add_mesh", "mesh", "material", "transform", "skeleton"), &EditorNode3DGizmo::add_mesh, DEFVAL(Variant()), DEFVAL(Transform3D()), DEFVAL(Ref())); ClassDB::bind_method(D_METHOD("add_collision_segments", "segments"), &EditorNode3DGizmo::add_collision_segments); ClassDB::bind_method(D_METHOD("add_collision_triangles", "triangles"), &EditorNode3DGizmo::add_collision_triangles); ClassDB::bind_method(D_METHOD("add_unscaled_billboard", "material", "default_scale", "modulate"), &EditorNode3DGizmo::add_unscaled_billboard, DEFVAL(1), DEFVAL(Color(1, 1, 1))); ClassDB::bind_method(D_METHOD("add_handles", "handles", "material", "ids", "billboard", "secondary"), &EditorNode3DGizmo::add_handles, DEFVAL(false), DEFVAL(false)); ClassDB::bind_method(D_METHOD("set_node_3d", "node"), &EditorNode3DGizmo::_set_node_3d); ClassDB::bind_method(D_METHOD("get_node_3d"), &EditorNode3DGizmo::get_node_3d); ClassDB::bind_method(D_METHOD("get_plugin"), &EditorNode3DGizmo::get_plugin); ClassDB::bind_method(D_METHOD("clear"), &EditorNode3DGizmo::clear); ClassDB::bind_method(D_METHOD("set_hidden", "hidden"), &EditorNode3DGizmo::set_hidden); ClassDB::bind_method(D_METHOD("is_subgizmo_selected", "id"), &EditorNode3DGizmo::is_subgizmo_selected); ClassDB::bind_method(D_METHOD("get_subgizmo_selection"), &EditorNode3DGizmo::get_subgizmo_selection); GDVIRTUAL_BIND(_redraw); GDVIRTUAL_BIND(_get_handle_name, "id", "secondary"); GDVIRTUAL_BIND(_is_handle_highlighted, "id", "secondary"); GDVIRTUAL_BIND(_get_handle_value, "id", "secondary"); GDVIRTUAL_BIND(_set_handle, "id", "secondary", "camera", "point"); GDVIRTUAL_BIND(_commit_handle, "id", "secondary", "restore", "cancel"); GDVIRTUAL_BIND(_subgizmos_intersect_ray, "camera", "point"); GDVIRTUAL_BIND(_subgizmos_intersect_frustum, "camera", "frustum"); GDVIRTUAL_BIND(_set_subgizmo_transform, "id", "transform"); GDVIRTUAL_BIND(_get_subgizmo_transform, "id"); GDVIRTUAL_BIND(_commit_subgizmos, "ids", "restores", "cancel"); } EditorNode3DGizmo::EditorNode3DGizmo() { valid = false; billboard_handle = false; hidden = false; selected = false; spatial_node = nullptr; gizmo_plugin = nullptr; selectable_icon_size = -1.0f; } EditorNode3DGizmo::~EditorNode3DGizmo() { if (gizmo_plugin != nullptr) { gizmo_plugin->unregister_gizmo(this); } clear(); } ///// void EditorNode3DGizmoPlugin::create_material(const String &p_name, const Color &p_color, bool p_billboard, bool p_on_top, bool p_use_vertex_color) { Color instantiated_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/instantiated"); Vector> mats; for (int i = 0; i < 4; i++) { bool selected = i % 2 == 1; bool instantiated = i < 2; Ref material = Ref(memnew(StandardMaterial3D)); Color color = instantiated ? instantiated_color : p_color; if (!selected) { color.a *= 0.3; } material->set_albedo(color); material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); material->set_render_priority(StandardMaterial3D::RENDER_PRIORITY_MIN + 1); material->set_cull_mode(StandardMaterial3D::CULL_DISABLED); if (p_use_vertex_color) { material->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); material->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); } if (p_billboard) { material->set_billboard_mode(StandardMaterial3D::BILLBOARD_ENABLED); } if (p_on_top && selected) { material->set_on_top_of_alpha(); } mats.push_back(material); } materials[p_name] = mats; } void EditorNode3DGizmoPlugin::create_icon_material(const String &p_name, const Ref &p_texture, bool p_on_top, const Color &p_albedo) { Color instantiated_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/instantiated"); Vector> icons; for (int i = 0; i < 4; i++) { bool selected = i % 2 == 1; bool instantiated = i < 2; Ref icon = Ref(memnew(StandardMaterial3D)); Color color = instantiated ? instantiated_color : p_albedo; if (!selected) { color.a *= 0.85; } icon->set_albedo(color); icon->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); icon->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); icon->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); icon->set_cull_mode(StandardMaterial3D::CULL_DISABLED); icon->set_depth_draw_mode(StandardMaterial3D::DEPTH_DRAW_DISABLED); icon->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); icon->set_texture(StandardMaterial3D::TEXTURE_ALBEDO, p_texture); icon->set_flag(StandardMaterial3D::FLAG_FIXED_SIZE, true); icon->set_billboard_mode(StandardMaterial3D::BILLBOARD_ENABLED); icon->set_render_priority(StandardMaterial3D::RENDER_PRIORITY_MIN); if (p_on_top && selected) { icon->set_on_top_of_alpha(); } icons.push_back(icon); } materials[p_name] = icons; } void EditorNode3DGizmoPlugin::create_handle_material(const String &p_name, bool p_billboard, const Ref &p_icon) { Ref handle_material = Ref(memnew(StandardMaterial3D)); handle_material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); handle_material->set_flag(StandardMaterial3D::FLAG_USE_POINT_SIZE, true); Ref handle_t = p_icon != nullptr ? p_icon : EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("Editor3DHandle"), EditorStringName(EditorIcons)); handle_material->set_point_size(handle_t->get_width()); handle_material->set_texture(StandardMaterial3D::TEXTURE_ALBEDO, handle_t); handle_material->set_albedo(Color(1, 1, 1)); handle_material->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); handle_material->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); handle_material->set_on_top_of_alpha(); if (p_billboard) { handle_material->set_billboard_mode(StandardMaterial3D::BILLBOARD_ENABLED); handle_material->set_on_top_of_alpha(); } handle_material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); materials[p_name] = Vector>(); materials[p_name].push_back(handle_material); } void EditorNode3DGizmoPlugin::add_material(const String &p_name, Ref p_material) { materials[p_name] = Vector>(); materials[p_name].push_back(p_material); } Ref EditorNode3DGizmoPlugin::get_material(const String &p_name, const Ref &p_gizmo) { ERR_FAIL_COND_V(!materials.has(p_name), Ref()); ERR_FAIL_COND_V(materials[p_name].size() == 0, Ref()); if (p_gizmo.is_null() || materials[p_name].size() == 1) { return materials[p_name][0]; } int index = (p_gizmo->is_selected() ? 1 : 0) + (p_gizmo->is_editable() ? 2 : 0); Ref mat = materials[p_name][index]; if (current_state == ON_TOP && p_gizmo->is_selected()) { mat->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, true); } else { mat->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, false); } return mat; } String EditorNode3DGizmoPlugin::get_gizmo_name() const { String ret; if (GDVIRTUAL_CALL(_get_gizmo_name, ret)) { return ret; } WARN_PRINT_ONCE("A 3D editor gizmo has no name defined (it will appear as \"Unnamed Gizmo\" in the \"View > Gizmos\" menu). To resolve this, override the `_get_gizmo_name()` function to return a String in the script that extends EditorNode3DGizmoPlugin."); return TTR("Unnamed Gizmo"); } int EditorNode3DGizmoPlugin::get_priority() const { int ret; if (GDVIRTUAL_CALL(_get_priority, ret)) { return ret; } return 0; } Ref EditorNode3DGizmoPlugin::get_gizmo(Node3D *p_spatial) { if (get_script_instance() && get_script_instance()->has_method("_get_gizmo")) { return get_script_instance()->call("_get_gizmo", p_spatial); } Ref ref = create_gizmo(p_spatial); if (ref.is_null()) { return ref; } ref->set_plugin(this); ref->set_node_3d(p_spatial); ref->set_hidden(current_state == HIDDEN); current_gizmos.push_back(ref.ptr()); return ref; } void EditorNode3DGizmoPlugin::_bind_methods() { ClassDB::bind_method(D_METHOD("create_material", "name", "color", "billboard", "on_top", "use_vertex_color"), &EditorNode3DGizmoPlugin::create_material, DEFVAL(false), DEFVAL(false), DEFVAL(false)); ClassDB::bind_method(D_METHOD("create_icon_material", "name", "texture", "on_top", "color"), &EditorNode3DGizmoPlugin::create_icon_material, DEFVAL(false), DEFVAL(Color(1, 1, 1, 1))); ClassDB::bind_method(D_METHOD("create_handle_material", "name", "billboard", "texture"), &EditorNode3DGizmoPlugin::create_handle_material, DEFVAL(false), DEFVAL(Variant())); ClassDB::bind_method(D_METHOD("add_material", "name", "material"), &EditorNode3DGizmoPlugin::add_material); ClassDB::bind_method(D_METHOD("get_material", "name", "gizmo"), &EditorNode3DGizmoPlugin::get_material, DEFVAL(Ref())); GDVIRTUAL_BIND(_has_gizmo, "for_node_3d"); GDVIRTUAL_BIND(_create_gizmo, "for_node_3d"); GDVIRTUAL_BIND(_get_gizmo_name); GDVIRTUAL_BIND(_get_priority); GDVIRTUAL_BIND(_can_be_hidden); GDVIRTUAL_BIND(_is_selectable_when_hidden); GDVIRTUAL_BIND(_redraw, "gizmo"); GDVIRTUAL_BIND(_get_handle_name, "gizmo", "handle_id", "secondary"); GDVIRTUAL_BIND(_is_handle_highlighted, "gizmo", "handle_id", "secondary"); GDVIRTUAL_BIND(_get_handle_value, "gizmo", "handle_id", "secondary"); GDVIRTUAL_BIND(_set_handle, "gizmo", "handle_id", "secondary", "camera", "screen_pos"); GDVIRTUAL_BIND(_commit_handle, "gizmo", "handle_id", "secondary", "restore", "cancel"); GDVIRTUAL_BIND(_subgizmos_intersect_ray, "gizmo", "camera", "screen_pos"); GDVIRTUAL_BIND(_subgizmos_intersect_frustum, "gizmo", "camera", "frustum_planes"); GDVIRTUAL_BIND(_get_subgizmo_transform, "gizmo", "subgizmo_id"); GDVIRTUAL_BIND(_set_subgizmo_transform, "gizmo", "subgizmo_id", "transform"); GDVIRTUAL_BIND(_commit_subgizmos, "gizmo", "ids", "restores", "cancel"); } bool EditorNode3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { bool success = false; GDVIRTUAL_CALL(_has_gizmo, p_spatial, success); return success; } Ref EditorNode3DGizmoPlugin::create_gizmo(Node3D *p_spatial) { Ref ret; if (GDVIRTUAL_CALL(_create_gizmo, p_spatial, ret)) { return ret; } Ref ref; if (has_gizmo(p_spatial)) { ref.instantiate(); } return ref; } bool EditorNode3DGizmoPlugin::can_be_hidden() const { bool ret = true; GDVIRTUAL_CALL(_can_be_hidden, ret); return ret; } bool EditorNode3DGizmoPlugin::is_selectable_when_hidden() const { bool ret = false; GDVIRTUAL_CALL(_is_selectable_when_hidden, ret); return ret; } void EditorNode3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { GDVIRTUAL_CALL(_redraw, p_gizmo); } bool EditorNode3DGizmoPlugin::is_handle_highlighted(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { bool ret = false; GDVIRTUAL_CALL(_is_handle_highlighted, Ref(p_gizmo), p_id, p_secondary, ret); return ret; } String EditorNode3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { String ret; GDVIRTUAL_CALL(_get_handle_name, Ref(p_gizmo), p_id, p_secondary, ret); return ret; } Variant EditorNode3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { Variant ret; GDVIRTUAL_CALL(_get_handle_value, Ref(p_gizmo), p_id, p_secondary, ret); return ret; } void EditorNode3DGizmoPlugin::begin_handle_action(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) { GDVIRTUAL_CALL(_begin_handle_action, Ref(p_gizmo), p_id, p_secondary); } void EditorNode3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { GDVIRTUAL_CALL(_set_handle, Ref(p_gizmo), p_id, p_secondary, p_camera, p_point); } void EditorNode3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { GDVIRTUAL_CALL(_commit_handle, Ref(p_gizmo), p_id, p_secondary, p_restore, p_cancel); } int EditorNode3DGizmoPlugin::subgizmos_intersect_ray(const EditorNode3DGizmo *p_gizmo, Camera3D *p_camera, const Vector2 &p_point) const { int ret = -1; GDVIRTUAL_CALL(_subgizmos_intersect_ray, Ref(p_gizmo), p_camera, p_point, ret); return ret; } Vector EditorNode3DGizmoPlugin::subgizmos_intersect_frustum(const EditorNode3DGizmo *p_gizmo, const Camera3D *p_camera, const Vector &p_frustum) const { TypedArray frustum; frustum.resize(p_frustum.size()); for (int i = 0; i < p_frustum.size(); i++) { frustum[i] = p_frustum[i]; } Vector ret; GDVIRTUAL_CALL(_subgizmos_intersect_frustum, Ref(p_gizmo), p_camera, frustum, ret); return ret; } Transform3D EditorNode3DGizmoPlugin::get_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id) const { Transform3D ret; GDVIRTUAL_CALL(_get_subgizmo_transform, Ref(p_gizmo), p_id, ret); return ret; } void EditorNode3DGizmoPlugin::set_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id, Transform3D p_transform) { GDVIRTUAL_CALL(_set_subgizmo_transform, Ref(p_gizmo), p_id, p_transform); } void EditorNode3DGizmoPlugin::commit_subgizmos(const EditorNode3DGizmo *p_gizmo, const Vector &p_ids, const Vector &p_restore, bool p_cancel) { TypedArray restore; restore.resize(p_restore.size()); for (int i = 0; i < p_restore.size(); i++) { restore[i] = p_restore[i]; } GDVIRTUAL_CALL(_commit_subgizmos, Ref(p_gizmo), p_ids, restore, p_cancel); } void EditorNode3DGizmoPlugin::set_state(int p_state) { current_state = p_state; for (int i = 0; i < current_gizmos.size(); ++i) { current_gizmos[i]->set_hidden(current_state == HIDDEN); } } int EditorNode3DGizmoPlugin::get_state() const { return current_state; } void EditorNode3DGizmoPlugin::unregister_gizmo(EditorNode3DGizmo *p_gizmo) { current_gizmos.erase(p_gizmo); } EditorNode3DGizmoPlugin::EditorNode3DGizmoPlugin() { current_state = VISIBLE; } EditorNode3DGizmoPlugin::~EditorNode3DGizmoPlugin() { for (int i = 0; i < current_gizmos.size(); ++i) { current_gizmos[i]->set_plugin(nullptr); current_gizmos[i]->get_node_3d()->remove_gizmo(current_gizmos[i]); } if (Node3DEditor::get_singleton()) { Node3DEditor::get_singleton()->update_all_gizmos(); } } //////