summaryrefslogtreecommitdiff
path: root/editor/plugins
diff options
context:
space:
mode:
authorjfons <joan.fonssanchez@gmail.com>2021-06-23 16:49:50 +0200
committerjfons <joan.fonssanchez@gmail.com>2021-07-23 21:01:10 +0200
commitcfb555a08175c811ea06a43ea320b81a2c90554a (patch)
tree544307bc3af847c392951553e247ee3e50fccb75 /editor/plugins
parent88bf6e1c6d3e711976304bb16ac51719e8e82f72 (diff)
Node3D gizmo improvements
* Clean-up of node_3d_editor_plugin.{h,cpp}: removed unused code, fixed some bugs. * Moved node_3d_editor_gizmos.{h,cpp} to editor/plugins. * Added support for multiple gizmos per node. This means custom gizmos will no longer override the built-in ones and that multiple gizmos can be used in more complex nodes. * Added support for handle IDs. When adding handles to a gizmo, an ID can be specified for each one, making it easier to work with gizmos that have a variable number of handles. * Added support for subgizmos, selectable elements that can be transformed without needing a node of their own. By overriding _subgizmo_intersect_frustum() and/or _subgizmo_intersect_ray() gizmos can define which subgizmos should be selected on a region or click selection. Subgizmo transformations are applied using get/set/commit virtual methods, similar to how handles work.
Diffstat (limited to 'editor/plugins')
-rw-r--r--editor/plugins/node_3d_editor_gizmos.cpp5477
-rw-r--r--editor/plugins/node_3d_editor_gizmos.h635
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp1288
-rw-r--r--editor/plugins/node_3d_editor_plugin.h177
-rw-r--r--editor/plugins/path_3d_editor_plugin.cpp56
-rw-r--r--editor/plugins/path_3d_editor_plugin.h10
6 files changed, 6792 insertions, 851 deletions
diff --git a/editor/plugins/node_3d_editor_gizmos.cpp b/editor/plugins/node_3d_editor_gizmos.cpp
new file mode 100644
index 0000000000..492ba51b8a
--- /dev/null
+++ b/editor/plugins/node_3d_editor_gizmos.cpp
@@ -0,0 +1,5477 @@
+/*************************************************************************/
+/* node_3d_editor_gizmos.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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/convex_hull.h"
+#include "core/math/geometry_2d.h"
+#include "core/math/geometry_3d.h"
+#include "editor/plugins/node_3d_editor_plugin.h"
+#include "scene/3d/audio_stream_player_3d.h"
+#include "scene/3d/camera_3d.h"
+#include "scene/3d/collision_polygon_3d.h"
+#include "scene/3d/collision_shape_3d.h"
+#include "scene/3d/cpu_particles_3d.h"
+#include "scene/3d/decal.h"
+#include "scene/3d/gpu_particles_3d.h"
+#include "scene/3d/gpu_particles_collision_3d.h"
+#include "scene/3d/light_3d.h"
+#include "scene/3d/lightmap_gi.h"
+#include "scene/3d/lightmap_probe.h"
+#include "scene/3d/listener_3d.h"
+#include "scene/3d/mesh_instance_3d.h"
+#include "scene/3d/navigation_region_3d.h"
+#include "scene/3d/occluder_instance_3d.h"
+#include "scene/3d/physics_joint_3d.h"
+#include "scene/3d/position_3d.h"
+#include "scene/3d/ray_cast_3d.h"
+#include "scene/3d/reflection_probe.h"
+#include "scene/3d/soft_body_3d.h"
+#include "scene/3d/spring_arm_3d.h"
+#include "scene/3d/sprite_3d.h"
+#include "scene/3d/vehicle_body_3d.h"
+#include "scene/3d/visible_on_screen_notifier_3d.h"
+#include "scene/3d/voxel_gi.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/primitive_meshes.h"
+#include "scene/resources/ray_shape_3d.h"
+#include "scene/resources/sphere_shape_3d.h"
+#include "scene/resources/surface_tool.h"
+#include "scene/resources/world_margin_shape_3d.h"
+
+#define HANDLE_HALF_SIZE 9.5
+
+bool EditorNode3DGizmo::is_editable() const {
+ ERR_FAIL_COND_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() {
+ 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<TriangleMesh>();
+ instances.clear();
+ handles.clear();
+ secondary_handles.clear();
+}
+
+void EditorNode3DGizmo::redraw() {
+ if (get_script_instance() && get_script_instance()->has_method("_redraw")) {
+ get_script_instance()->call("_redraw");
+ } else {
+ ERR_FAIL_COND(!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) const {
+ if (get_script_instance() && get_script_instance()->has_method("_get_handle_name")) {
+ return get_script_instance()->call("_get_handle_name", p_id);
+ }
+
+ ERR_FAIL_COND_V(!gizmo_plugin, "");
+ return gizmo_plugin->get_handle_name(this, p_id);
+}
+
+bool EditorNode3DGizmo::is_handle_highlighted(int p_id) const {
+ if (get_script_instance() && get_script_instance()->has_method("_is_handle_highlighted")) {
+ return get_script_instance()->call("_is_handle_highlighted", p_id);
+ }
+
+ ERR_FAIL_COND_V(!gizmo_plugin, false);
+ return gizmo_plugin->is_handle_highlighted(this, p_id);
+}
+
+Variant EditorNode3DGizmo::get_handle_value(int p_id) const {
+ if (get_script_instance() && get_script_instance()->has_method("_get_handle_value")) {
+ return get_script_instance()->call("_get_handle_value", p_id);
+ }
+
+ ERR_FAIL_COND_V(!gizmo_plugin, Variant());
+ return gizmo_plugin->get_handle_value(this, p_id);
+}
+
+void EditorNode3DGizmo::set_handle(int p_id, Camera3D *p_camera, const Point2 &p_point) const {
+ if (get_script_instance() && get_script_instance()->has_method("_set_handle")) {
+ get_script_instance()->call("_set_handle", p_id, p_camera, p_point);
+ return;
+ }
+
+ ERR_FAIL_COND(!gizmo_plugin);
+ gizmo_plugin->set_handle(this, p_id, p_camera, p_point);
+}
+
+void EditorNode3DGizmo::commit_handle(int p_id, const Variant &p_restore, bool p_cancel) const {
+ if (get_script_instance() && get_script_instance()->has_method("_commit_handle")) {
+ get_script_instance()->call("_commit_handle", p_id, p_restore, p_cancel);
+ return;
+ }
+
+ ERR_FAIL_COND(!gizmo_plugin);
+ gizmo_plugin->commit_handle(this, p_id, p_restore, p_cancel);
+}
+
+int EditorNode3DGizmo::subgizmos_intersect_ray(Camera3D *p_camera, const Vector2 &p_point) const {
+ if (get_script_instance() && get_script_instance()->has_method("_subgizmos_intersect_ray")) {
+ return get_script_instance()->call("_subgizmos_intersect_ray", p_camera, p_point);
+ }
+
+ ERR_FAIL_COND_V(!gizmo_plugin, -1);
+ return gizmo_plugin->subgizmos_intersect_ray(this, p_camera, p_point);
+}
+
+Vector<int> EditorNode3DGizmo::subgizmos_intersect_frustum(const Camera3D *p_camera, const Vector<Plane> &p_frustum) const {
+ if (get_script_instance() && get_script_instance()->has_method("_subgizmos_intersect_frustum")) {
+ Array frustum;
+ for (int i = 0; i < p_frustum.size(); i++) {
+ frustum[i] = p_frustum[i];
+ }
+ return get_script_instance()->call("_subgizmos_intersect_frustum", p_camera, frustum);
+ }
+
+ ERR_FAIL_COND_V(!gizmo_plugin, Vector<int>());
+ return gizmo_plugin->subgizmos_intersect_frustum(this, p_camera, p_frustum);
+}
+
+Transform3D EditorNode3DGizmo::get_subgizmo_transform(int p_id) const {
+ if (get_script_instance() && get_script_instance()->has_method("_get_subgizmo_transform")) {
+ return get_script_instance()->call("_get_subgizmo_transform", p_id);
+ }
+
+ ERR_FAIL_COND_V(!gizmo_plugin, Transform3D());
+ return gizmo_plugin->get_subgizmo_transform(this, p_id);
+}
+
+void EditorNode3DGizmo::set_subgizmo_transform(int p_id, Transform3D p_transform) const {
+ if (get_script_instance() && get_script_instance()->has_method("_set_subgizmo_transform")) {
+ get_script_instance()->call("_set_subgizmo_transform", p_id, p_transform);
+ return;
+ }
+
+ ERR_FAIL_COND(!gizmo_plugin);
+ gizmo_plugin->set_subgizmo_transform(this, p_id, p_transform);
+}
+
+void EditorNode3DGizmo::commit_subgizmos(const Vector<int> &p_ids, const Vector<Transform3D> &p_restore, bool p_cancel) const {
+ if (get_script_instance() && get_script_instance()->has_method("_commit_subgizmos")) {
+ Array ids;
+ for (int i = 0; i < p_ids.size(); i++) {
+ ids[i] = p_ids[i];
+ }
+
+ Array restore;
+ for (int i = 0; i < p_restore.size(); i++) {
+ restore[i] = p_restore[i];
+ }
+
+ get_script_instance()->call("_commit_subgizmos", ids, restore, p_cancel);
+ return;
+ }
+
+ ERR_FAIL_COND(!gizmo_plugin);
+ gizmo_plugin->commit_subgizmos(this, p_ids, p_restore, p_cancel);
+}
+
+void EditorNode3DGizmo::set_spatial_node(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);
+}
+
+void EditorNode3DGizmo::add_mesh(const Ref<ArrayMesh> &p_mesh, const Ref<Material> &p_material, const Transform3D &p_xform, const Ref<SkinReference> &p_skin_reference) {
+ ERR_FAIL_COND(!spatial_node);
+ 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<Vector3> &p_lines, const Ref<Material> &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<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;
+ }
+
+ ERR_FAIL_COND(!spatial_node);
+ Instance ins;
+
+ Ref<ArrayMesh> mesh = memnew(ArrayMesh);
+ Array a;
+ a.resize(Mesh::ARRAY_MAX);
+
+ a[Mesh::ARRAY_VERTEX] = p_vertices;
+
+ Vector<Color> 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<Material> &p_material, float p_scale, const Color &p_modulate) {
+ ERR_FAIL_COND(!spatial_node);
+ Instance ins;
+
+ Vector<Vector3> vs;
+ Vector<Vector2> uv;
+ Vector<Color> colors;
+
+ vs.push_back(Vector3(-p_scale, p_scale, 0));
+ vs.push_back(Vector3(p_scale, p_scale, 0));
+ vs.push_back(Vector3(p_scale, -p_scale, 0));
+ vs.push_back(Vector3(-p_scale, -p_scale, 0));
+
+ uv.push_back(Vector2(0, 0));
+ uv.push_back(Vector2(1, 0));
+ uv.push_back(Vector2(1, 1));
+ uv.push_back(Vector2(0, 1));
+
+ colors.push_back(p_modulate);
+ colors.push_back(p_modulate);
+ colors.push_back(p_modulate);
+ colors.push_back(p_modulate);
+
+ Ref<ArrayMesh> mesh = memnew(ArrayMesh);
+ Array a;
+ a.resize(Mesh::ARRAY_MAX);
+ a[Mesh::ARRAY_VERTEX] = vs;
+ a[Mesh::ARRAY_TEX_UV] = uv;
+ Vector<int> indices;
+ indices.push_back(0);
+ indices.push_back(1);
+ indices.push_back(2);
+ indices.push_back(0);
+ indices.push_back(2);
+ indices.push_back(3);
+ 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<TriangleMesh> &p_tmesh) {
+ collision_mesh = p_tmesh;
+}
+
+void EditorNode3DGizmo::add_collision_segments(const Vector<Vector3> &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<Vector3> &p_handles, const Ref<Material> &p_material, const Vector<int> &p_ids, bool p_billboard, bool p_secondary) {
+ billboard_handle = p_billboard;
+
+ if (!is_selected() || !is_editable()) {
+ return;
+ }
+
+ ERR_FAIL_COND(!spatial_node);
+
+ if (p_ids.is_empty()) {
+ ERR_FAIL_COND_MSG((!handles.is_empty() && !handle_ids.is_empty()) || (!secondary_handles.is_empty() && !secondary_handle_ids.is_empty()), "Fail");
+ } else {
+ ERR_FAIL_COND_MSG(handles.size() != handle_ids.size() || secondary_handles.size() != secondary_handle_ids.size(), "Fail");
+ }
+
+ bool is_current_hover_gizmo = Node3DEditor::get_singleton()->get_current_hover_gizmo() == this;
+ int current_hover_handle = Node3DEditor::get_singleton()->get_current_hover_gizmo_handle();
+
+ Instance ins;
+ Ref<ArrayMesh> mesh = memnew(ArrayMesh);
+
+ Array a;
+ a.resize(RS::ARRAY_MAX);
+ a[RS::ARRAY_VERTEX] = p_handles;
+ Vector<Color> 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)) {
+ 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) {
+ 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);
+
+ Vector<Vector3> &h = p_secondary ? secondary_handles : handles;
+ int current_size = h.size();
+ h.resize(current_size + p_handles.size());
+ for (int i = 0; i < p_handles.size(); i++) {
+ h.write[current_size + i] = p_handles[i];
+ }
+
+ if (!p_ids.is_empty()) {
+ Vector<int> &ids = p_secondary ? secondary_handle_ids : handle_ids;
+ current_size = ids.size();
+ ids.resize(current_size + p_ids.size());
+ for (int i = 0; i < p_ids.size(); i++) {
+ ids.write[current_size + i] = p_ids[i];
+ }
+ }
+}
+
+void EditorNode3DGizmo::add_solid_box(Ref<Material> &p_material, Vector3 p_size, Vector3 p_position, const Transform3D &p_xform) {
+ ERR_FAIL_COND(!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<ArrayMesh> 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<Plane> &p_frustum) {
+ ERR_FAIL_COND_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<Plane> 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<Vector3> 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) {
+ r_id = -1;
+
+ ERR_FAIL_COND(!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_axis(2), camera_xform.basis.get_axis(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];
+ }
+ }
+ }
+ }
+
+ 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];
+ }
+ }
+ }
+ }
+}
+
+bool EditorNode3DGizmo::intersect_ray(Camera3D *p_camera, const Point2 &p_point, Vector3 &r_pos, Vector3 &r_normal) {
+ ERR_FAIL_COND_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_axis(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().origin, (-p_camera->get_transform().basis.get_axis(2)).normalized());
+
+ 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_axis(2), p_camera->get_transform().basis.get_axis(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_axis(2), p_camera->get_transform().basis.get_axis(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_COND_V(!ed, false);
+ return ed->is_current_selected_gizmo(this) && ed->is_subgizmo_selected(p_id);
+}
+
+Vector<int> EditorNode3DGizmo::get_subgizmo_selection() const {
+ Vector<int> ret;
+
+ Node3DEditor *ed = Node3DEditor::get_singleton();
+ ERR_FAIL_COND_V(!ed, ret);
+
+ if (ed->is_current_selected_gizmo(this)) {
+ ret = ed->get_subgizmo_selection();
+ }
+
+ return ret;
+}
+
+void EditorNode3DGizmo::create() {
+ ERR_FAIL_COND(!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_COND(!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_COND(!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<SkinReference>()));
+ 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_spatial_node", "node"), &EditorNode3DGizmo::_set_spatial_node);
+ ClassDB::bind_method(D_METHOD("get_spatial_node"), &EditorNode3DGizmo::get_spatial_node);
+ 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"), &EditorNode3DGizmo::is_subgizmo_selected);
+ ClassDB::bind_method(D_METHOD("get_subgizmo_selection"), &EditorNode3DGizmo::get_subgizmo_selection);
+
+ BIND_VMETHOD(MethodInfo("_redraw"));
+ BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_handle_name", PropertyInfo(Variant::INT, "id")));
+ BIND_VMETHOD(MethodInfo(Variant::BOOL, "_is_handle_highlighted", PropertyInfo(Variant::INT, "id")));
+
+ MethodInfo hvget(Variant::NIL, "_get_handle_value", PropertyInfo(Variant::INT, "id"));
+ hvget.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
+ BIND_VMETHOD(hvget);
+
+ BIND_VMETHOD(MethodInfo("_set_handle", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Camera3D"), PropertyInfo(Variant::VECTOR2, "point")));
+ MethodInfo cm = MethodInfo("_commit_handle", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::NIL, "restore"), PropertyInfo(Variant::BOOL, "cancel"));
+ cm.default_arguments.push_back(false);
+ BIND_VMETHOD(cm);
+
+ BIND_VMETHOD(MethodInfo(Variant::INT, "_subgizmos_intersect_ray", PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Camera3D"), PropertyInfo(Variant::VECTOR2, "point")));
+ BIND_VMETHOD(MethodInfo(Variant::PACKED_INT32_ARRAY, "_subgizmos_intersect_frustum", PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Camera3D"), PropertyInfo(Variant::ARRAY, "frustum")));
+ BIND_VMETHOD(MethodInfo(Variant::TRANSFORM3D, "_get_subgizmo_transform", PropertyInfo(Variant::INT, "id")));
+ BIND_VMETHOD(MethodInfo("_set_subgizmo_transform", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::TRANSFORM3D, "transform")));
+ MethodInfo cs = MethodInfo("_commit_subgizmos", PropertyInfo(Variant::PACKED_INT32_ARRAY, "ids"), PropertyInfo(Variant::ARRAY, "restore"), PropertyInfo(Variant::BOOL, "cancel"));
+ cs.default_arguments.push_back(false);
+ BIND_VMETHOD(cs);
+}
+
+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_DEF("editors/3d_gizmos/gizmo_colors/instantiated", Color(0.7, 0.7, 0.7, 0.6));
+
+ Vector<Ref<StandardMaterial3D>> mats;
+
+ for (int i = 0; i < 4; i++) {
+ bool selected = i % 2 == 1;
+ bool instantiated = i < 2;
+
+ Ref<StandardMaterial3D> material = Ref<StandardMaterial3D>(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<Texture2D> &p_texture, bool p_on_top, const Color &p_albedo) {
+ Color instantiated_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/instantiated", Color(0.7, 0.7, 0.7, 0.6));
+
+ Vector<Ref<StandardMaterial3D>> icons;
+
+ for (int i = 0; i < 4; i++) {
+ bool selected = i % 2 == 1;
+ bool instantiated = i < 2;
+
+ Ref<StandardMaterial3D> icon = Ref<StandardMaterial3D>(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<Texture2D> &p_icon) {
+ Ref<StandardMaterial3D> handle_material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D));
+
+ handle_material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
+ handle_material->set_flag(StandardMaterial3D::FLAG_USE_POINT_SIZE, true);
+ Ref<Texture2D> handle_t = p_icon != nullptr ? p_icon : Node3DEditor::get_singleton()->get_theme_icon(SNAME("Editor3DHandle"), SNAME("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<Ref<StandardMaterial3D>>();
+ materials[p_name].push_back(handle_material);
+}
+
+void EditorNode3DGizmoPlugin::add_material(const String &p_name, Ref<StandardMaterial3D> p_material) {
+ materials[p_name] = Vector<Ref<StandardMaterial3D>>();
+ materials[p_name].push_back(p_material);
+}
+
+Ref<StandardMaterial3D> EditorNode3DGizmoPlugin::get_material(const String &p_name, const Ref<EditorNode3DGizmo> &p_gizmo) {
+ ERR_FAIL_COND_V(!materials.has(p_name), Ref<StandardMaterial3D>());
+ ERR_FAIL_COND_V(materials[p_name].size() == 0, Ref<StandardMaterial3D>());
+
+ 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<StandardMaterial3D> 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 {
+ if (get_script_instance() && get_script_instance()->has_method("_get_gizmo_name")) {
+ return get_script_instance()->call("_get_gizmo_name");
+ }
+ return TTR("Nameless gizmo");
+}
+
+int EditorNode3DGizmoPlugin::get_priority() const {
+ if (get_script_instance() && get_script_instance()->has_method("_get_priority")) {
+ return get_script_instance()->call("_get_priority");
+ }
+ return 0;
+}
+
+Ref<EditorNode3DGizmo> 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<EditorNode3DGizmo> ref = create_gizmo(p_spatial);
+
+ if (ref.is_null()) {
+ return ref;
+ }
+
+ ref->set_plugin(this);
+ ref->set_spatial_node(p_spatial);
+ ref->set_hidden(current_state == HIDDEN);
+
+ current_gizmos.push_back(ref.ptr());
+ return ref;
+}
+
+void EditorNode3DGizmoPlugin::_bind_methods() {
+#define GIZMO_REF PropertyInfo(Variant::OBJECT, "gizmo", PROPERTY_HINT_RESOURCE_TYPE, "EditorNode3DGizmo")
+
+ BIND_VMETHOD(MethodInfo(Variant::BOOL, "_has_gizmo", PropertyInfo(Variant::OBJECT, "spatial", PROPERTY_HINT_RESOURCE_TYPE, "Node3D")));
+ BIND_VMETHOD(MethodInfo(GIZMO_REF, "_create_gizmo", PropertyInfo(Variant::OBJECT, "spatial", PROPERTY_HINT_RESOURCE_TYPE, "Node3D")));
+
+ 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<EditorNode3DGizmo>()));
+
+ BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_gizmo_name"));
+ BIND_VMETHOD(MethodInfo(Variant::INT, "_get_priority"));
+ BIND_VMETHOD(MethodInfo(Variant::BOOL, "_can_be_hidden"));
+ BIND_VMETHOD(MethodInfo(Variant::BOOL, "_is_selectable_when_hidden"));
+
+ BIND_VMETHOD(MethodInfo("_redraw", GIZMO_REF));
+ BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_handle_name", GIZMO_REF, PropertyInfo(Variant::INT, "id")));
+ BIND_VMETHOD(MethodInfo(Variant::BOOL, "_is_handle_highlighted", GIZMO_REF, PropertyInfo(Variant::INT, "id")));
+
+ MethodInfo hvget(Variant::NIL, "_get_handle_value", GIZMO_REF, PropertyInfo(Variant::INT, "id"));
+ hvget.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
+ BIND_VMETHOD(hvget);
+
+ BIND_VMETHOD(MethodInfo("_set_handle", GIZMO_REF, PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Camera3D"), PropertyInfo(Variant::VECTOR2, "point")));
+ MethodInfo cm = MethodInfo("_commit_handle", GIZMO_REF, PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::NIL, "restore"), PropertyInfo(Variant::BOOL, "cancel"));
+ cm.default_arguments.push_back(false);
+ BIND_VMETHOD(cm);
+
+ BIND_VMETHOD(MethodInfo(Variant::INT, "_subgizmos_intersect_ray", GIZMO_REF, PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Camera3D"), PropertyInfo(Variant::VECTOR2, "point")));
+ BIND_VMETHOD(MethodInfo(Variant::PACKED_INT32_ARRAY, "_subgizmos_intersect_frustum", GIZMO_REF, PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Camera3D"), PropertyInfo(Variant::ARRAY, "frustum")));
+ BIND_VMETHOD(MethodInfo(Variant::TRANSFORM3D, "_get_subgizmo_transform", GIZMO_REF, PropertyInfo(Variant::INT, "id")));
+ BIND_VMETHOD(MethodInfo("_set_subgizmo_transform", GIZMO_REF, PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::TRANSFORM3D, "transform")));
+ MethodInfo cs = MethodInfo("_commit_subgizmos", GIZMO_REF, PropertyInfo(Variant::PACKED_INT32_ARRAY, "ids"), PropertyInfo(Variant::ARRAY, "restore"), PropertyInfo(Variant::BOOL, "cancel"));
+ cs.default_arguments.push_back(false);
+ BIND_VMETHOD(cs);
+
+#undef GIZMO_REF
+}
+
+bool EditorNode3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ if (get_script_instance() && get_script_instance()->has_method("_has_gizmo")) {
+ return get_script_instance()->call("_has_gizmo", p_spatial);
+ }
+ return false;
+}
+
+Ref<EditorNode3DGizmo> EditorNode3DGizmoPlugin::create_gizmo(Node3D *p_spatial) {
+ if (get_script_instance() && get_script_instance()->has_method("_create_gizmo")) {
+ return get_script_instance()->call("_create_gizmo", p_spatial);
+ }
+
+ Ref<EditorNode3DGizmo> ref;
+ if (has_gizmo(p_spatial)) {
+ ref.instantiate();
+ }
+ return ref;
+}
+
+bool EditorNode3DGizmoPlugin::can_be_hidden() const {
+ if (get_script_instance() && get_script_instance()->has_method("_can_be_hidden")) {
+ return get_script_instance()->call("_can_be_hidden");
+ }
+ return true;
+}
+
+bool EditorNode3DGizmoPlugin::is_selectable_when_hidden() const {
+ if (get_script_instance() && get_script_instance()->has_method("_is_selectable_when_hidden")) {
+ return get_script_instance()->call("_is_selectable_when_hidden");
+ }
+ return false;
+}
+
+void EditorNode3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ if (get_script_instance() && get_script_instance()->has_method("_redraw")) {
+ Ref<EditorNode3DGizmo> ref(p_gizmo);
+ get_script_instance()->call("_redraw", ref);
+ }
+}
+
+bool EditorNode3DGizmoPlugin::is_handle_highlighted(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ if (get_script_instance() && get_script_instance()->has_method("_is_handle_highlighted")) {
+ return get_script_instance()->call("_is_handle_highlighted", p_gizmo, p_id);
+ }
+ return false;
+}
+
+String EditorNode3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ if (get_script_instance() && get_script_instance()->has_method("_get_handle_name")) {
+ return get_script_instance()->call("_get_handle_name", p_gizmo, p_id);
+ }
+ return "";
+}
+
+Variant EditorNode3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ if (get_script_instance() && get_script_instance()->has_method("_get_handle_value")) {
+ return get_script_instance()->call("_get_handle_value", p_gizmo, p_id);
+ }
+ return Variant();
+}
+
+void EditorNode3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const {
+ if (get_script_instance() && get_script_instance()->has_method("_set_handle")) {
+ get_script_instance()->call("_set_handle", p_gizmo, p_id, p_camera, p_point);
+ }
+}
+
+void EditorNode3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) const {
+ if (get_script_instance() && get_script_instance()->has_method("_commit_handle")) {
+ get_script_instance()->call("_commit_handle", p_gizmo, p_id, p_restore, p_cancel);
+ }
+}
+
+int EditorNode3DGizmoPlugin::subgizmos_intersect_ray(const EditorNode3DGizmo *p_gizmo, Camera3D *p_camera, const Vector2 &p_point) const {
+ if (get_script_instance() && get_script_instance()->has_method("_subgizmos_intersect_ray")) {
+ return get_script_instance()->call("_subgizmos_intersect_ray", p_camera, p_point);
+ }
+ return -1;
+}
+
+Vector<int> EditorNode3DGizmoPlugin::subgizmos_intersect_frustum(const EditorNode3DGizmo *p_gizmo, const Camera3D *p_camera, const Vector<Plane> &p_frustum) const {
+ if (get_script_instance() && get_script_instance()->has_method("_subgizmos_intersect_frustum")) {
+ Array frustum;
+ for (int i = 0; i < p_frustum.size(); i++) {
+ frustum[i] = p_frustum[i];
+ }
+ return get_script_instance()->call("_subgizmos_intersect_frustum", p_camera, frustum);
+ }
+
+ return Vector<int>();
+}
+
+Transform3D EditorNode3DGizmoPlugin::get_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ if (get_script_instance() && get_script_instance()->has_method("_get_subgizmo_transform")) {
+ return get_script_instance()->call("_get_subgizmo_transform", p_id);
+ }
+
+ return Transform3D();
+}
+
+void EditorNode3DGizmoPlugin::set_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id, Transform3D p_transform) const {
+ if (get_script_instance() && get_script_instance()->has_method("_set_subgizmo_transform")) {
+ get_script_instance()->call("_set_subgizmo_transform", p_id, p_transform);
+ }
+}
+
+void EditorNode3DGizmoPlugin::commit_subgizmos(const EditorNode3DGizmo *p_gizmo, const Vector<int> &p_ids, const Vector<Transform3D> &p_restore, bool p_cancel) const {
+ if (get_script_instance() && get_script_instance()->has_method("_commit_subgizmos")) {
+ Array ids;
+ for (int i = 0; i < p_ids.size(); i++) {
+ ids[i] = p_ids[i];
+ }
+
+ Array restore;
+ for (int i = 0; i < p_restore.size(); i++) {
+ restore[i] = p_restore[i];
+ }
+
+ get_script_instance()->call("_commit_subgizmos", 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_spatial_node()->remove_gizmo(current_gizmos[i]);
+ }
+ if (Node3DEditor::get_singleton()) {
+ Node3DEditor::get_singleton()->update_all_gizmos();
+ }
+}
+
+//// light gizmo
+
+Light3DGizmoPlugin::Light3DGizmoPlugin() {
+ // Enable vertex colors for the materials below as the gizmo color depends on the light color.
+ create_material("lines_primary", Color(1, 1, 1), false, false, true);
+ create_material("lines_secondary", Color(1, 1, 1, 0.35), false, false, true);
+ create_material("lines_billboard", Color(1, 1, 1), true, false, true);
+
+ create_icon_material("light_directional_icon", Node3DEditor::get_singleton()->get_theme_icon(SNAME("GizmoDirectionalLight"), SNAME("EditorIcons")));
+ create_icon_material("light_omni_icon", Node3DEditor::get_singleton()->get_theme_icon(SNAME("GizmoLight"), SNAME("EditorIcons")));
+ create_icon_material("light_spot_icon", Node3DEditor::get_singleton()->get_theme_icon(SNAME("GizmoSpotLight"), SNAME("EditorIcons")));
+
+ create_handle_material("handles");
+ create_handle_material("handles_billboard", true);
+}
+
+bool Light3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<Light3D>(p_spatial) != nullptr;
+}
+
+String Light3DGizmoPlugin::get_gizmo_name() const {
+ return "Light3D";
+}
+
+int Light3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+String Light3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ if (p_id == 0) {
+ return "Radius";
+ } else {
+ return "Aperture";
+ }
+}
+
+Variant Light3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ Light3D *light = Object::cast_to<Light3D>(p_gizmo->get_spatial_node());
+ if (p_id == 0) {
+ return light->get_param(Light3D::PARAM_RANGE);
+ }
+ if (p_id == 1) {
+ return light->get_param(Light3D::PARAM_SPOT_ANGLE);
+ }
+
+ return Variant();
+}
+
+static float _find_closest_angle_to_half_pi_arc(const Vector3 &p_from, const Vector3 &p_to, float p_arc_radius, const Transform3D &p_arc_xform) {
+ //bleh, discrete is simpler
+ static const int arc_test_points = 64;
+ float min_d = 1e20;
+ Vector3 min_p;
+
+ for (int i = 0; i < arc_test_points; i++) {
+ float a = i * Math_PI * 0.5 / arc_test_points;
+ float an = (i + 1) * Math_PI * 0.5 / arc_test_points;
+ Vector3 p = Vector3(Math::cos(a), 0, -Math::sin(a)) * p_arc_radius;
+ Vector3 n = Vector3(Math::cos(an), 0, -Math::sin(an)) * p_arc_radius;
+
+ Vector3 ra, rb;
+ Geometry3D::get_closest_points_between_segments(p, n, p_from, p_to, ra, rb);
+
+ float d = ra.distance_to(rb);
+ if (d < min_d) {
+ min_d = d;
+ min_p = ra;
+ }
+ }
+
+ //min_p = p_arc_xform.affine_inverse().xform(min_p);
+ float a = (Math_PI * 0.5) - Vector2(min_p.x, -min_p.z).angle();
+ return Math::rad2deg(a);
+}
+
+void Light3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const {
+ Light3D *light = Object::cast_to<Light3D>(p_gizmo->get_spatial_node());
+ Transform3D gt = light->get_global_transform();
+ Transform3D gi = gt.affine_inverse();
+
+ Vector3 ray_from = p_camera->project_ray_origin(p_point);
+ Vector3 ray_dir = p_camera->project_ray_normal(p_point);
+
+ Vector3 s[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 4096) };
+ if (p_id == 0) {
+ if (Object::cast_to<SpotLight3D>(light)) {
+ Vector3 ra, rb;
+ Geometry3D::get_closest_points_between_segments(Vector3(), Vector3(0, 0, -4096), s[0], s[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) { // Equal is here for negative zero.
+ d = 0;
+ }
+
+ light->set_param(Light3D::PARAM_RANGE, d);
+ } else if (Object::cast_to<OmniLight3D>(light)) {
+ Plane cp = Plane(gt.origin, p_camera->get_transform().basis.get_axis(2));
+
+ Vector3 inters;
+ if (cp.intersects_ray(ray_from, ray_dir, &inters)) {
+ float r = inters.distance_to(gt.origin);
+ if (Node3DEditor::get_singleton()->is_snap_enabled()) {
+ r = Math::snapped(r, Node3DEditor::get_singleton()->get_translate_snap());
+ }
+
+ light->set_param(Light3D::PARAM_RANGE, r);
+ }
+ }
+
+ } else if (p_id == 1) {
+ float a = _find_closest_angle_to_half_pi_arc(s[0], s[1], light->get_param(Light3D::PARAM_RANGE), gt);
+ light->set_param(Light3D::PARAM_SPOT_ANGLE, CLAMP(a, 0.01, 89.99));
+ }
+}
+
+void Light3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) const {
+ Light3D *light = Object::cast_to<Light3D>(p_gizmo->get_spatial_node());
+ if (p_cancel) {
+ light->set_param(p_id == 0 ? Light3D::PARAM_RANGE : Light3D::PARAM_SPOT_ANGLE, p_restore);
+
+ } else if (p_id == 0) {
+ UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
+ ur->create_action(TTR("Change Light Radius"));
+ ur->add_do_method(light, "set_param", Light3D::PARAM_RANGE, light->get_param(Light3D::PARAM_RANGE));
+ ur->add_undo_method(light, "set_param", Light3D::PARAM_RANGE, p_restore);
+ ur->commit_action();
+ } else if (p_id == 1) {
+ UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
+ ur->create_action(TTR("Change Light Radius"));
+ ur->add_do_method(light, "set_param", Light3D::PARAM_SPOT_ANGLE, light->get_param(Light3D::PARAM_SPOT_ANGLE));
+ ur->add_undo_method(light, "set_param", Light3D::PARAM_SPOT_ANGLE, p_restore);
+ ur->commit_action();
+ }
+}
+
+void Light3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ Light3D *light = Object::cast_to<Light3D>(p_gizmo->get_spatial_node());
+
+ Color color = light->get_color();
+ // Make the gizmo color as bright as possible for better visibility
+ color.set_hsv(color.get_h(), color.get_s(), 1);
+
+ p_gizmo->clear();
+
+ if (Object::cast_to<DirectionalLight3D>(light)) {
+ Ref<Material> material = get_material("lines_primary", p_gizmo);
+ Ref<Material> icon = get_material("light_directional_icon", p_gizmo);
+
+ const int arrow_points = 7;
+ const float arrow_length = 1.5;
+
+ Vector3 arrow[arrow_points] = {
+ Vector3(0, 0, -1),
+ Vector3(0, 0.8, 0),
+ Vector3(0, 0.3, 0),
+ Vector3(0, 0.3, arrow_length),
+ Vector3(0, -0.3, arrow_length),
+ Vector3(0, -0.3, 0),
+ Vector3(0, -0.8, 0)
+ };
+
+ int arrow_sides = 2;
+
+ Vector<Vector3> lines;
+
+ for (int i = 0; i < arrow_sides; i++) {
+ for (int j = 0; j < arrow_points; j++) {
+ Basis ma(Vector3(0, 0, 1), Math_PI * i / arrow_sides);
+
+ Vector3 v1 = arrow[j] - Vector3(0, 0, arrow_length);
+ Vector3 v2 = arrow[(j + 1) % arrow_points] - Vector3(0, 0, arrow_length);
+
+ lines.push_back(ma.xform(v1));
+ lines.push_back(ma.xform(v2));
+ }
+ }
+
+ p_gizmo->add_lines(lines, material, false, color);
+ p_gizmo->add_unscaled_billboard(icon, 0.05, color);
+ }
+
+ if (Object::cast_to<OmniLight3D>(light)) {
+ // Use both a billboard circle and 3 non-billboard circles for a better sphere-like representation
+ const Ref<Material> lines_material = get_material("lines_secondary", p_gizmo);
+ const Ref<Material> lines_billboard_material = get_material("lines_billboard", p_gizmo);
+ const Ref<Material> icon = get_material("light_omni_icon", p_gizmo);
+
+ OmniLight3D *on = Object::cast_to<OmniLight3D>(light);
+ const float r = on->get_param(Light3D::PARAM_RANGE);
+ Vector<Vector3> points;
+ Vector<Vector3> points_billboard;
+
+ for (int i = 0; i < 120; i++) {
+ // Create a circle
+ const float ra = Math::deg2rad((float)(i * 3));
+ const float rb = Math::deg2rad((float)((i + 1) * 3));
+ const Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * r;
+ const Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * r;
+
+ // Draw axis-aligned circles
+ 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));
+
+ // Draw a billboarded circle
+ points_billboard.push_back(Vector3(a.x, a.y, 0));
+ points_billboard.push_back(Vector3(b.x, b.y, 0));
+ }
+
+ p_gizmo->add_lines(points, lines_material, true, color);
+ p_gizmo->add_lines(points_billboard, lines_billboard_material, true, color);
+ p_gizmo->add_unscaled_billboard(icon, 0.05, color);
+
+ Vector<Vector3> handles;
+ handles.push_back(Vector3(r, 0, 0));
+ p_gizmo->add_handles(handles, get_material("handles_billboard"), Vector<int>(), true);
+ }
+
+ if (Object::cast_to<SpotLight3D>(light)) {
+ const Ref<Material> material_primary = get_material("lines_primary", p_gizmo);
+ const Ref<Material> material_secondary = get_material("lines_secondary", p_gizmo);
+ const Ref<Material> icon = get_material("light_spot_icon", p_gizmo);
+
+ Vector<Vector3> points_primary;
+ Vector<Vector3> points_secondary;
+ SpotLight3D *sl = Object::cast_to<SpotLight3D>(light);
+
+ float r = sl->get_param(Light3D::PARAM_RANGE);
+ float w = r * Math::sin(Math::deg2rad(sl->get_param(Light3D::PARAM_SPOT_ANGLE)));
+ float d = r * Math::cos(Math::deg2rad(sl->get_param(Light3D::PARAM_SPOT_ANGLE)));
+
+ for (int i = 0; i < 120; i++) {
+ // Draw a circle
+ const float ra = Math::deg2rad((float)(i * 3));
+ const float rb = Math::deg2rad((float)((i + 1) * 3));
+ const Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * w;
+ const Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * w;
+
+ points_primary.push_back(Vector3(a.x, a.y, -d));
+ points_primary.push_back(Vector3(b.x, b.y, -d));
+
+ if (i % 15 == 0) {
+ // Draw 8 lines from the cone origin to the sides of the circle
+ points_secondary.push_back(Vector3(a.x, a.y, -d));
+ points_secondary.push_back(Vector3());
+ }
+ }
+
+ points_primary.push_back(Vector3(0, 0, -r));
+ points_primary.push_back(Vector3());
+
+ p_gizmo->add_lines(points_primary, material_primary, false, color);
+ p_gizmo->add_lines(points_secondary, material_secondary, false, color);
+
+ Vector<Vector3> handles;
+ handles.push_back(Vector3(0, 0, -r));
+ handles.push_back(Vector3(w, 0, -d));
+
+ p_gizmo->add_handles(handles, get_material("handles"));
+ p_gizmo->add_unscaled_billboard(icon, 0.05, color);
+ }
+}
+
+//////
+
+//// player gizmo
+AudioStreamPlayer3DGizmoPlugin::AudioStreamPlayer3DGizmoPlugin() {
+ Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/stream_player_3d", Color(0.4, 0.8, 1));
+
+ create_icon_material("stream_player_3d_icon", Node3DEditor::get_singleton()->get_theme_icon(SNAME("Gizmo3DSamplePlayer"), SNAME("EditorIcons")));
+ create_material("stream_player_3d_material_primary", gizmo_color);
+ create_material("stream_player_3d_material_secondary", gizmo_color * Color(1, 1, 1, 0.35));
+ create_handle_material("handles");
+}
+
+bool AudioStreamPlayer3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<AudioStreamPlayer3D>(p_spatial) != nullptr;
+}
+
+String AudioStreamPlayer3DGizmoPlugin::get_gizmo_name() const {
+ return "AudioStreamPlayer3D";
+}
+
+int AudioStreamPlayer3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+String AudioStreamPlayer3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ return "Emission Radius";
+}
+
+Variant AudioStreamPlayer3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ AudioStreamPlayer3D *player = Object::cast_to<AudioStreamPlayer3D>(p_gizmo->get_spatial_node());
+ return player->get_emission_angle();
+}
+
+void AudioStreamPlayer3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const {
+ AudioStreamPlayer3D *player = Object::cast_to<AudioStreamPlayer3D>(p_gizmo->get_spatial_node());
+
+ Transform3D gt = player->get_global_transform();
+ Transform3D gi = gt.affine_inverse();
+
+ Vector3 ray_from = p_camera->project_ray_origin(p_point);
+ Vector3 ray_dir = p_camera->project_ray_normal(p_point);
+ Vector3 ray_to = ray_from + ray_dir * 4096;
+
+ ray_from = gi.xform(ray_from);
+ ray_to = gi.xform(ray_to);
+
+ float closest_dist = 1e20;
+ float closest_angle = 1e20;
+
+ for (int i = 0; i < 180; i++) {
+ float a = Math::deg2rad((float)i);
+ float an = Math::deg2rad((float)(i + 1));
+
+ Vector3 from(Math::sin(a), 0, -Math::cos(a));
+ Vector3 to(Math::sin(an), 0, -Math::cos(an));
+
+ Vector3 r1, r2;
+ Geometry3D::get_closest_points_between_segments(from, to, ray_from, ray_to, r1, r2);
+ float d = r1.distance_to(r2);
+ if (d < closest_dist) {
+ closest_dist = d;
+ closest_angle = i;
+ }
+ }
+
+ if (closest_angle < 91) {
+ player->set_emission_angle(closest_angle);
+ }
+}
+
+void AudioStreamPlayer3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) const {
+ AudioStreamPlayer3D *player = Object::cast_to<AudioStreamPlayer3D>(p_gizmo->get_spatial_node());
+
+ if (p_cancel) {
+ player->set_emission_angle(p_restore);
+
+ } else {
+ UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
+ ur->create_action(TTR("Change AudioStreamPlayer3D Emission Angle"));
+ ur->add_do_method(player, "set_emission_angle", player->get_emission_angle());
+ ur->add_undo_method(player, "set_emission_angle", p_restore);
+ ur->commit_action();
+ }
+}
+
+void AudioStreamPlayer3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ const AudioStreamPlayer3D *player = Object::cast_to<AudioStreamPlayer3D>(p_gizmo->get_spatial_node());
+
+ p_gizmo->clear();
+
+ const Ref<Material> icon = get_material("stream_player_3d_icon", p_gizmo);
+
+ if (player->is_emission_angle_enabled()) {
+ const float pc = player->get_emission_angle();
+ const float ofs = -Math::cos(Math::deg2rad(pc));
+ const float radius = Math::sin(Math::deg2rad(pc));
+
+ Vector<Vector3> points_primary;
+ points_primary.resize(200);
+
+ real_t step = Math_TAU / 100.0;
+ for (int i = 0; i < 100; i++) {
+ const float a = i * step;
+ const float an = (i + 1) * step;
+
+ const Vector3 from(Math::sin(a) * radius, Math::cos(a) * radius, ofs);
+ const Vector3 to(Math::sin(an) * radius, Math::cos(an) * radius, ofs);
+
+ points_primary.write[i * 2 + 0] = from;
+ points_primary.write[i * 2 + 1] = to;
+ }
+
+ const Ref<Material> material_primary = get_material("stream_player_3d_material_primary", p_gizmo);
+ p_gizmo->add_lines(points_primary, material_primary);
+
+ Vector<Vector3> points_secondary;
+ points_secondary.resize(16);
+
+ for (int i = 0; i < 8; i++) {
+ const float a = i * (Math_TAU / 8.0);
+ const Vector3 from(Math::sin(a) * radius, Math::cos(a) * radius, ofs);
+
+ points_secondary.write[i * 2 + 0] = from;
+ points_secondary.write[i * 2 + 1] = Vector3();
+ }
+
+ const Ref<Material> material_secondary = get_material("stream_player_3d_material_secondary", p_gizmo);
+ p_gizmo->add_lines(points_secondary, material_secondary);
+
+ Vector<Vector3> handles;
+ const float ha = Math::deg2rad(player->get_emission_angle());
+ handles.push_back(Vector3(Math::sin(ha), 0, -Math::cos(ha)));
+ p_gizmo->add_handles(handles, get_material("handles"));
+ }
+
+ p_gizmo->add_unscaled_billboard(icon, 0.05);
+}
+
+//////
+
+Camera3DGizmoPlugin::Camera3DGizmoPlugin() {
+ Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/camera", Color(0.8, 0.4, 0.8));
+
+ create_material("camera_material", gizmo_color);
+ create_handle_material("handles");
+}
+
+bool Camera3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<Camera3D>(p_spatial) != nullptr;
+}
+
+String Camera3DGizmoPlugin::get_gizmo_name() const {
+ return "Camera3D";
+}
+
+int Camera3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+String Camera3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_spatial_node());
+
+ if (camera->get_projection() == Camera3D::PROJECTION_PERSPECTIVE) {
+ return "FOV";
+ } else {
+ return "Size";
+ }
+}
+
+Variant Camera3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_spatial_node());
+
+ if (camera->get_projection() == Camera3D::PROJECTION_PERSPECTIVE) {
+ return camera->get_fov();
+ } else {
+ return camera->get_size();
+ }
+}
+
+void Camera3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const {
+ Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_spatial_node());
+
+ Transform3D gt = camera->get_global_transform();
+ Transform3D gi = gt.affine_inverse();
+
+ Vector3 ray_from = p_camera->project_ray_origin(p_point);
+ Vector3 ray_dir = p_camera->project_ray_normal(p_point);
+
+ Vector3 s[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 4096) };
+
+ if (camera->get_projection() == Camera3D::PROJECTION_PERSPECTIVE) {
+ Transform3D gt2 = camera->get_global_transform();
+ float a = _find_closest_angle_to_half_pi_arc(s[0], s[1], 1.0, gt2);
+ camera->set("fov", CLAMP(a * 2.0, 1, 179));
+ } else {
+ Vector3 ra, rb;
+ Geometry3D::get_closest_points_between_segments(Vector3(0, 0, -1), Vector3(4096, 0, -1), s[0], s[1], ra, rb);
+ float d = ra.x * 2.0;
+ if (Node3DEditor::get_singleton()->is_snap_enabled()) {
+ d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
+ }
+
+ d = CLAMP(d, 0.1, 16384);
+
+ camera->set("size", d);
+ }
+}
+
+void Camera3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) const {
+ Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_spatial_node());
+
+ if (camera->get_projection() == Camera3D::PROJECTION_PERSPECTIVE) {
+ if (p_cancel) {
+ camera->set("fov", p_restore);
+ } else {
+ UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
+ ur->create_action(TTR("Change Camera FOV"));
+ ur->add_do_property(camera, "fov", camera->get_fov());
+ ur->add_undo_property(camera, "fov", p_restore);
+ ur->commit_action();
+ }
+
+ } else {
+ if (p_cancel) {
+ camera->set("size", p_restore);
+ } else {
+ UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
+ ur->create_action(TTR("Change Camera Size"));
+ ur->add_do_property(camera, "size", camera->get_size());
+ ur->add_undo_property(camera, "size", p_restore);
+ ur->commit_action();
+ }
+ }
+}
+
+void Camera3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_spatial_node());
+
+ p_gizmo->clear();
+
+ Vector<Vector3> lines;
+ Vector<Vector3> handles;
+
+ Ref<Material> material = get_material("camera_material", p_gizmo);
+
+#define ADD_TRIANGLE(m_a, m_b, m_c) \
+ { \
+ lines.push_back(m_a); \
+ lines.push_back(m_b); \
+ lines.push_back(m_b); \
+ lines.push_back(m_c); \
+ lines.push_back(m_c); \
+ lines.push_back(m_a); \
+ }
+
+#define ADD_QUAD(m_a, m_b, m_c, m_d) \
+ { \
+ lines.push_back(m_a); \
+ lines.push_back(m_b); \
+ lines.push_back(m_b); \
+ lines.push_back(m_c); \
+ lines.push_back(m_c); \
+ lines.push_back(m_d); \
+ lines.push_back(m_d); \
+ lines.push_back(m_a); \
+ }
+
+ switch (camera->get_projection()) {
+ case Camera3D::PROJECTION_PERSPECTIVE: {
+ // The real FOV is halved for accurate representation
+ float fov = camera->get_fov() / 2.0;
+
+ Vector3 side = Vector3(Math::sin(Math::deg2rad(fov)), 0, -Math::cos(Math::deg2rad(fov)));
+ Vector3 nside = side;
+ nside.x = -nside.x;
+ Vector3 up = Vector3(0, side.x, 0);
+
+ ADD_TRIANGLE(Vector3(), side + up, side - up);
+ ADD_TRIANGLE(Vector3(), nside + up, nside - up);
+ ADD_TRIANGLE(Vector3(), side + up, nside + up);
+ ADD_TRIANGLE(Vector3(), side - up, nside - up);
+
+ handles.push_back(side);
+ side.x *= 0.25;
+ nside.x *= 0.25;
+ Vector3 tup(0, up.y * 3 / 2, side.z);
+ ADD_TRIANGLE(tup, side + up, nside + up);
+
+ } break;
+ case Camera3D::PROJECTION_ORTHOGONAL: {
+ float size = camera->get_size();
+
+ float hsize = size * 0.5;
+ Vector3 right(hsize, 0, 0);
+ Vector3 up(0, hsize, 0);
+ Vector3 back(0, 0, -1.0);
+ Vector3 front(0, 0, 0);
+
+ ADD_QUAD(-up - right, -up + right, up + right, up - right);
+ ADD_QUAD(-up - right + back, -up + right + back, up + right + back, up - right + back);
+ ADD_QUAD(up + right, up + right + back, up - right + back, up - right);
+ ADD_QUAD(-up + right, -up + right + back, -up - right + back, -up - right);
+
+ handles.push_back(right + back);
+
+ right.x *= 0.25;
+ Vector3 tup(0, up.y * 3 / 2, back.z);
+ ADD_TRIANGLE(tup, right + up + back, -right + up + back);
+
+ } break;
+ case Camera3D::PROJECTION_FRUSTUM: {
+ float hsize = camera->get_size() / 2.0;
+
+ Vector3 side = Vector3(hsize, 0, -camera->get_near()).normalized();
+ Vector3 nside = side;
+ nside.x = -nside.x;
+ Vector3 up = Vector3(0, side.x, 0);
+ Vector3 offset = Vector3(camera->get_frustum_offset().x, camera->get_frustum_offset().y, 0.0);
+
+ ADD_TRIANGLE(Vector3(), side + up + offset, side - up + offset);
+ ADD_TRIANGLE(Vector3(), nside + up + offset, nside - up + offset);
+ ADD_TRIANGLE(Vector3(), side + up + offset, nside + up + offset);
+ ADD_TRIANGLE(Vector3(), side - up + offset, nside - up + offset);
+
+ side.x *= 0.25;
+ nside.x *= 0.25;
+ Vector3 tup(0, up.y * 3 / 2, side.z);
+ ADD_TRIANGLE(tup + offset, side + up + offset, nside + up + offset);
+ }
+ }
+
+#undef ADD_TRIANGLE
+#undef ADD_QUAD
+
+ p_gizmo->add_lines(lines, material);
+ p_gizmo->add_handles(handles, get_material("handles"));
+
+ ClippedCamera3D *clipcam = Object::cast_to<ClippedCamera3D>(camera);
+ if (clipcam) {
+ Node3D *parent = Object::cast_to<Node3D>(camera->get_parent());
+ if (!parent) {
+ return;
+ }
+ Vector3 cam_normal = -camera->get_global_transform().basis.get_axis(Vector3::AXIS_Z).normalized();
+ Vector3 cam_x = camera->get_global_transform().basis.get_axis(Vector3::AXIS_X).normalized();
+ Vector3 cam_y = camera->get_global_transform().basis.get_axis(Vector3::AXIS_Y).normalized();
+ Vector3 cam_pos = camera->get_global_transform().origin;
+ Vector3 parent_pos = parent->get_global_transform().origin;
+
+ Plane parent_plane(parent_pos, cam_normal);
+ Vector3 ray_from = parent_plane.project(cam_pos);
+
+ lines.clear();
+ lines.push_back(ray_from + cam_x * 0.5 + cam_y * 0.5);
+ lines.push_back(ray_from + cam_x * 0.5 + cam_y * -0.5);
+
+ lines.push_back(ray_from + cam_x * 0.5 + cam_y * -0.5);
+ lines.push_back(ray_from + cam_x * -0.5 + cam_y * -0.5);
+
+ lines.push_back(ray_from + cam_x * -0.5 + cam_y * -0.5);
+ lines.push_back(ray_from + cam_x * -0.5 + cam_y * 0.5);
+
+ lines.push_back(ray_from + cam_x * -0.5 + cam_y * 0.5);
+ lines.push_back(ray_from + cam_x * 0.5 + cam_y * 0.5);
+
+ if (parent_plane.distance_to(cam_pos) < 0) {
+ lines.push_back(ray_from);
+ lines.push_back(cam_pos);
+ }
+
+ Transform3D local = camera->get_global_transform().affine_inverse();
+ for (int i = 0; i < lines.size(); i++) {
+ lines.write[i] = local.xform(lines[i]);
+ }
+
+ p_gizmo->add_lines(lines, material);
+ }
+}
+
+//////
+
+MeshInstance3DGizmoPlugin::MeshInstance3DGizmoPlugin() {
+}
+
+bool MeshInstance3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<MeshInstance3D>(p_spatial) != nullptr && Object::cast_to<SoftBody3D>(p_spatial) == nullptr;
+}
+
+String MeshInstance3DGizmoPlugin::get_gizmo_name() const {
+ return "MeshInstance3D";
+}
+
+int MeshInstance3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+bool MeshInstance3DGizmoPlugin::can_be_hidden() const {
+ return false;
+}
+
+void MeshInstance3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ MeshInstance3D *mesh = Object::cast_to<MeshInstance3D>(p_gizmo->get_spatial_node());
+
+ p_gizmo->clear();
+
+ Ref<Mesh> m = mesh->get_mesh();
+
+ if (!m.is_valid()) {
+ return; //none
+ }
+
+ Ref<TriangleMesh> tm = m->generate_triangle_mesh();
+ if (tm.is_valid()) {
+ p_gizmo->add_collision_triangles(tm);
+ }
+}
+
+/////
+
+OccluderInstance3DGizmoPlugin::OccluderInstance3DGizmoPlugin() {
+ create_material("line_material", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/occluder", Color(0.8, 0.5, 1)));
+}
+
+bool OccluderInstance3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<OccluderInstance3D>(p_spatial) != nullptr;
+}
+
+String OccluderInstance3DGizmoPlugin::get_gizmo_name() const {
+ return "OccluderInstance3D";
+}
+
+int OccluderInstance3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+void OccluderInstance3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ OccluderInstance3D *occluder_instance = Object::cast_to<OccluderInstance3D>(p_gizmo->get_spatial_node());
+
+ p_gizmo->clear();
+
+ Ref<Occluder3D> o = occluder_instance->get_occluder();
+
+ if (!o.is_valid()) {
+ return;
+ }
+
+ Vector<Vector3> lines = o->get_debug_lines();
+ if (!lines.is_empty()) {
+ Ref<Material> material = get_material("line_material", p_gizmo);
+ p_gizmo->add_lines(lines, material);
+ p_gizmo->add_collision_segments(lines);
+ }
+}
+
+/////
+
+Sprite3DGizmoPlugin::Sprite3DGizmoPlugin() {
+}
+
+bool Sprite3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<Sprite3D>(p_spatial) != nullptr;
+}
+
+String Sprite3DGizmoPlugin::get_gizmo_name() const {
+ return "Sprite3D";
+}
+
+int Sprite3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+bool Sprite3DGizmoPlugin::can_be_hidden() const {
+ return false;
+}
+
+void Sprite3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ Sprite3D *sprite = Object::cast_to<Sprite3D>(p_gizmo->get_spatial_node());
+
+ p_gizmo->clear();
+
+ Ref<TriangleMesh> tm = sprite->generate_triangle_mesh();
+ if (tm.is_valid()) {
+ p_gizmo->add_collision_triangles(tm);
+ }
+}
+
+///
+
+Position3DGizmoPlugin::Position3DGizmoPlugin() {
+ pos3d_mesh = Ref<ArrayMesh>(memnew(ArrayMesh));
+ cursor_points = Vector<Vector3>();
+
+ Vector<Color> cursor_colors;
+ const float cs = 0.25;
+ // Add more points to create a "hard stop" in the color gradient.
+ cursor_points.push_back(Vector3(+cs, 0, 0));
+ cursor_points.push_back(Vector3());
+ cursor_points.push_back(Vector3());
+ cursor_points.push_back(Vector3(-cs, 0, 0));
+
+ cursor_points.push_back(Vector3(0, +cs, 0));
+ cursor_points.push_back(Vector3());
+ cursor_points.push_back(Vector3());
+ cursor_points.push_back(Vector3(0, -cs, 0));
+
+ cursor_points.push_back(Vector3(0, 0, +cs));
+ cursor_points.push_back(Vector3());
+ cursor_points.push_back(Vector3());
+ cursor_points.push_back(Vector3(0, 0, -cs));
+
+ // Use the axis color which is brighter for the positive axis.
+ // Use a darkened axis color for the negative axis.
+ // This makes it possible to see in which direction the Position3D node is rotated
+ // (which can be important depending on how it's used).
+ const Color color_x = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("axis_x_color"), SNAME("Editor"));
+ cursor_colors.push_back(color_x);
+ cursor_colors.push_back(color_x);
+ // FIXME: Use less strong darkening factor once GH-48573 is fixed.
+ // The current darkening factor compensates for lines being too bright in the 3D editor.
+ cursor_colors.push_back(color_x.lerp(Color(0, 0, 0), 0.75));
+ cursor_colors.push_back(color_x.lerp(Color(0, 0, 0), 0.75));
+
+ const Color color_y = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("axis_y_color"), SNAME("Editor"));
+ cursor_colors.push_back(color_y);
+ cursor_colors.push_back(color_y);
+ cursor_colors.push_back(color_y.lerp(Color(0, 0, 0), 0.75));
+ cursor_colors.push_back(color_y.lerp(Color(0, 0, 0), 0.75));
+
+ const Color color_z = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("axis_z_color"), SNAME("Editor"));
+ cursor_colors.push_back(color_z);
+ cursor_colors.push_back(color_z);
+ cursor_colors.push_back(color_z.lerp(Color(0, 0, 0), 0.75));
+ cursor_colors.push_back(color_z.lerp(Color(0, 0, 0), 0.75));
+
+ Ref<StandardMaterial3D> mat = memnew(StandardMaterial3D);
+ mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
+ mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
+ mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true);
+ mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
+
+ Array d;
+ d.resize(RS::ARRAY_MAX);
+ d[Mesh::ARRAY_VERTEX] = cursor_points;
+ d[Mesh::ARRAY_COLOR] = cursor_colors;
+ pos3d_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_LINES, d);
+ pos3d_mesh->surface_set_material(0, mat);
+}
+
+bool Position3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<Position3D>(p_spatial) != nullptr;
+}
+
+String Position3DGizmoPlugin::get_gizmo_name() const {
+ return "Position3D";
+}
+
+int Position3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+void Position3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ p_gizmo->clear();
+ p_gizmo->add_mesh(pos3d_mesh);
+ p_gizmo->add_collision_segments(cursor_points);
+}
+
+/////
+
+Skeleton3DGizmoPlugin::Skeleton3DGizmoPlugin() {
+ Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/skeleton", Color(1, 0.8, 0.4));
+ create_material("skeleton_material", gizmo_color);
+}
+
+bool Skeleton3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<Skeleton3D>(p_spatial) != nullptr;
+}
+
+String Skeleton3DGizmoPlugin::get_gizmo_name() const {
+ return "Skeleton3D";
+}
+
+int Skeleton3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ Skeleton3D *skel = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node());
+
+ p_gizmo->clear();
+
+ Ref<Material> material = get_material("skeleton_material", p_gizmo);
+
+ Ref<SurfaceTool> surface_tool(memnew(SurfaceTool));
+
+ surface_tool->begin(Mesh::PRIMITIVE_LINES);
+ surface_tool->set_material(material);
+ Vector<Transform3D> grests;
+ grests.resize(skel->get_bone_count());
+
+ Vector<int> bones;
+ Vector<float> weights;
+ bones.resize(4);
+ weights.resize(4);
+
+ for (int i = 0; i < 4; i++) {
+ bones.write[i] = 0;
+ weights.write[i] = 0;
+ }
+
+ weights.write[0] = 1;
+
+ AABB aabb;
+
+ Color bonecolor = Color(1.0, 0.4, 0.4, 0.3);
+ Color rootcolor = Color(0.4, 1.0, 0.4, 0.1);
+
+ for (int i_bone = 0; i_bone < skel->get_bone_count(); i_bone++) {
+ int i = skel->get_process_order(i_bone);
+
+ int parent = skel->get_bone_parent(i);
+
+ if (parent >= 0) {
+ grests.write[i] = grests[parent] * skel->get_bone_rest(i);
+
+ Vector3 v0 = grests[parent].origin;
+ Vector3 v1 = grests[i].origin;
+ Vector3 d = (v1 - v0).normalized();
+ float dist = v0.distance_to(v1);
+
+ //find closest axis
+ int closest = -1;
+ float closest_d = 0.0;
+
+ for (int j = 0; j < 3; j++) {
+ float dp = Math::abs(grests[parent].basis[j].normalized().dot(d));
+ if (j == 0 || dp > closest_d) {
+ closest = j;
+ }
+ }
+
+ //find closest other
+ Vector3 first;
+ Vector3 points[4];
+ int pointidx = 0;
+ for (int j = 0; j < 3; j++) {
+ bones.write[0] = parent;
+ surface_tool->set_bones(bones);
+ surface_tool->set_weights(weights);
+ surface_tool->set_color(rootcolor);
+ surface_tool->add_vertex(v0 - grests[parent].basis[j].normalized() * dist * 0.05);
+ surface_tool->set_bones(bones);
+ surface_tool->set_weights(weights);
+ surface_tool->set_color(rootcolor);
+ surface_tool->add_vertex(v0 + grests[parent].basis[j].normalized() * dist * 0.05);
+
+ if (j == closest) {
+ continue;
+ }
+
+ Vector3 axis;
+ if (first == Vector3()) {
+ axis = d.cross(d.cross(grests[parent].basis[j])).normalized();
+ first = axis;
+ } else {
+ axis = d.cross(first).normalized();
+ }
+
+ for (int k = 0; k < 2; k++) {
+ if (k == 1) {
+ axis = -axis;
+ }
+ Vector3 point = v0 + d * dist * 0.2;
+ point += axis * dist * 0.1;
+
+ bones.write[0] = parent;
+ surface_tool->set_bones(bones);
+ surface_tool->set_weights(weights);
+ surface_tool->set_color(bonecolor);
+ surface_tool->add_vertex(v0);
+ surface_tool->set_bones(bones);
+ surface_tool->set_weights(weights);
+ surface_tool->set_color(bonecolor);
+ surface_tool->add_vertex(point);
+
+ bones.write[0] = parent;
+ surface_tool->set_bones(bones);
+ surface_tool->set_weights(weights);
+ surface_tool->set_color(bonecolor);
+ surface_tool->add_vertex(point);
+ bones.write[0] = i;
+ surface_tool->set_bones(bones);
+ surface_tool->set_weights(weights);
+ surface_tool->set_color(bonecolor);
+ surface_tool->add_vertex(v1);
+ points[pointidx++] = point;
+ }
+ }
+
+ SWAP(points[1], points[2]);
+ for (int j = 0; j < 4; j++) {
+ bones.write[0] = parent;
+ surface_tool->set_bones(bones);
+ surface_tool->set_weights(weights);
+ surface_tool->set_color(bonecolor);
+ surface_tool->add_vertex(points[j]);
+ surface_tool->set_bones(bones);
+ surface_tool->set_weights(weights);
+ surface_tool->set_color(bonecolor);
+ surface_tool->add_vertex(points[(j + 1) % 4]);
+ }
+ } else {
+ grests.write[i] = skel->get_bone_rest(i);
+ bones.write[0] = i;
+ }
+ }
+
+ Ref<ArrayMesh> m = surface_tool->commit();
+ p_gizmo->add_mesh(m, Ref<Material>(), Transform3D(), skel->register_skin(Ref<Skin>()));
+}
+
+////
+
+PhysicalBone3DGizmoPlugin::PhysicalBone3DGizmoPlugin() {
+ create_material("joint_material", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/joint", Color(0.5, 0.8, 1)));
+}
+
+bool PhysicalBone3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<PhysicalBone3D>(p_spatial) != nullptr;
+}
+
+String PhysicalBone3DGizmoPlugin::get_gizmo_name() const {
+ return "PhysicalBone3D";
+}
+
+int PhysicalBone3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+void PhysicalBone3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ p_gizmo->clear();
+
+ PhysicalBone3D *physical_bone = Object::cast_to<PhysicalBone3D>(p_gizmo->get_spatial_node());
+
+ if (!physical_bone) {
+ return;
+ }
+
+ Skeleton3D *sk(physical_bone->find_skeleton_parent());
+ if (!sk) {
+ return;
+ }
+
+ PhysicalBone3D *pb(sk->get_physical_bone(physical_bone->get_bone_id()));
+ if (!pb) {
+ return;
+ }
+
+ PhysicalBone3D *pbp(sk->get_physical_bone_parent(physical_bone->get_bone_id()));
+ if (!pbp) {
+ return;
+ }
+
+ Vector<Vector3> points;
+
+ switch (physical_bone->get_joint_type()) {
+ case PhysicalBone3D::JOINT_TYPE_PIN: {
+ Joint3DGizmoPlugin::CreatePinJointGizmo(physical_bone->get_joint_offset(), points);
+ } break;
+ case PhysicalBone3D::JOINT_TYPE_CONE: {
+ const PhysicalBone3D::ConeJointData *cjd(static_cast<const PhysicalBone3D::ConeJointData *>(physical_bone->get_joint_data()));
+ Joint3DGizmoPlugin::CreateConeTwistJointGizmo(
+ physical_bone->get_joint_offset(),
+ physical_bone->get_global_transform() * physical_bone->get_joint_offset(),
+ pb->get_global_transform(),
+ pbp->get_global_transform(),
+ cjd->swing_span,
+ cjd->twist_span,
+ &points,
+ &points);
+ } break;
+ case PhysicalBone3D::JOINT_TYPE_HINGE: {
+ const PhysicalBone3D::HingeJointData *hjd(static_cast<const PhysicalBone3D::HingeJointData *>(physical_bone->get_joint_data()));
+ Joint3DGizmoPlugin::CreateHingeJointGizmo(
+ physical_bone->get_joint_offset(),
+ physical_bone->get_global_transform() * physical_bone->get_joint_offset(),
+ pb->get_global_transform(),
+ pbp->get_global_transform(),
+ hjd->angular_limit_lower,
+ hjd->angular_limit_upper,
+ hjd->angular_limit_enabled,
+ points,
+ &points,
+ &points);
+ } break;
+ case PhysicalBone3D::JOINT_TYPE_SLIDER: {
+ const PhysicalBone3D::SliderJointData *sjd(static_cast<const PhysicalBone3D::SliderJointData *>(physical_bone->get_joint_data()));
+ Joint3DGizmoPlugin::CreateSliderJointGizmo(
+ physical_bone->get_joint_offset(),
+ physical_bone->get_global_transform() * physical_bone->get_joint_offset(),
+ pb->get_global_transform(),
+ pbp->get_global_transform(),
+ sjd->angular_limit_lower,
+ sjd->angular_limit_upper,
+ sjd->linear_limit_lower,
+ sjd->linear_limit_upper,
+ points,
+ &points,
+ &points);
+ } break;
+ case PhysicalBone3D::JOINT_TYPE_6DOF: {
+ const PhysicalBone3D::SixDOFJointData *sdofjd(static_cast<const PhysicalBone3D::SixDOFJointData *>(physical_bone->get_joint_data()));
+ Joint3DGizmoPlugin::CreateGeneric6DOFJointGizmo(
+ physical_bone->get_joint_offset(),
+
+ physical_bone->get_global_transform() * physical_bone->get_joint_offset(),
+ pb->get_global_transform(),
+ pbp->get_global_transform(),
+
+ sdofjd->axis_data[0].angular_limit_lower,
+ sdofjd->axis_data[0].angular_limit_upper,
+ sdofjd->axis_data[0].linear_limit_lower,
+ sdofjd->axis_data[0].linear_limit_upper,
+ sdofjd->axis_data[0].angular_limit_enabled,
+ sdofjd->axis_data[0].linear_limit_enabled,
+
+ sdofjd->axis_data[1].angular_limit_lower,
+ sdofjd->axis_data[1].angular_limit_upper,
+ sdofjd->axis_data[1].linear_limit_lower,
+ sdofjd->axis_data[1].linear_limit_upper,
+ sdofjd->axis_data[1].angular_limit_enabled,
+ sdofjd->axis_data[1].linear_limit_enabled,
+
+ sdofjd->axis_data[2].angular_limit_lower,
+ sdofjd->axis_data[2].angular_limit_upper,
+ sdofjd->axis_data[2].linear_limit_lower,
+ sdofjd->axis_data[2].linear_limit_upper,
+ sdofjd->axis_data[2].angular_limit_enabled,
+ sdofjd->axis_data[2].linear_limit_enabled,
+
+ points,
+ &points,
+ &points);
+ } break;
+ default:
+ return;
+ }
+
+ Ref<Material> material = get_material("joint_material", p_gizmo);
+
+ p_gizmo->add_collision_segments(points);
+ p_gizmo->add_lines(points, material);
+}
+
+/////
+
+RayCast3DGizmoPlugin::RayCast3DGizmoPlugin() {
+ const Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1));
+ 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);
+}
+
+bool RayCast3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<RayCast3D>(p_spatial) != nullptr;
+}
+
+String RayCast3DGizmoPlugin::get_gizmo_name() const {
+ return "RayCast3D";
+}
+
+int RayCast3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+void RayCast3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ RayCast3D *raycast = Object::cast_to<RayCast3D>(p_gizmo->get_spatial_node());
+
+ p_gizmo->clear();
+
+ const Ref<StandardMaterial3D> material = raycast->is_enabled() ? raycast->get_debug_material() : get_material("shape_material_disabled");
+
+ p_gizmo->add_lines(raycast->get_debug_line_vertices(), material);
+
+ if (raycast->get_debug_shape_thickness() > 1) {
+ p_gizmo->add_vertices(raycast->get_debug_shape_vertices(), material, Mesh::PRIMITIVE_TRIANGLE_STRIP);
+ }
+
+ p_gizmo->add_collision_segments(raycast->get_debug_line_vertices());
+}
+
+/////
+
+void SpringArm3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ SpringArm3D *spring_arm = Object::cast_to<SpringArm3D>(p_gizmo->get_spatial_node());
+
+ p_gizmo->clear();
+
+ Vector<Vector3> lines;
+
+ lines.push_back(Vector3());
+ lines.push_back(Vector3(0, 0, 1.0) * spring_arm->get_length());
+
+ Ref<StandardMaterial3D> material = get_material("shape_material", p_gizmo);
+
+ p_gizmo->add_lines(lines, material);
+ p_gizmo->add_collision_segments(lines);
+}
+
+SpringArm3DGizmoPlugin::SpringArm3DGizmoPlugin() {
+ Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1));
+ create_material("shape_material", gizmo_color);
+}
+
+bool SpringArm3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<SpringArm3D>(p_spatial) != nullptr;
+}
+
+String SpringArm3DGizmoPlugin::get_gizmo_name() const {
+ return "SpringArm3D";
+}
+
+int SpringArm3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+/////
+
+VehicleWheel3DGizmoPlugin::VehicleWheel3DGizmoPlugin() {
+ Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1));
+ create_material("shape_material", gizmo_color);
+}
+
+bool VehicleWheel3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<VehicleWheel3D>(p_spatial) != nullptr;
+}
+
+String VehicleWheel3DGizmoPlugin::get_gizmo_name() const {
+ return "VehicleWheel3D";
+}
+
+int VehicleWheel3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+void VehicleWheel3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ VehicleWheel3D *car_wheel = Object::cast_to<VehicleWheel3D>(p_gizmo->get_spatial_node());
+
+ p_gizmo->clear();
+
+ Vector<Vector3> points;
+
+ float r = car_wheel->get_radius();
+ const int skip = 10;
+ for (int i = 0; i <= 360; i += skip) {
+ float ra = Math::deg2rad((float)i);
+ float rb = Math::deg2rad((float)i + skip);
+ Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * r;
+ Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * r;
+
+ points.push_back(Vector3(0, a.x, a.y));
+ points.push_back(Vector3(0, b.x, b.y));
+
+ const int springsec = 4;
+
+ for (int j = 0; j < springsec; j++) {
+ float t = car_wheel->get_suspension_rest_length() * 5;
+ points.push_back(Vector3(a.x, i / 360.0 * t / springsec + j * (t / springsec), a.y) * 0.2);
+ points.push_back(Vector3(b.x, (i + skip) / 360.0 * t / springsec + j * (t / springsec), b.y) * 0.2);
+ }
+ }
+
+ //travel
+ points.push_back(Vector3(0, 0, 0));
+ points.push_back(Vector3(0, car_wheel->get_suspension_rest_length(), 0));
+
+ //axis
+ points.push_back(Vector3(r * 0.2, car_wheel->get_suspension_rest_length(), 0));
+ points.push_back(Vector3(-r * 0.2, car_wheel->get_suspension_rest_length(), 0));
+ //axis
+ points.push_back(Vector3(r * 0.2, 0, 0));
+ points.push_back(Vector3(-r * 0.2, 0, 0));
+
+ //forward line
+ points.push_back(Vector3(0, -r, 0));
+ points.push_back(Vector3(0, -r, r * 2));
+ points.push_back(Vector3(0, -r, r * 2));
+ points.push_back(Vector3(r * 2 * 0.2, -r, r * 2 * 0.8));
+ points.push_back(Vector3(0, -r, r * 2));
+ points.push_back(Vector3(-r * 2 * 0.2, -r, r * 2 * 0.8));
+
+ Ref<Material> material = get_material("shape_material", p_gizmo);
+
+ p_gizmo->add_lines(points, material);
+ p_gizmo->add_collision_segments(points);
+}
+
+///////////
+
+SoftBody3DGizmoPlugin::SoftBody3DGizmoPlugin() {
+ Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1));
+ create_material("shape_material", gizmo_color);
+ create_handle_material("handles");
+}
+
+bool SoftBody3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<SoftBody3D>(p_spatial) != nullptr;
+}
+
+String SoftBody3DGizmoPlugin::get_gizmo_name() const {
+ return "SoftBody3D";
+}
+
+int SoftBody3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+bool SoftBody3DGizmoPlugin::is_selectable_when_hidden() const {
+ return true;
+}
+
+void SoftBody3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ SoftBody3D *soft_body = Object::cast_to<SoftBody3D>(p_gizmo->get_spatial_node());
+
+ p_gizmo->clear();
+
+ if (!soft_body || soft_body->get_mesh().is_null()) {
+ return;
+ }
+
+ // find mesh
+
+ Vector<Vector3> lines;
+
+ soft_body->get_mesh()->generate_debug_mesh_lines(lines);
+
+ if (!lines.size()) {
+ return;
+ }
+
+ Ref<TriangleMesh> tm = soft_body->get_mesh()->generate_triangle_mesh();
+
+ Vector<Vector3> points;
+ for (int i = 0; i < soft_body->get_mesh()->get_surface_count(); i++) {
+ Array arrays = soft_body->get_mesh()->surface_get_arrays(i);
+ ERR_CONTINUE(arrays.is_empty());
+
+ const Vector<Vector3> &vertices = arrays[Mesh::ARRAY_VERTEX];
+ points.append_array(vertices);
+ }
+
+ Ref<Material> material = get_material("shape_material", p_gizmo);
+
+ p_gizmo->add_lines(lines, material);
+ p_gizmo->add_handles(points, get_material("handles"));
+ p_gizmo->add_collision_triangles(tm);
+}
+
+String SoftBody3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ return "SoftBody3D pin point";
+}
+
+Variant SoftBody3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ SoftBody3D *soft_body = Object::cast_to<SoftBody3D>(p_gizmo->get_spatial_node());
+ return Variant(soft_body->is_point_pinned(p_id));
+}
+
+void SoftBody3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) const {
+ SoftBody3D *soft_body = Object::cast_to<SoftBody3D>(p_gizmo->get_spatial_node());
+ soft_body->pin_point_toggle(p_id);
+}
+
+bool SoftBody3DGizmoPlugin::is_handle_highlighted(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ SoftBody3D *soft_body = Object::cast_to<SoftBody3D>(p_gizmo->get_spatial_node());
+ return soft_body->is_point_pinned(p_id);
+}
+
+///////////
+
+VisibleOnScreenNotifier3DGizmoPlugin::VisibleOnScreenNotifier3DGizmoPlugin() {
+ Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/visibility_notifier", Color(0.8, 0.5, 0.7));
+ create_material("visibility_notifier_material", gizmo_color);
+ gizmo_color.a = 0.1;
+ create_material("visibility_notifier_solid_material", gizmo_color);
+ create_handle_material("handles");
+}
+
+bool VisibleOnScreenNotifier3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<VisibleOnScreenNotifier3D>(p_spatial) != nullptr;
+}
+
+String VisibleOnScreenNotifier3DGizmoPlugin::get_gizmo_name() const {
+ return "VisibleOnScreenNotifier3D";
+}
+
+int VisibleOnScreenNotifier3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+String VisibleOnScreenNotifier3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ switch (p_id) {
+ case 0:
+ return "Size X";
+ case 1:
+ return "Size Y";
+ case 2:
+ return "Size Z";
+ case 3:
+ return "Pos X";
+ case 4:
+ return "Pos Y";
+ case 5:
+ return "Pos Z";
+ }
+
+ return "";
+}
+
+Variant VisibleOnScreenNotifier3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ VisibleOnScreenNotifier3D *notifier = Object::cast_to<VisibleOnScreenNotifier3D>(p_gizmo->get_spatial_node());
+ return notifier->get_aabb();
+}
+
+void VisibleOnScreenNotifier3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const {
+ VisibleOnScreenNotifier3D *notifier = Object::cast_to<VisibleOnScreenNotifier3D>(p_gizmo->get_spatial_node());
+
+ Transform3D gt = notifier->get_global_transform();
+
+ Transform3D gi = gt.affine_inverse();
+
+ bool move = p_id >= 3;
+ p_id = p_id % 3;
+
+ AABB aabb = notifier->get_aabb();
+ Vector3 ray_from = p_camera->project_ray_origin(p_point);
+ Vector3 ray_dir = p_camera->project_ray_normal(p_point);
+
+ Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 4096) };
+
+ Vector3 ofs = aabb.position + aabb.size * 0.5;
+
+ Vector3 axis;
+ axis[p_id] = 1.0;
+
+ if (move) {
+ Vector3 ra, rb;
+ Geometry3D::get_closest_points_between_segments(ofs - axis * 4096, ofs + axis * 4096, sg[0], sg[1], ra, rb);
+
+ float d = ra[p_id];
+ if (Node3DEditor::get_singleton()->is_snap_enabled()) {
+ d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
+ }
+
+ aabb.position[p_id] = d - 1.0 - aabb.size[p_id] * 0.5;
+ notifier->set_aabb(aabb);
+
+ } else {
+ Vector3 ra, rb;
+ Geometry3D::get_closest_points_between_segments(ofs, ofs + axis * 4096, sg[0], sg[1], ra, rb);
+
+ float d = ra[p_id] - ofs[p_id];
+ if (Node3DEditor::get_singleton()->is_snap_enabled()) {
+ d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
+ }
+
+ if (d < 0.001) {
+ d = 0.001;
+ }
+ //resize
+ aabb.position[p_id] = (aabb.position[p_id] + aabb.size[p_id] * 0.5) - d;
+ aabb.size[p_id] = d * 2;
+ notifier->set_aabb(aabb);
+ }
+}
+
+void VisibleOnScreenNotifier3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) const {
+ VisibleOnScreenNotifier3D *notifier = Object::cast_to<VisibleOnScreenNotifier3D>(p_gizmo->get_spatial_node());
+
+ if (p_cancel) {
+ notifier->set_aabb(p_restore);
+ return;
+ }
+
+ UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
+ ur->create_action(TTR("Change Notifier AABB"));
+ ur->add_do_method(notifier, "set_aabb", notifier->get_aabb());
+ ur->add_undo_method(notifier, "set_aabb", p_restore);
+ ur->commit_action();
+}
+
+void VisibleOnScreenNotifier3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ VisibleOnScreenNotifier3D *notifier = Object::cast_to<VisibleOnScreenNotifier3D>(p_gizmo->get_spatial_node());
+
+ p_gizmo->clear();
+
+ Vector<Vector3> lines;
+ AABB aabb = notifier->get_aabb();
+
+ for (int i = 0; i < 12; i++) {
+ Vector3 a, b;
+ aabb.get_edge(i, a, b);
+ lines.push_back(a);
+ lines.push_back(b);
+ }
+
+ Vector<Vector3> handles;
+
+ for (int i = 0; i < 3; i++) {
+ Vector3 ax;
+ ax[i] = aabb.position[i] + aabb.size[i];
+ ax[(i + 1) % 3] = aabb.position[(i + 1) % 3] + aabb.size[(i + 1) % 3] * 0.5;
+ ax[(i + 2) % 3] = aabb.position[(i + 2) % 3] + aabb.size[(i + 2) % 3] * 0.5;
+ handles.push_back(ax);
+ }
+
+ Vector3 center = aabb.position + aabb.size * 0.5;
+ for (int i = 0; i < 3; i++) {
+ Vector3 ax;
+ ax[i] = 1.0;
+ handles.push_back(center + ax);
+ lines.push_back(center);
+ lines.push_back(center + ax);
+ }
+
+ Ref<Material> material = get_material("visibility_notifier_material", p_gizmo);
+
+ p_gizmo->add_lines(lines, material);
+ p_gizmo->add_collision_segments(lines);
+
+ if (p_gizmo->is_selected()) {
+ Ref<Material> solid_material = get_material("visibility_notifier_solid_material", p_gizmo);
+ p_gizmo->add_solid_box(solid_material, aabb.get_size(), aabb.get_position() + aabb.get_size() / 2.0);
+ }
+
+ p_gizmo->add_handles(handles, get_material("handles"));
+}
+
+////
+
+CPUParticles3DGizmoPlugin::CPUParticles3DGizmoPlugin() {
+ create_icon_material("particles_icon", Node3DEditor::get_singleton()->get_theme_icon(SNAME("GizmoCPUParticles3D"), SNAME("EditorIcons")));
+}
+
+bool CPUParticles3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<CPUParticles3D>(p_spatial) != nullptr;
+}
+
+String CPUParticles3DGizmoPlugin::get_gizmo_name() const {
+ return "CPUParticles3D";
+}
+
+int CPUParticles3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+bool CPUParticles3DGizmoPlugin::is_selectable_when_hidden() const {
+ return true;
+}
+
+void CPUParticles3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ Ref<Material> icon = get_material("particles_icon", p_gizmo);
+ p_gizmo->add_unscaled_billboard(icon, 0.05);
+}
+
+////
+
+GPUParticles3DGizmoPlugin::GPUParticles3DGizmoPlugin() {
+ Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/particles", Color(0.8, 0.7, 0.4));
+ create_material("particles_material", gizmo_color);
+ gizmo_color.a = 0.1;
+ create_material("particles_solid_material", gizmo_color);
+ create_icon_material("particles_icon", Node3DEditor::get_singleton()->get_theme_icon(SNAME("GizmoGPUParticles3D"), SNAME("EditorIcons")));
+ create_handle_material("handles");
+}
+
+bool GPUParticles3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<GPUParticles3D>(p_spatial) != nullptr;
+}
+
+String GPUParticles3DGizmoPlugin::get_gizmo_name() const {
+ return "GPUParticles3D";
+}
+
+int GPUParticles3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+bool GPUParticles3DGizmoPlugin::is_selectable_when_hidden() const {
+ return true;
+}
+
+String GPUParticles3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ switch (p_id) {
+ case 0:
+ return "Size X";
+ case 1:
+ return "Size Y";
+ case 2:
+ return "Size Z";
+ case 3:
+ return "Pos X";
+ case 4:
+ return "Pos Y";
+ case 5:
+ return "Pos Z";
+ }
+
+ return "";
+}
+
+Variant GPUParticles3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(p_gizmo->get_spatial_node());
+ return particles->get_visibility_aabb();
+}
+
+void GPUParticles3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const {
+ GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(p_gizmo->get_spatial_node());
+
+ Transform3D gt = particles->get_global_transform();
+ Transform3D gi = gt.affine_inverse();
+
+ bool move = p_id >= 3;
+ p_id = p_id % 3;
+
+ AABB aabb = particles->get_visibility_aabb();
+ Vector3 ray_from = p_camera->project_ray_origin(p_point);
+ Vector3 ray_dir = p_camera->project_ray_normal(p_point);
+
+ Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 4096) };
+
+ Vector3 ofs = aabb.position + aabb.size * 0.5;
+
+ Vector3 axis;
+ axis[p_id] = 1.0;
+
+ if (move) {
+ Vector3 ra, rb;
+ Geometry3D::get_closest_points_between_segments(ofs - axis * 4096, ofs + axis * 4096, sg[0], sg[1], ra, rb);
+
+ float d = ra[p_id];
+ if (Node3DEditor::get_singleton()->is_snap_enabled()) {
+ d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
+ }
+
+ aabb.position[p_id] = d - 1.0 - aabb.size[p_id] * 0.5;
+ particles->set_visibility_aabb(aabb);
+
+ } else {
+ Vector3 ra, rb;
+ Geometry3D::get_closest_points_between_segments(ofs, ofs + axis * 4096, sg[0], sg[1], ra, rb);
+
+ float d = ra[p_id] - ofs[p_id];
+ if (Node3DEditor::get_singleton()->is_snap_enabled()) {
+ d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
+ }
+
+ if (d < 0.001) {
+ d = 0.001;
+ }
+ //resize
+ aabb.position[p_id] = (aabb.position[p_id] + aabb.size[p_id] * 0.5) - d;
+ aabb.size[p_id] = d * 2;
+ particles->set_visibility_aabb(aabb);
+ }
+}
+
+void GPUParticles3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) const {
+ GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(p_gizmo->get_spatial_node());
+
+ if (p_cancel) {
+ particles->set_visibility_aabb(p_restore);
+ return;
+ }
+
+ UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
+ ur->create_action(TTR("Change Particles AABB"));
+ ur->add_do_method(particles, "set_visibility_aabb", particles->get_visibility_aabb());
+ ur->add_undo_method(particles, "set_visibility_aabb", p_restore);
+ ur->commit_action();
+}
+
+void GPUParticles3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(p_gizmo->get_spatial_node());
+
+ p_gizmo->clear();
+
+ Vector<Vector3> lines;
+ AABB aabb = particles->get_visibility_aabb();
+
+ for (int i = 0; i < 12; i++) {
+ Vector3 a, b;
+ aabb.get_edge(i, a, b);
+ lines.push_back(a);
+ lines.push_back(b);
+ }
+
+ Vector<Vector3> handles;
+
+ for (int i = 0; i < 3; i++) {
+ Vector3 ax;
+ ax[i] = aabb.position[i] + aabb.size[i];
+ ax[(i + 1) % 3] = aabb.position[(i + 1) % 3] + aabb.size[(i + 1) % 3] * 0.5;
+ ax[(i + 2) % 3] = aabb.position[(i + 2) % 3] + aabb.size[(i + 2) % 3] * 0.5;
+ handles.push_back(ax);
+ }
+
+ Vector3 center = aabb.position + aabb.size * 0.5;
+ for (int i = 0; i < 3; i++) {
+ Vector3 ax;
+ ax[i] = 1.0;
+ handles.push_back(center + ax);
+ lines.push_back(center);
+ lines.push_back(center + ax);
+ }
+
+ Ref<Material> material = get_material("particles_material", p_gizmo);
+ Ref<Material> icon = get_material("particles_icon", p_gizmo);
+
+ p_gizmo->add_lines(lines, material);
+
+ if (p_gizmo->is_selected()) {
+ Ref<Material> solid_material = get_material("particles_solid_material", p_gizmo);
+ p_gizmo->add_solid_box(solid_material, aabb.get_size(), aabb.get_position() + aabb.get_size() / 2.0);
+ }
+
+ p_gizmo->add_handles(handles, get_material("handles"));
+ p_gizmo->add_unscaled_billboard(icon, 0.05);
+}
+
+////
+
+GPUParticlesCollision3DGizmoPlugin::GPUParticlesCollision3DGizmoPlugin() {
+ Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/particle_collision", Color(0.5, 0.7, 1));
+ create_material("shape_material", gizmo_color);
+ gizmo_color.a = 0.15;
+ create_material("shape_material_internal", gizmo_color);
+
+ create_handle_material("handles");
+}
+
+bool GPUParticlesCollision3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return (Object::cast_to<GPUParticlesCollision3D>(p_spatial) != nullptr) || (Object::cast_to<GPUParticlesAttractor3D>(p_spatial) != nullptr);
+}
+
+String GPUParticlesCollision3DGizmoPlugin::get_gizmo_name() const {
+ return "GPUParticlesCollision3D";
+}
+
+int GPUParticlesCollision3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+String GPUParticlesCollision3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ const Node3D *cs = p_gizmo->get_spatial_node();
+
+ if (Object::cast_to<GPUParticlesCollisionSphere>(cs) || Object::cast_to<GPUParticlesAttractorSphere>(cs)) {
+ return "Radius";
+ }
+
+ if (Object::cast_to<GPUParticlesCollisionBox>(cs) || Object::cast_to<GPUParticlesAttractorBox>(cs) || Object::cast_to<GPUParticlesAttractorVectorField>(cs) || Object::cast_to<GPUParticlesCollisionSDF>(cs) || Object::cast_to<GPUParticlesCollisionHeightField>(cs)) {
+ return "Extents";
+ }
+
+ return "";
+}
+
+Variant GPUParticlesCollision3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ const Node3D *cs = p_gizmo->get_spatial_node();
+
+ if (Object::cast_to<GPUParticlesCollisionSphere>(cs) || Object::cast_to<GPUParticlesAttractorSphere>(cs)) {
+ return p_gizmo->get_spatial_node()->call("get_radius");
+ }
+
+ if (Object::cast_to<GPUParticlesCollisionBox>(cs) || Object::cast_to<GPUParticlesAttractorBox>(cs) || Object::cast_to<GPUParticlesAttractorVectorField>(cs) || Object::cast_to<GPUParticlesCollisionSDF>(cs) || Object::cast_to<GPUParticlesCollisionHeightField>(cs)) {
+ return Vector3(p_gizmo->get_spatial_node()->call("get_extents"));
+ }
+
+ return Variant();
+}
+
+void GPUParticlesCollision3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const {
+ Node3D *sn = p_gizmo->get_spatial_node();
+
+ Transform3D gt = sn->get_global_transform();
+ Transform3D gi = gt.affine_inverse();
+
+ Vector3 ray_from = p_camera->project_ray_origin(p_point);
+ Vector3 ray_dir = p_camera->project_ray_normal(p_point);
+
+ Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 4096) };
+
+ if (Object::cast_to<GPUParticlesCollisionSphere>(sn) || Object::cast_to<GPUParticlesAttractorSphere>(sn)) {
+ Vector3 ra, rb;
+ Geometry3D::get_closest_points_between_segments(Vector3(), Vector3(4096, 0, 0), sg[0], sg[1], ra, rb);
+ float d = ra.x;
+ if (Node3DEditor::get_singleton()->is_snap_enabled()) {
+ d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
+ }
+
+ if (d < 0.001) {
+ d = 0.001;
+ }
+
+ sn->call("set_radius", d);
+ }
+
+ if (Object::cast_to<GPUParticlesCollisionBox>(sn) || Object::cast_to<GPUParticlesAttractorBox>(sn) || Object::cast_to<GPUParticlesAttractorVectorField>(sn) || Object::cast_to<GPUParticlesCollisionSDF>(sn) || Object::cast_to<GPUParticlesCollisionHeightField>(sn)) {
+ Vector3 axis;
+ axis[p_id] = 1.0;
+ Vector3 ra, rb;
+ Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb);
+ float d = ra[p_id];
+ if (Node3DEditor::get_singleton()->is_snap_enabled()) {
+ d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
+ }
+
+ if (d < 0.001) {
+ d = 0.001;
+ }
+
+ Vector3 he = sn->call("get_extents");
+ he[p_id] = d;
+ sn->call("set_extents", he);
+ }
+}
+
+void GPUParticlesCollision3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) const {
+ Node3D *sn = p_gizmo->get_spatial_node();
+
+ if (Object::cast_to<GPUParticlesCollisionSphere>(sn) || Object::cast_to<GPUParticlesAttractorSphere>(sn)) {
+ if (p_cancel) {
+ sn->call("set_radius", p_restore);
+ return;
+ }
+
+ UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
+ ur->create_action(TTR("Change Radius"));
+ ur->add_do_method(sn, "set_radius", sn->call("get_radius"));
+ ur->add_undo_method(sn, "set_radius", p_restore);
+ ur->commit_action();
+ }
+
+ if (Object::cast_to<GPUParticlesCollisionBox>(sn) || Object::cast_to<GPUParticlesAttractorBox>(sn) || Object::cast_to<GPUParticlesAttractorVectorField>(sn) || Object::cast_to<GPUParticlesCollisionSDF>(sn) || Object::cast_to<GPUParticlesCollisionHeightField>(sn)) {
+ if (p_cancel) {
+ sn->call("set_extents", p_restore);
+ return;
+ }
+
+ UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
+ ur->create_action(TTR("Change Box Shape Extents"));
+ ur->add_do_method(sn, "set_extents", sn->call("get_extents"));
+ ur->add_undo_method(sn, "set_extents", p_restore);
+ ur->commit_action();
+ }
+}
+
+void GPUParticlesCollision3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ Node3D *cs = p_gizmo->get_spatial_node();
+
+ print_line("redraw request " + itos(cs != nullptr));
+ p_gizmo->clear();
+
+ const Ref<Material> material =
+ get_material("shape_material", p_gizmo);
+ const Ref<Material> material_internal =
+ get_material("shape_material_internal", p_gizmo);
+
+ Ref<Material> handles_material = get_material("handles");
+
+ if (Object::cast_to<GPUParticlesCollisionSphere>(cs) || Object::cast_to<GPUParticlesAttractorSphere>(cs)) {
+ float r = cs->call("get_radius");
+
+ Vector<Vector3> points;
+
+ for (int i = 0; i <= 360; i++) {
+ float ra = Math::deg2rad((float)i);
+ float rb = Math::deg2rad((float)i + 1);
+ Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * r;
+ Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * r;
+
+ points.push_back(Vector3(a.x, 0, a.y));
+ points.push_back(Vector3(b.x, 0, b.y));
+ points.push_back(Vector3(0, a.x, a.y));
+ points.push_back(Vector3(0, b.x, b.y));
+ points.push_back(Vector3(a.x, a.y, 0));
+ points.push_back(Vector3(b.x, b.y, 0));
+ }
+
+ Vector<Vector3> collision_segments;
+
+ for (int i = 0; i < 64; i++) {
+ float ra = i * (Math_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<Vector3> handles;
+ handles.push_back(Vector3(r, 0, 0));
+ p_gizmo->add_handles(handles, handles_material);
+ }
+
+ if (Object::cast_to<GPUParticlesCollisionBox>(cs) || Object::cast_to<GPUParticlesAttractorBox>(cs) || Object::cast_to<GPUParticlesAttractorVectorField>(cs) || Object::cast_to<GPUParticlesCollisionSDF>(cs) || Object::cast_to<GPUParticlesCollisionHeightField>(cs)) {
+ Vector<Vector3> lines;
+ AABB aabb;
+ aabb.position = -cs->call("get_extents").operator Vector3();
+ aabb.size = aabb.position * -2;
+
+ for (int i = 0; i < 12; i++) {
+ Vector3 a, b;
+ aabb.get_edge(i, a, b);
+ lines.push_back(a);
+ lines.push_back(b);
+ }
+
+ Vector<Vector3> handles;
+
+ for (int i = 0; i < 3; i++) {
+ Vector3 ax;
+ ax[i] = cs->call("get_extents").operator Vector3()[i];
+ handles.push_back(ax);
+ }
+
+ p_gizmo->add_lines(lines, material);
+ p_gizmo->add_collision_segments(lines);
+ p_gizmo->add_handles(handles, handles_material);
+
+ GPUParticlesCollisionSDF *col_sdf = Object::cast_to<GPUParticlesCollisionSDF>(cs);
+ if (col_sdf) {
+ static const int subdivs[GPUParticlesCollisionSDF::RESOLUTION_MAX] = { 16, 32, 64, 128, 256, 512 };
+ int subdiv = subdivs[col_sdf->get_resolution()];
+ float cell_size = aabb.get_longest_axis_size() / subdiv;
+
+ lines.clear();
+
+ for (int i = 1; i < subdiv; i++) {
+ for (int j = 0; j < 3; j++) {
+ if (cell_size * i > aabb.size[j]) {
+ continue;
+ }
+
+ Vector2 dir;
+ dir[j] = 1.0;
+ Vector2 ta, tb;
+ int j_n1 = (j + 1) % 3;
+ int j_n2 = (j + 2) % 3;
+ ta[j_n1] = 1.0;
+ tb[j_n2] = 1.0;
+
+ for (int k = 0; k < 4; k++) {
+ Vector3 from = aabb.position, to = aabb.position;
+ from[j] += cell_size * i;
+ to[j] += cell_size * i;
+
+ if (k & 1) {
+ to[j_n1] += aabb.size[j_n1];
+ } else {
+ to[j_n2] += aabb.size[j_n2];
+ }
+
+ if (k & 2) {
+ from[j_n1] += aabb.size[j_n1];
+ from[j_n2] += aabb.size[j_n2];
+ }
+
+ lines.push_back(from);
+ lines.push_back(to);
+ }
+ }
+ }
+
+ p_gizmo->add_lines(lines, material_internal);
+ }
+ }
+}
+
+/////
+
+////
+
+ReflectionProbeGizmoPlugin::ReflectionProbeGizmoPlugin() {
+ Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/reflection_probe", Color(0.6, 1, 0.5));
+
+ create_material("reflection_probe_material", gizmo_color);
+
+ gizmo_color.a = 0.5;
+ create_material("reflection_internal_material", gizmo_color);
+
+ gizmo_color.a = 0.1;
+ create_material("reflection_probe_solid_material", gizmo_color);
+
+ create_icon_material("reflection_probe_icon", Node3DEditor::get_singleton()->get_theme_icon(SNAME("GizmoReflectionProbe"), SNAME("EditorIcons")));
+ create_handle_material("handles");
+}
+
+bool ReflectionProbeGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<ReflectionProbe>(p_spatial) != nullptr;
+}
+
+String ReflectionProbeGizmoPlugin::get_gizmo_name() const {
+ return "ReflectionProbe";
+}
+
+int ReflectionProbeGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+String ReflectionProbeGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ switch (p_id) {
+ case 0:
+ return "Extents X";
+ case 1:
+ return "Extents Y";
+ case 2:
+ return "Extents Z";
+ case 3:
+ return "Origin X";
+ case 4:
+ return "Origin Y";
+ case 5:
+ return "Origin Z";
+ }
+
+ return "";
+}
+
+Variant ReflectionProbeGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ ReflectionProbe *probe = Object::cast_to<ReflectionProbe>(p_gizmo->get_spatial_node());
+ return AABB(probe->get_extents(), probe->get_origin_offset());
+}
+
+void ReflectionProbeGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const {
+ ReflectionProbe *probe = Object::cast_to<ReflectionProbe>(p_gizmo->get_spatial_node());
+ Transform3D gt = probe->get_global_transform();
+
+ Transform3D gi = gt.affine_inverse();
+
+ if (p_id < 3) {
+ Vector3 extents = probe->get_extents();
+
+ Vector3 ray_from = p_camera->project_ray_origin(p_point);
+ Vector3 ray_dir = p_camera->project_ray_normal(p_point);
+
+ Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 16384) };
+
+ Vector3 axis;
+ axis[p_id] = 1.0;
+
+ Vector3 ra, rb;
+ Geometry3D::get_closest_points_between_segments(Vector3(), axis * 16384, sg[0], sg[1], ra, rb);
+ float d = ra[p_id];
+ if (Node3DEditor::get_singleton()->is_snap_enabled()) {
+ d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
+ }
+
+ if (d < 0.001) {
+ d = 0.001;
+ }
+
+ extents[p_id] = d;
+ probe->set_extents(extents);
+ } else {
+ p_id -= 3;
+
+ Vector3 origin = probe->get_origin_offset();
+ origin[p_id] = 0;
+
+ Vector3 ray_from = p_camera->project_ray_origin(p_point);
+ Vector3 ray_dir = p_camera->project_ray_normal(p_point);
+
+ Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 16384) };
+
+ Vector3 axis;
+ axis[p_id] = 1.0;
+
+ Vector3 ra, rb;
+ Geometry3D::get_closest_points_between_segments(origin - axis * 16384, origin + axis * 16384, sg[0], sg[1], ra, rb);
+ // Adjust the actual position to account for the gizmo handle position
+ float d = ra[p_id] + 0.25;
+ if (Node3DEditor::get_singleton()->is_snap_enabled()) {
+ d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
+ }
+
+ origin[p_id] = d;
+ probe->set_origin_offset(origin);
+ }
+}
+
+void ReflectionProbeGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) const {
+ ReflectionProbe *probe = Object::cast_to<ReflectionProbe>(p_gizmo->get_spatial_node());
+
+ AABB restore = p_restore;
+
+ if (p_cancel) {
+ probe->set_extents(restore.position);
+ probe->set_origin_offset(restore.size);
+ return;
+ }
+
+ UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
+ ur->create_action(TTR("Change Probe Extents"));
+ ur->add_do_method(probe, "set_extents", probe->get_extents());
+ ur->add_do_method(probe, "set_origin_offset", probe->get_origin_offset());
+ ur->add_undo_method(probe, "set_extents", restore.position);
+ ur->add_undo_method(probe, "set_origin_offset", restore.size);
+ ur->commit_action();
+}
+
+void ReflectionProbeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ ReflectionProbe *probe = Object::cast_to<ReflectionProbe>(p_gizmo->get_spatial_node());
+
+ p_gizmo->clear();
+
+ Vector<Vector3> lines;
+ Vector<Vector3> internal_lines;
+ Vector3 extents = probe->get_extents();
+
+ AABB aabb;
+ aabb.position = -extents;
+ aabb.size = extents * 2;
+
+ for (int i = 0; i < 12; i++) {
+ Vector3 a, b;
+ aabb.get_edge(i, a, b);
+ lines.push_back(a);
+ lines.push_back(b);
+ }
+
+ for (int i = 0; i < 8; i++) {
+ Vector3 ep = aabb.get_endpoint(i);
+ internal_lines.push_back(probe->get_origin_offset());
+ internal_lines.push_back(ep);
+ }
+
+ Vector<Vector3> handles;
+
+ for (int i = 0; i < 3; i++) {
+ Vector3 ax;
+ ax[i] = aabb.position[i] + aabb.size[i];
+ handles.push_back(ax);
+ }
+
+ for (int i = 0; i < 3; i++) {
+ Vector3 orig_handle = probe->get_origin_offset();
+ orig_handle[i] -= 0.25;
+ lines.push_back(orig_handle);
+ handles.push_back(orig_handle);
+
+ orig_handle[i] += 0.5;
+ lines.push_back(orig_handle);
+ }
+
+ Ref<Material> material = get_material("reflection_probe_material", p_gizmo);
+ Ref<Material> material_internal = get_material("reflection_internal_material", p_gizmo);
+ Ref<Material> icon = get_material("reflection_probe_icon", p_gizmo);
+
+ p_gizmo->add_lines(lines, material);
+ p_gizmo->add_lines(internal_lines, material_internal);
+
+ if (p_gizmo->is_selected()) {
+ Ref<Material> solid_material = get_material("reflection_probe_solid_material", p_gizmo);
+ p_gizmo->add_solid_box(solid_material, probe->get_extents() * 2.0);
+ }
+
+ p_gizmo->add_unscaled_billboard(icon, 0.05);
+ p_gizmo->add_handles(handles, get_material("handles"));
+}
+
+///////////////////////////////
+
+////
+
+DecalGizmoPlugin::DecalGizmoPlugin() {
+ Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/decal", Color(0.6, 0.5, 1.0));
+
+ create_material("decal_material", gizmo_color);
+
+ create_handle_material("handles");
+}
+
+bool DecalGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<Decal>(p_spatial) != nullptr;
+}
+
+String DecalGizmoPlugin::get_gizmo_name() const {
+ return "Decal";
+}
+
+int DecalGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+String DecalGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ switch (p_id) {
+ case 0:
+ return "Extents X";
+ case 1:
+ return "Extents Y";
+ case 2:
+ return "Extents Z";
+ }
+
+ return "";
+}
+
+Variant DecalGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ Decal *decal = Object::cast_to<Decal>(p_gizmo->get_spatial_node());
+ return decal->get_extents();
+}
+
+void DecalGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const {
+ Decal *decal = Object::cast_to<Decal>(p_gizmo->get_spatial_node());
+ Transform3D gt = decal->get_global_transform();
+
+ Transform3D gi = gt.affine_inverse();
+
+ Vector3 extents = decal->get_extents();
+
+ Vector3 ray_from = p_camera->project_ray_origin(p_point);
+ Vector3 ray_dir = p_camera->project_ray_normal(p_point);
+
+ Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 16384) };
+
+ Vector3 axis;
+ axis[p_id] = 1.0;
+
+ Vector3 ra, rb;
+ Geometry3D::get_closest_points_between_segments(Vector3(), axis * 16384, sg[0], sg[1], ra, rb);
+ float d = ra[p_id];
+ if (Node3DEditor::get_singleton()->is_snap_enabled()) {
+ d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
+ }
+
+ if (d < 0.001) {
+ d = 0.001;
+ }
+
+ extents[p_id] = d;
+ decal->set_extents(extents);
+}
+
+void DecalGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) const {
+ Decal *decal = Object::cast_to<Decal>(p_gizmo->get_spatial_node());
+
+ Vector3 restore = p_restore;
+
+ if (p_cancel) {
+ decal->set_extents(restore);
+ return;
+ }
+
+ UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
+ ur->create_action(TTR("Change Decal Extents"));
+ ur->add_do_method(decal, "set_extents", decal->get_extents());
+ ur->add_undo_method(decal, "set_extents", restore);
+ ur->commit_action();
+}
+
+void DecalGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ Decal *decal = Object::cast_to<Decal>(p_gizmo->get_spatial_node());
+
+ p_gizmo->clear();
+
+ Vector<Vector3> lines;
+ Vector3 extents = decal->get_extents();
+
+ AABB aabb;
+ aabb.position = -extents;
+ aabb.size = extents * 2;
+
+ for (int i = 0; i < 12; i++) {
+ Vector3 a, b;
+ aabb.get_edge(i, a, b);
+ if (a.y == b.y) {
+ lines.push_back(a);
+ lines.push_back(b);
+ } else {
+ Vector3 ah = a.lerp(b, 0.2);
+ lines.push_back(a);
+ lines.push_back(ah);
+ Vector3 bh = b.lerp(a, 0.2);
+ lines.push_back(b);
+ lines.push_back(bh);
+ }
+ }
+
+ lines.push_back(Vector3(0, extents.y, 0));
+ lines.push_back(Vector3(0, extents.y * 1.2, 0));
+
+ Vector<Vector3> handles;
+
+ for (int i = 0; i < 3; i++) {
+ Vector3 ax;
+ ax[i] = aabb.position[i] + aabb.size[i];
+ handles.push_back(ax);
+ }
+
+ Ref<Material> material = get_material("decal_material", p_gizmo);
+
+ p_gizmo->add_lines(lines, material);
+ p_gizmo->add_handles(handles, get_material("handles"));
+}
+
+///////////////////////////////
+VoxelGIGizmoPlugin::VoxelGIGizmoPlugin() {
+ Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/voxel_gi", Color(0.5, 1, 0.6));
+
+ create_material("voxel_gi_material", gizmo_color);
+
+ // This gizmo draws a lot of lines. Use a low opacity to make it not too intrusive.
+ gizmo_color.a = 0.1;
+ create_material("voxel_gi_internal_material", gizmo_color);
+
+ gizmo_color.a = 0.05;
+ create_material("voxel_gi_solid_material", gizmo_color);
+
+ create_icon_material("voxel_gi_icon", Node3DEditor::get_singleton()->get_theme_icon(SNAME("GizmoVoxelGI"), SNAME("EditorIcons")));
+ create_handle_material("handles");
+}
+
+bool VoxelGIGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<VoxelGI>(p_spatial) != nullptr;
+}
+
+String VoxelGIGizmoPlugin::get_gizmo_name() const {
+ return "VoxelGI";
+}
+
+int VoxelGIGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+String VoxelGIGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ switch (p_id) {
+ case 0:
+ return "Extents X";
+ case 1:
+ return "Extents Y";
+ case 2:
+ return "Extents Z";
+ }
+
+ return "";
+}
+
+Variant VoxelGIGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ VoxelGI *probe = Object::cast_to<VoxelGI>(p_gizmo->get_spatial_node());
+ return probe->get_extents();
+}
+
+void VoxelGIGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const {
+ VoxelGI *probe = Object::cast_to<VoxelGI>(p_gizmo->get_spatial_node());
+
+ Transform3D gt = probe->get_global_transform();
+ Transform3D gi = gt.affine_inverse();
+
+ Vector3 extents = probe->get_extents();
+
+ Vector3 ray_from = p_camera->project_ray_origin(p_point);
+ Vector3 ray_dir = p_camera->project_ray_normal(p_point);
+
+ Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 16384) };
+
+ Vector3 axis;
+ axis[p_id] = 1.0;
+
+ Vector3 ra, rb;
+ Geometry3D::get_closest_points_between_segments(Vector3(), axis * 16384, sg[0], sg[1], ra, rb);
+ float d = ra[p_id];
+ if (Node3DEditor::get_singleton()->is_snap_enabled()) {
+ d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
+ }
+
+ if (d < 0.001) {
+ d = 0.001;
+ }
+
+ extents[p_id] = d;
+ probe->set_extents(extents);
+}
+
+void VoxelGIGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) const {
+ VoxelGI *probe = Object::cast_to<VoxelGI>(p_gizmo->get_spatial_node());
+
+ Vector3 restore = p_restore;
+
+ if (p_cancel) {
+ probe->set_extents(restore);
+ return;
+ }
+
+ UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
+ ur->create_action(TTR("Change Probe Extents"));
+ ur->add_do_method(probe, "set_extents", probe->get_extents());
+ ur->add_undo_method(probe, "set_extents", restore);
+ ur->commit_action();
+}
+
+void VoxelGIGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ VoxelGI *probe = Object::cast_to<VoxelGI>(p_gizmo->get_spatial_node());
+
+ Ref<Material> material = get_material("voxel_gi_material", p_gizmo);
+ Ref<Material> icon = get_material("voxel_gi_icon", p_gizmo);
+ Ref<Material> material_internal = get_material("voxel_gi_internal_material", p_gizmo);
+
+ p_gizmo->clear();
+
+ Vector<Vector3> lines;
+ Vector3 extents = probe->get_extents();
+
+ static const int subdivs[VoxelGI::SUBDIV_MAX] = { 64, 128, 256, 512 };
+
+ AABB aabb = AABB(-extents, extents * 2);
+ int subdiv = subdivs[probe->get_subdiv()];
+ float cell_size = aabb.get_longest_axis_size() / subdiv;
+
+ for (int i = 0; i < 12; i++) {
+ Vector3 a, b;
+ aabb.get_edge(i, a, b);
+ lines.push_back(a);
+ lines.push_back(b);
+ }
+
+ p_gizmo->add_lines(lines, material);
+
+ lines.clear();
+
+ for (int i = 1; i < subdiv; i++) {
+ for (int j = 0; j < 3; j++) {
+ if (cell_size * i > aabb.size[j]) {
+ continue;
+ }
+
+ Vector2 dir;
+ dir[j] = 1.0;
+ Vector2 ta, tb;
+ int j_n1 = (j + 1) % 3;
+ int j_n2 = (j + 2) % 3;
+ ta[j_n1] = 1.0;
+ tb[j_n2] = 1.0;
+
+ for (int k = 0; k < 4; k++) {
+ Vector3 from = aabb.position, to = aabb.position;
+ from[j] += cell_size * i;
+ to[j] += cell_size * i;
+
+ if (k & 1) {
+ to[j_n1] += aabb.size[j_n1];
+ } else {
+ to[j_n2] += aabb.size[j_n2];
+ }
+
+ if (k & 2) {
+ from[j_n1] += aabb.size[j_n1];
+ from[j_n2] += aabb.size[j_n2];
+ }
+
+ lines.push_back(from);
+ lines.push_back(to);
+ }
+ }
+ }
+
+ p_gizmo->add_lines(lines, material_internal);
+
+ Vector<Vector3> handles;
+
+ for (int i = 0; i < 3; i++) {
+ Vector3 ax;
+ ax[i] = aabb.position[i] + aabb.size[i];
+ handles.push_back(ax);
+ }
+
+ if (p_gizmo->is_selected()) {
+ Ref<Material> solid_material = get_material("voxel_gi_solid_material", p_gizmo);
+ p_gizmo->add_solid_box(solid_material, aabb.get_size());
+ }
+
+ p_gizmo->add_unscaled_billboard(icon, 0.05);
+ p_gizmo->add_handles(handles, get_material("handles"));
+}
+
+////
+
+LightmapGIGizmoPlugin::LightmapGIGizmoPlugin() {
+ Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/lightmap_lines", Color(0.5, 0.6, 1));
+
+ gizmo_color.a = 0.1;
+ create_material("lightmap_lines", gizmo_color);
+
+ Ref<StandardMaterial3D> mat = memnew(StandardMaterial3D);
+ mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
+ mat->set_cull_mode(StandardMaterial3D::CULL_DISABLED);
+ mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
+ mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, false);
+
+ add_material("lightmap_probe_material", mat);
+
+ create_icon_material("baked_indirect_light_icon", Node3DEditor::get_singleton()->get_theme_icon(SNAME("GizmoLightmapGI"), SNAME("EditorIcons")));
+}
+
+String LightmapGIGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ return "";
+}
+
+Variant LightmapGIGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ return Variant();
+}
+
+void LightmapGIGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const {
+}
+
+void LightmapGIGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) const {
+}
+
+bool LightmapGIGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<LightmapGI>(p_spatial) != nullptr;
+}
+
+String LightmapGIGizmoPlugin::get_gizmo_name() const {
+ return "LightmapGI";
+}
+
+int LightmapGIGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+void LightmapGIGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ Ref<Material> icon = get_material("baked_indirect_light_icon", p_gizmo);
+ LightmapGI *baker = Object::cast_to<LightmapGI>(p_gizmo->get_spatial_node());
+ Ref<LightmapGIData> data = baker->get_light_data();
+
+ p_gizmo->add_unscaled_billboard(icon, 0.05);
+
+ if (data.is_null()) {
+ return;
+ }
+
+ Ref<Material> material_lines = get_material("lightmap_lines", p_gizmo);
+ Ref<Material> material_probes = get_material("lightmap_probe_material", p_gizmo);
+
+ p_gizmo->clear();
+
+ Vector<Vector3> lines;
+ Set<Vector2i> lines_found;
+
+ Vector<Vector3> points = data->get_capture_points();
+ if (points.size() == 0) {
+ return;
+ }
+ Vector<Color> sh = data->get_capture_sh();
+ if (sh.size() != points.size() * 9) {
+ return;
+ }
+
+ Vector<int> tetrahedrons = data->get_capture_tetrahedra();
+
+ for (int i = 0; i < tetrahedrons.size(); i += 4) {
+ for (int j = 0; j < 4; j++) {
+ for (int k = j + 1; k < 4; k++) {
+ Vector2i pair;
+ pair.x = tetrahedrons[i + j];
+ pair.y = tetrahedrons[i + k];
+
+ if (pair.y < pair.x) {
+ SWAP(pair.x, pair.y);
+ }
+ if (lines_found.has(pair)) {
+ continue;
+ }
+ lines_found.insert(pair);
+ lines.push_back(points[pair.x]);
+ lines.push_back(points[pair.y]);
+ }
+ }
+ }
+
+ p_gizmo->add_lines(lines, material_lines);
+
+ int stack_count = 8;
+ int sector_count = 16;
+
+ float sector_step = (Math_PI * 2.0) / sector_count;
+ float stack_step = Math_PI / stack_count;
+
+ Vector<Vector3> vertices;
+ Vector<Color> colors;
+ Vector<int> indices;
+ float radius = 0.3;
+
+ for (int p = 0; p < points.size(); p++) {
+ int vertex_base = vertices.size();
+ Vector3 sh_col[9];
+ for (int i = 0; i < 9; i++) {
+ sh_col[i].x = sh[p * 9 + i].r;
+ sh_col[i].y = sh[p * 9 + i].g;
+ sh_col[i].z = sh[p * 9 + i].b;
+ }
+
+ for (int i = 0; i <= stack_count; ++i) {
+ float stack_angle = Math_PI / 2 - i * stack_step; // starting from pi/2 to -pi/2
+ float xy = radius * Math::cos(stack_angle); // r * cos(u)
+ float z = radius * Math::sin(stack_angle); // r * sin(u)
+
+ // add (sector_count+1) vertices per stack
+ // the first and last vertices have same position and normal, but different tex coords
+ for (int j = 0; j <= sector_count; ++j) {
+ float sector_angle = j * sector_step; // starting from 0 to 2pi
+
+ // vertex position (x, y, z)
+ float x = xy * Math::cos(sector_angle); // r * cos(u) * cos(v)
+ float y = xy * Math::sin(sector_angle); // r * cos(u) * sin(v)
+
+ Vector3 n = Vector3(x, z, y);
+ vertices.push_back(points[p] + n);
+ n.normalize();
+
+ const float c1 = 0.429043;
+ const float c2 = 0.511664;
+ const float c3 = 0.743125;
+ const float c4 = 0.886227;
+ const float c5 = 0.247708;
+ Vector3 light = (c1 * sh_col[8] * (n.x * n.x - n.y * n.y) +
+ c3 * sh_col[6] * n.z * n.z +
+ c4 * sh_col[0] -
+ c5 * sh_col[6] +
+ 2.0 * c1 * sh_col[4] * n.x * n.y +
+ 2.0 * c1 * sh_col[7] * n.x * n.z +
+ 2.0 * c1 * sh_col[5] * n.y * n.z +
+ 2.0 * c2 * sh_col[3] * n.x +
+ 2.0 * c2 * sh_col[1] * n.y +
+ 2.0 * c2 * sh_col[2] * n.z);
+
+ colors.push_back(Color(light.x, light.y, light.z, 1));
+ }
+ }
+
+ for (int i = 0; i < stack_count; ++i) {
+ int k1 = i * (sector_count + 1); // beginning of current stack
+ int k2 = k1 + sector_count + 1; // beginning of next stack
+
+ for (int j = 0; j < sector_count; ++j, ++k1, ++k2) {
+ // 2 triangles per sector excluding first and last stacks
+ // k1 => k2 => k1+1
+ if (i != 0) {
+ indices.push_back(vertex_base + k1);
+ indices.push_back(vertex_base + k2);
+ indices.push_back(vertex_base + k1 + 1);
+ }
+
+ // k1+1 => k2 => k2+1
+ if (i != (stack_count - 1)) {
+ indices.push_back(vertex_base + k1 + 1);
+ indices.push_back(vertex_base + k2);
+ indices.push_back(vertex_base + k2 + 1);
+ }
+ }
+ }
+ }
+
+ Array array;
+ array.resize(RS::ARRAY_MAX);
+ array[RS::ARRAY_VERTEX] = vertices;
+ array[RS::ARRAY_INDEX] = indices;
+ array[RS::ARRAY_COLOR] = colors;
+
+ Ref<ArrayMesh> mesh;
+ mesh.instantiate();
+ mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, array, Array(), Dictionary(), 0); //no compression
+ mesh->surface_set_material(0, material_probes);
+
+ p_gizmo->add_mesh(mesh);
+}
+
+/////////
+
+LightmapProbeGizmoPlugin::LightmapProbeGizmoPlugin() {
+ Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/lightprobe_lines", Color(0.5, 0.6, 1));
+
+ gizmo_color.a = 0.3;
+ create_material("lightprobe_lines", gizmo_color);
+}
+
+String LightmapProbeGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ return "";
+}
+
+Variant LightmapProbeGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ return Variant();
+}
+
+void LightmapProbeGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const {
+}
+
+void LightmapProbeGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) const {
+}
+
+bool LightmapProbeGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<LightmapProbe>(p_spatial) != nullptr;
+}
+
+String LightmapProbeGizmoPlugin::get_gizmo_name() const {
+ return "LightmapProbe";
+}
+
+int LightmapProbeGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+void LightmapProbeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ Ref<Material> material_lines = get_material("lightprobe_lines", p_gizmo);
+
+ p_gizmo->clear();
+
+ Vector<Vector3> lines;
+
+ int stack_count = 8;
+ int sector_count = 16;
+
+ float sector_step = (Math_PI * 2.0) / sector_count;
+ float stack_step = Math_PI / stack_count;
+
+ Vector<Vector3> vertices;
+ float radius = 0.2;
+
+ for (int i = 0; i <= stack_count; ++i) {
+ float stack_angle = Math_PI / 2 - i * stack_step; // starting from pi/2 to -pi/2
+ float xy = radius * Math::cos(stack_angle); // r * cos(u)
+ float z = radius * Math::sin(stack_angle); // r * sin(u)
+
+ // add (sector_count+1) vertices per stack
+ // the first and last vertices have same position and normal, but different tex coords
+ for (int j = 0; j <= sector_count; ++j) {
+ float sector_angle = j * sector_step; // starting from 0 to 2pi
+
+ // vertex position (x, y, z)
+ float x = xy * Math::cos(sector_angle); // r * cos(u) * cos(v)
+ float y = xy * Math::sin(sector_angle); // r * cos(u) * sin(v)
+
+ Vector3 n = Vector3(x, z, y);
+ vertices.push_back(n);
+ }
+ }
+
+ for (int i = 0; i < stack_count; ++i) {
+ int k1 = i * (sector_count + 1); // beginning of current stack
+ int k2 = k1 + sector_count + 1; // beginning of next stack
+
+ for (int j = 0; j < sector_count; ++j, ++k1, ++k2) {
+ // 2 triangles per sector excluding first and last stacks
+ // k1 => k2 => k1+1
+ if (i != 0) {
+ lines.push_back(vertices[k1]);
+ lines.push_back(vertices[k2]);
+ lines.push_back(vertices[k1]);
+ lines.push_back(vertices[k1 + 1]);
+ }
+
+ if (i != (stack_count - 1)) {
+ lines.push_back(vertices[k1 + 1]);
+ lines.push_back(vertices[k2]);
+ lines.push_back(vertices[k2]);
+ lines.push_back(vertices[k2 + 1]);
+ }
+ }
+ }
+
+ p_gizmo->add_lines(lines, material_lines);
+}
+
+////
+
+CollisionObject3DGizmoPlugin::CollisionObject3DGizmoPlugin() {
+ const Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1));
+ 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);
+}
+
+bool CollisionObject3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<CollisionObject3D>(p_spatial) != nullptr;
+}
+
+String CollisionObject3DGizmoPlugin::get_gizmo_name() const {
+ return "CollisionObject3D";
+}
+
+int CollisionObject3DGizmoPlugin::get_priority() const {
+ return -2;
+}
+
+void CollisionObject3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ CollisionObject3D *co = Object::cast_to<CollisionObject3D>(p_gizmo->get_spatial_node());
+
+ p_gizmo->clear();
+
+ List<uint32_t> owners;
+ co->get_shape_owners(&owners);
+ for (List<uint32_t>::Element *E = owners.front(); E; E = E->next()) {
+ uint32_t owner_id = E->get();
+ Transform3D xform = co->shape_owner_get_transform(owner_id);
+ Object *owner = co->shape_owner_get_owner(owner_id);
+ // Exclude CollisionShape3D and CollisionPolygon3D as they have their gizmo.
+ if (!Object::cast_to<CollisionShape3D>(owner) && !Object::cast_to<CollisionPolygon3D>(owner)) {
+ Ref<Material> material = get_material(!co->is_shape_owner_disabled(owner_id) ? "shape_material" : "shape_material_disabled", p_gizmo);
+ for (int shape_id = 0; shape_id < co->shape_owner_get_shape_count(owner_id); shape_id++) {
+ Ref<Shape3D> s = co->shape_owner_get_shape(owner_id, shape_id);
+ if (s.is_null()) {
+ continue;
+ }
+ SurfaceTool st;
+ st.append_from(s->get_debug_mesh(), 0, xform);
+
+ p_gizmo->add_mesh(st.commit(), material);
+ p_gizmo->add_collision_segments(s->get_debug_mesh_lines());
+ }
+ }
+ }
+}
+
+////
+
+CollisionShape3DGizmoPlugin::CollisionShape3DGizmoPlugin() {
+ const Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1));
+ 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");
+}
+
+bool CollisionShape3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<CollisionShape3D>(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) const {
+ const CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_spatial_node());
+
+ Ref<Shape3D> s = cs->get_shape();
+ if (s.is_null()) {
+ return "";
+ }
+
+ if (Object::cast_to<SphereShape3D>(*s)) {
+ return "Radius";
+ }
+
+ if (Object::cast_to<BoxShape3D>(*s)) {
+ return "Size";
+ }
+
+ if (Object::cast_to<CapsuleShape3D>(*s)) {
+ return p_id == 0 ? "Radius" : "Height";
+ }
+
+ if (Object::cast_to<CylinderShape3D>(*s)) {
+ return p_id == 0 ? "Radius" : "Height";
+ }
+
+ if (Object::cast_to<RayShape3D>(*s)) {
+ return "Length";
+ }
+
+ return "";
+}
+
+Variant CollisionShape3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_spatial_node());
+
+ Ref<Shape3D> s = cs->get_shape();
+ if (s.is_null()) {
+ return Variant();
+ }
+
+ if (Object::cast_to<SphereShape3D>(*s)) {
+ Ref<SphereShape3D> ss = s;
+ return ss->get_radius();
+ }
+
+ if (Object::cast_to<BoxShape3D>(*s)) {
+ Ref<BoxShape3D> bs = s;
+ return bs->get_size();
+ }
+
+ if (Object::cast_to<CapsuleShape3D>(*s)) {
+ Ref<CapsuleShape3D> cs2 = s;
+ return p_id == 0 ? cs2->get_radius() : cs2->get_height();
+ }
+
+ if (Object::cast_to<CylinderShape3D>(*s)) {
+ Ref<CylinderShape3D> cs2 = s;
+ return p_id == 0 ? cs2->get_radius() : cs2->get_height();
+ }
+
+ if (Object::cast_to<RayShape3D>(*s)) {
+ Ref<RayShape3D> cs2 = s;
+ return cs2->get_length();
+ }
+
+ return Variant();
+}
+
+void CollisionShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const {
+ CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_spatial_node());
+
+ Ref<Shape3D> s = cs->get_shape();
+ if (s.is_null()) {
+ return;
+ }
+
+ Transform3D gt = cs->get_global_transform();
+ Transform3D gi = gt.affine_inverse();
+
+ Vector3 ray_from = p_camera->project_ray_origin(p_point);
+ Vector3 ray_dir = p_camera->project_ray_normal(p_point);
+
+ Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 4096) };
+
+ if (Object::cast_to<SphereShape3D>(*s)) {
+ Ref<SphereShape3D> 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<RayShape3D>(*s)) {
+ Ref<RayShape3D> 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<BoxShape3D>(*s)) {
+ Vector3 axis;
+ axis[p_id] = 1.0;
+ Ref<BoxShape3D> bs = s;
+ Vector3 ra, rb;
+ Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb);
+ float d = ra[p_id];
+ if (Node3DEditor::get_singleton()->is_snap_enabled()) {
+ d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
+ }
+
+ if (d < 0.001) {
+ d = 0.001;
+ }
+
+ Vector3 he = bs->get_size();
+ he[p_id] = d * 2;
+ bs->set_size(he);
+ }
+
+ if (Object::cast_to<CapsuleShape3D>(*s)) {
+ Vector3 axis;
+ axis[p_id == 0 ? 0 : 2] = 1.0;
+ Ref<CapsuleShape3D> 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 (p_id == 1) {
+ d -= cs2->get_radius();
+ }
+
+ 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<CylinderShape3D>(*s)) {
+ Vector3 axis;
+ axis[p_id == 0 ? 0 : 1] = 1.0;
+ Ref<CylinderShape3D> 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, const Variant &p_restore, bool p_cancel) const {
+ CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_spatial_node());
+
+ Ref<Shape3D> s = cs->get_shape();
+ if (s.is_null()) {
+ return;
+ }
+
+ if (Object::cast_to<SphereShape3D>(*s)) {
+ Ref<SphereShape3D> ss = s;
+ if (p_cancel) {
+ ss->set_radius(p_restore);
+ return;
+ }
+
+ UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
+ 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<BoxShape3D>(*s)) {
+ Ref<BoxShape3D> ss = s;
+ if (p_cancel) {
+ ss->set_size(p_restore);
+ return;
+ }
+
+ UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
+ ur->create_action(TTR("Change Box Shape Size"));
+ ur->add_do_method(ss.ptr(), "set_size", ss->get_size());
+ ur->add_undo_method(ss.ptr(), "set_size", p_restore);
+ ur->commit_action();
+ }
+
+ if (Object::cast_to<CapsuleShape3D>(*s)) {
+ Ref<CapsuleShape3D> ss = s;
+ if (p_cancel) {
+ if (p_id == 0) {
+ ss->set_radius(p_restore);
+ } else {
+ ss->set_height(p_restore);
+ }
+ return;
+ }
+
+ UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
+ if (p_id == 0) {
+ ur->create_action(TTR("Change Capsule 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 Capsule 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<CylinderShape3D>(*s)) {
+ Ref<CylinderShape3D> ss = s;
+ if (p_cancel) {
+ if (p_id == 0) {
+ ss->set_radius(p_restore);
+ } else {
+ ss->set_height(p_restore);
+ }
+ return;
+ }
+
+ UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
+ 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<RayShape3D>(*s)) {
+ Ref<RayShape3D> ss = s;
+ if (p_cancel) {
+ ss->set_length(p_restore);
+ return;
+ }
+
+ UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
+ ur->create_action(TTR("Change 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<CollisionShape3D>(p_gizmo->get_spatial_node());
+
+ p_gizmo->clear();
+
+ Ref<Shape3D> s = cs->get_shape();
+ if (s.is_null()) {
+ return;
+ }
+
+ const Ref<Material> material =
+ get_material(!cs->is_disabled() ? "shape_material" : "shape_material_disabled", p_gizmo);
+ Ref<Material> handles_material = get_material("handles");
+
+ if (Object::cast_to<SphereShape3D>(*s)) {
+ Ref<SphereShape3D> sp = s;
+ float r = sp->get_radius();
+
+ Vector<Vector3> points;
+
+ for (int i = 0; i <= 360; i++) {
+ float ra = Math::deg2rad((float)i);
+ float rb = Math::deg2rad((float)i + 1);
+ Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * r;
+ Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * r;
+
+ points.push_back(Vector3(a.x, 0, a.y));
+ points.push_back(Vector3(b.x, 0, b.y));
+ points.push_back(Vector3(0, a.x, a.y));
+ points.push_back(Vector3(0, b.x, b.y));
+ points.push_back(Vector3(a.x, a.y, 0));
+ points.push_back(Vector3(b.x, b.y, 0));
+ }
+
+ Vector<Vector3> collision_segments;
+
+ for (int i = 0; i < 64; i++) {
+ float ra = i * (Math_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<Vector3> handles;
+ handles.push_back(Vector3(r, 0, 0));
+ p_gizmo->add_handles(handles, handles_material);
+ }
+
+ if (Object::cast_to<BoxShape3D>(*s)) {
+ Ref<BoxShape3D> bs = s;
+ Vector<Vector3> 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);
+ }
+
+ Vector<Vector3> handles;
+
+ for (int i = 0; i < 3; i++) {
+ Vector3 ax;
+ ax[i] = bs->get_size()[i] / 2;
+ handles.push_back(ax);
+ }
+
+ p_gizmo->add_lines(lines, material);
+ p_gizmo->add_collision_segments(lines);
+ p_gizmo->add_handles(handles, handles_material);
+ }
+
+ if (Object::cast_to<CapsuleShape3D>(*s)) {
+ Ref<CapsuleShape3D> cs2 = s;
+ float radius = cs2->get_radius();
+ float height = cs2->get_height();
+
+ Vector<Vector3> points;
+
+ Vector3 d(0, height * 0.5, 0);
+ for (int i = 0; i < 360; i++) {
+ float ra = Math::deg2rad((float)i);
+ float rb = Math::deg2rad((float)i + 1);
+ Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * 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<Vector3> 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<Vector3> handles;
+ handles.push_back(Vector3(cs2->get_radius(), 0, 0));
+ handles.push_back(Vector3(0, cs2->get_height() * 0.5 + cs2->get_radius(), 0));
+ p_gizmo->add_handles(handles, handles_material);
+ }
+
+ if (Object::cast_to<CylinderShape3D>(*s)) {
+ Ref<CylinderShape3D> cs2 = s;
+ float radius = cs2->get_radius();
+ float height = cs2->get_height();
+
+ Vector<Vector3> points;
+
+ Vector3 d(0, height * 0.5, 0);
+ for (int i = 0; i < 360; i++) {
+ float ra = Math::deg2rad((float)i);
+ float rb = Math::deg2rad((float)i + 1);
+ Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * 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<Vector3> 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<Vector3> handles;
+ handles.push_back(Vector3(cs2->get_radius(), 0, 0));
+ handles.push_back(Vector3(0, cs2->get_height() * 0.5, 0));
+ p_gizmo->add_handles(handles, handles_material);
+ }
+
+ if (Object::cast_to<WorldMarginShape3D>(*s)) {
+ Ref<WorldMarginShape3D> ps = s;
+ Plane p = ps->get_plane();
+ Vector<Vector3> points;
+
+ 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,
+ };
+
+ points.push_back(pface[0]);
+ points.push_back(pface[1]);
+ points.push_back(pface[1]);
+ points.push_back(pface[2]);
+ points.push_back(pface[2]);
+ points.push_back(pface[3]);
+ points.push_back(pface[3]);
+ points.push_back(pface[0]);
+ points.push_back(p.normal * p.d);
+ points.push_back(p.normal * p.d + p.normal * 3);
+
+ p_gizmo->add_lines(points, material);
+ p_gizmo->add_collision_segments(points);
+ }
+
+ if (Object::cast_to<ConvexPolygonShape3D>(*s)) {
+ Vector<Vector3> points = Object::cast_to<ConvexPolygonShape3D>(*s)->get_points();
+
+ if (points.size() > 3) {
+ Vector<Vector3> varr = Variant(points);
+ Geometry3D::MeshData md;
+ Error err = ConvexHullComputer::convex_hull(varr, md);
+ if (err == OK) {
+ Vector<Vector3> points2;
+ points2.resize(md.edges.size() * 2);
+ for (int i = 0; i < md.edges.size(); i++) {
+ points2.write[i * 2 + 0] = md.vertices[md.edges[i].a];
+ points2.write[i * 2 + 1] = md.vertices[md.edges[i].b];
+ }
+
+ p_gizmo->add_lines(points2, material);
+ p_gizmo->add_collision_segments(points2);
+ }
+ }
+ }
+
+ if (Object::cast_to<ConcavePolygonShape3D>(*s)) {
+ Ref<ConcavePolygonShape3D> cs2 = s;
+ Ref<ArrayMesh> 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<RayShape3D>(*s)) {
+ Ref<RayShape3D> rs = s;
+
+ Vector<Vector3> points;
+ points.push_back(Vector3());
+ points.push_back(Vector3(0, 0, rs->get_length()));
+ p_gizmo->add_lines(points, material);
+ p_gizmo->add_collision_segments(points);
+ Vector<Vector3> handles;
+ handles.push_back(Vector3(0, 0, rs->get_length()));
+ p_gizmo->add_handles(handles, handles_material);
+ }
+
+ if (Object::cast_to<HeightMapShape3D>(*s)) {
+ Ref<HeightMapShape3D> hms = s;
+
+ Ref<ArrayMesh> mesh = hms->get_debug_mesh();
+ p_gizmo->add_mesh(mesh, material);
+ }
+}
+
+/////
+
+CollisionPolygon3DGizmoPlugin::CollisionPolygon3DGizmoPlugin() {
+ const Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1));
+ 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);
+}
+
+bool CollisionPolygon3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<CollisionPolygon3D>(p_spatial) != nullptr;
+}
+
+String CollisionPolygon3DGizmoPlugin::get_gizmo_name() const {
+ return "CollisionPolygon3D";
+}
+
+int CollisionPolygon3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+void CollisionPolygon3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ CollisionPolygon3D *polygon = Object::cast_to<CollisionPolygon3D>(p_gizmo->get_spatial_node());
+
+ p_gizmo->clear();
+
+ Vector<Vector2> points = polygon->get_polygon();
+ float depth = polygon->get_depth() * 0.5;
+
+ Vector<Vector3> lines;
+ for (int i = 0; i < points.size(); i++) {
+ int n = (i + 1) % points.size();
+ lines.push_back(Vector3(points[i].x, points[i].y, depth));
+ lines.push_back(Vector3(points[n].x, points[n].y, depth));
+ lines.push_back(Vector3(points[i].x, points[i].y, -depth));
+ lines.push_back(Vector3(points[n].x, points[n].y, -depth));
+ lines.push_back(Vector3(points[i].x, points[i].y, depth));
+ lines.push_back(Vector3(points[i].x, points[i].y, -depth));
+ }
+
+ const Ref<Material> material =
+ get_material(!polygon->is_disabled() ? "shape_material" : "shape_material_disabled", p_gizmo);
+
+ p_gizmo->add_lines(lines, material);
+ p_gizmo->add_collision_segments(lines);
+}
+
+////
+
+NavigationRegion3DGizmoPlugin::NavigationRegion3DGizmoPlugin() {
+ create_material("navigation_edge_material", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/navigation_edge", Color(0.5, 1, 1)));
+ create_material("navigation_edge_material_disabled", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/navigation_edge_disabled", Color(0.7, 0.7, 0.7)));
+ create_material("navigation_solid_material", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/navigation_solid", Color(0.5, 1, 1, 0.4)));
+ create_material("navigation_solid_material_disabled", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/navigation_solid_disabled", Color(0.7, 0.7, 0.7, 0.4)));
+}
+
+bool NavigationRegion3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<NavigationRegion3D>(p_spatial) != nullptr;
+}
+
+String NavigationRegion3DGizmoPlugin::get_gizmo_name() const {
+ return "NavigationRegion3D";
+}
+
+int NavigationRegion3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+void NavigationRegion3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ NavigationRegion3D *navmesh = Object::cast_to<NavigationRegion3D>(p_gizmo->get_spatial_node());
+
+ Ref<Material> edge_material = get_material("navigation_edge_material", p_gizmo);
+ Ref<Material> edge_material_disabled = get_material("navigation_edge_material_disabled", p_gizmo);
+ Ref<Material> solid_material = get_material("navigation_solid_material", p_gizmo);
+ Ref<Material> solid_material_disabled = get_material("navigation_solid_material_disabled", p_gizmo);
+
+ p_gizmo->clear();
+ Ref<NavigationMesh> navmeshie = navmesh->get_navigation_mesh();
+ if (navmeshie.is_null()) {
+ return;
+ }
+
+ Vector<Vector3> vertices = navmeshie->get_vertices();
+ const Vector3 *vr = vertices.ptr();
+ List<Face3> faces;
+ for (int i = 0; i < navmeshie->get_polygon_count(); i++) {
+ Vector<int> p = navmeshie->get_polygon(i);
+
+ for (int j = 2; j < p.size(); j++) {
+ Face3 f;
+ f.vertex[0] = vr[p[0]];
+ f.vertex[1] = vr[p[j - 1]];
+ f.vertex[2] = vr[p[j]];
+
+ faces.push_back(f);
+ }
+ }
+
+ if (faces.is_empty()) {
+ return;
+ }
+
+ Map<_EdgeKey, bool> edge_map;
+ Vector<Vector3> tmeshfaces;
+ tmeshfaces.resize(faces.size() * 3);
+
+ {
+ Vector3 *tw = tmeshfaces.ptrw();
+ int tidx = 0;
+
+ for (List<Face3>::Element *E = faces.front(); E; E = E->next()) {
+ const Face3 &f = E->get();
+
+ for (int j = 0; j < 3; j++) {
+ tw[tidx++] = f.vertex[j];
+ _EdgeKey ek;
+ ek.from = f.vertex[j].snapped(Vector3(CMP_EPSILON, CMP_EPSILON, CMP_EPSILON));
+ ek.to = f.vertex[(j + 1) % 3].snapped(Vector3(CMP_EPSILON, CMP_EPSILON, CMP_EPSILON));
+ if (ek.from < ek.to) {
+ SWAP(ek.from, ek.to);
+ }
+
+ Map<_EdgeKey, bool>::Element *F = edge_map.find(ek);
+
+ if (F) {
+ F->get() = false;
+
+ } else {
+ edge_map[ek] = true;
+ }
+ }
+ }
+ }
+ Vector<Vector3> lines;
+
+ for (Map<_EdgeKey, bool>::Element *E = edge_map.front(); E; E = E->next()) {
+ if (E->get()) {
+ lines.push_back(E->key().from);
+ lines.push_back(E->key().to);
+ }
+ }
+
+ Ref<TriangleMesh> tmesh = memnew(TriangleMesh);
+ tmesh->create(tmeshfaces);
+
+ if (lines.size()) {
+ p_gizmo->add_lines(lines, navmesh->is_enabled() ? edge_material : edge_material_disabled);
+ }
+ p_gizmo->add_collision_triangles(tmesh);
+ Ref<ArrayMesh> m = memnew(ArrayMesh);
+ Array a;
+ a.resize(Mesh::ARRAY_MAX);
+ a[0] = tmeshfaces;
+ m->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, a);
+ m->surface_set_material(0, navmesh->is_enabled() ? solid_material : solid_material_disabled);
+ p_gizmo->add_mesh(m);
+ p_gizmo->add_collision_segments(lines);
+}
+
+//////
+
+#define BODY_A_RADIUS 0.25
+#define BODY_B_RADIUS 0.27
+
+Basis JointGizmosDrawer::look_body(const Transform3D &p_joint_transform, const Transform3D &p_body_transform) {
+ const Vector3 &p_eye(p_joint_transform.origin);
+ const Vector3 &p_target(p_body_transform.origin);
+
+ Vector3 v_x, v_y, v_z;
+
+ // Look the body with X
+ v_x = p_target - p_eye;
+ v_x.normalize();
+
+ v_z = v_x.cross(Vector3(0, 1, 0));
+ v_z.normalize();
+
+ v_y = v_z.cross(v_x);
+ v_y.normalize();
+
+ Basis base;
+ base.set(v_x, v_y, v_z);
+
+ // Absorb current joint transform
+ base = p_joint_transform.basis.inverse() * base;
+
+ return base;
+}
+
+Basis JointGizmosDrawer::look_body_toward(Vector3::Axis p_axis, const Transform3D &joint_transform, const Transform3D &body_transform) {
+ switch (p_axis) {
+ case Vector3::AXIS_X:
+ return look_body_toward_x(joint_transform, body_transform);
+ case Vector3::AXIS_Y:
+ return look_body_toward_y(joint_transform, body_transform);
+ case Vector3::AXIS_Z:
+ return look_body_toward_z(joint_transform, body_transform);
+ default:
+ return Basis();
+ }
+}
+
+Basis JointGizmosDrawer::look_body_toward_x(const Transform3D &p_joint_transform, const Transform3D &p_body_transform) {
+ const Vector3 &p_eye(p_joint_transform.origin);
+ const Vector3 &p_target(p_body_transform.origin);
+
+ const Vector3 p_front(p_joint_transform.basis.get_axis(0));
+
+ Vector3 v_x, v_y, v_z;
+
+ // Look the body with X
+ v_x = p_target - p_eye;
+ v_x.normalize();
+
+ v_y = p_front.cross(v_x);
+ v_y.normalize();
+
+ v_z = v_y.cross(p_front);
+ v_z.normalize();
+
+ // Clamp X to FRONT axis
+ v_x = p_front;
+ v_x.normalize();
+
+ Basis base;
+ base.set(v_x, v_y, v_z);
+
+ // Absorb current joint transform
+ base = p_joint_transform.basis.inverse() * base;
+
+ return base;
+}
+
+Basis JointGizmosDrawer::look_body_toward_y(const Transform3D &p_joint_transform, const Transform3D &p_body_transform) {
+ const Vector3 &p_eye(p_joint_transform.origin);
+ const Vector3 &p_target(p_body_transform.origin);
+
+ const Vector3 p_up(p_joint_transform.basis.get_axis(1));
+
+ Vector3 v_x, v_y, v_z;
+
+ // Look the body with X
+ v_x = p_target - p_eye;
+ v_x.normalize();
+
+ v_z = v_x.cross(p_up);
+ v_z.normalize();
+
+ v_x = p_up.cross(v_z);
+ v_x.normalize();
+
+ // Clamp Y to UP axis
+ v_y = p_up;
+ v_y.normalize();
+
+ Basis base;
+ base.set(v_x, v_y, v_z);
+
+ // Absorb current joint transform
+ base = p_joint_transform.basis.inverse() * base;
+
+ return base;
+}
+
+Basis JointGizmosDrawer::look_body_toward_z(const Transform3D &p_joint_transform, const Transform3D &p_body_transform) {
+ const Vector3 &p_eye(p_joint_transform.origin);
+ const Vector3 &p_target(p_body_transform.origin);
+
+ const Vector3 p_lateral(p_joint_transform.basis.get_axis(2));
+
+ Vector3 v_x, v_y, v_z;
+
+ // Look the body with X
+ v_x = p_target - p_eye;
+ v_x.normalize();
+
+ v_z = p_lateral;
+ v_z.normalize();
+
+ v_y = v_z.cross(v_x);
+ v_y.normalize();
+
+ // Clamp X to Z axis
+ v_x = v_y.cross(v_z);
+ v_x.normalize();
+
+ Basis base;
+ base.set(v_x, v_y, v_z);
+
+ // Absorb current joint transform
+ base = p_joint_transform.basis.inverse() * base;
+
+ return base;
+}
+
+void JointGizmosDrawer::draw_circle(Vector3::Axis p_axis, real_t p_radius, const Transform3D &p_offset, const Basis &p_base, real_t p_limit_lower, real_t p_limit_upper, Vector<Vector3> &r_points, bool p_inverse) {
+ if (p_limit_lower == p_limit_upper) {
+ r_points.push_back(p_offset.translated(Vector3()).origin);
+ r_points.push_back(p_offset.translated(p_base.xform(Vector3(0.5, 0, 0))).origin);
+
+ } else {
+ if (p_limit_lower > p_limit_upper) {
+ p_limit_lower = -Math_PI;
+ p_limit_upper = Math_PI;
+ }
+
+ const int points = 32;
+
+ for (int i = 0; i < points; i++) {
+ real_t s = p_limit_lower + i * (p_limit_upper - p_limit_lower) / points;
+ real_t n = p_limit_lower + (i + 1) * (p_limit_upper - p_limit_lower) / points;
+
+ Vector3 from;
+ Vector3 to;
+ switch (p_axis) {
+ case Vector3::AXIS_X:
+ if (p_inverse) {
+ from = p_base.xform(Vector3(0, Math::sin(s), Math::cos(s))) * p_radius;
+ to = p_base.xform(Vector3(0, Math::sin(n), Math::cos(n))) * p_radius;
+ } else {
+ from = p_base.xform(Vector3(0, -Math::sin(s), Math::cos(s))) * p_radius;
+ to = p_base.xform(Vector3(0, -Math::sin(n), Math::cos(n))) * p_radius;
+ }
+ break;
+ case Vector3::AXIS_Y:
+ if (p_inverse) {
+ from = p_base.xform(Vector3(Math::cos(s), 0, -Math::sin(s))) * p_radius;
+ to = p_base.xform(Vector3(Math::cos(n), 0, -Math::sin(n))) * p_radius;
+ } else {
+ from = p_base.xform(Vector3(Math::cos(s), 0, Math::sin(s))) * p_radius;
+ to = p_base.xform(Vector3(Math::cos(n), 0, Math::sin(n))) * p_radius;
+ }
+ break;
+ case Vector3::AXIS_Z:
+ from = p_base.xform(Vector3(Math::cos(s), Math::sin(s), 0)) * p_radius;
+ to = p_base.xform(Vector3(Math::cos(n), Math::sin(n), 0)) * p_radius;
+ break;
+ }
+
+ if (i == points - 1) {
+ r_points.push_back(p_offset.translated(to).origin);
+ r_points.push_back(p_offset.translated(Vector3()).origin);
+ }
+ if (i == 0) {
+ r_points.push_back(p_offset.translated(from).origin);
+ r_points.push_back(p_offset.translated(Vector3()).origin);
+ }
+
+ r_points.push_back(p_offset.translated(from).origin);
+ r_points.push_back(p_offset.translated(to).origin);
+ }
+
+ r_points.push_back(p_offset.translated(Vector3(0, p_radius * 1.5, 0)).origin);
+ r_points.push_back(p_offset.translated(Vector3()).origin);
+ }
+}
+
+void JointGizmosDrawer::draw_cone(const Transform3D &p_offset, const Basis &p_base, real_t p_swing, real_t p_twist, Vector<Vector3> &r_points) {
+ float r = 1.0;
+ float w = r * Math::sin(p_swing);
+ float d = r * Math::cos(p_swing);
+
+ //swing
+ for (int i = 0; i < 360; i += 10) {
+ float ra = Math::deg2rad((float)i);
+ float rb = Math::deg2rad((float)i + 10);
+ Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * w;
+ Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * w;
+
+ r_points.push_back(p_offset.translated(p_base.xform(Vector3(d, a.x, a.y))).origin);
+ r_points.push_back(p_offset.translated(p_base.xform(Vector3(d, b.x, b.y))).origin);
+
+ if (i % 90 == 0) {
+ r_points.push_back(p_offset.translated(p_base.xform(Vector3(d, a.x, a.y))).origin);
+ r_points.push_back(p_offset.translated(p_base.xform(Vector3())).origin);
+ }
+ }
+
+ r_points.push_back(p_offset.translated(p_base.xform(Vector3())).origin);
+ r_points.push_back(p_offset.translated(p_base.xform(Vector3(1, 0, 0))).origin);
+
+ /// Twist
+ float ts = Math::rad2deg(p_twist);
+ ts = MIN(ts, 720);
+
+ for (int i = 0; i < int(ts); i += 5) {
+ float ra = Math::deg2rad((float)i);
+ float rb = Math::deg2rad((float)i + 5);
+ float c = i / 720.0;
+ float cn = (i + 5) / 720.0;
+ Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * w * c;
+ Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * w * cn;
+
+ r_points.push_back(p_offset.translated(p_base.xform(Vector3(c, a.x, a.y))).origin);
+ r_points.push_back(p_offset.translated(p_base.xform(Vector3(cn, b.x, b.y))).origin);
+ }
+}
+
+////
+
+Joint3DGizmoPlugin::Joint3DGizmoPlugin() {
+ create_material("joint_material", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/joint", Color(0.5, 0.8, 1)));
+ create_material("joint_body_a_material", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/joint_body_a", Color(0.6, 0.8, 1)));
+ create_material("joint_body_b_material", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/joint_body_b", Color(0.6, 0.9, 1)));
+
+ update_timer = memnew(Timer);
+ update_timer->set_name("JointGizmoUpdateTimer");
+ update_timer->set_wait_time(1.0 / 120.0);
+ update_timer->connect("timeout", callable_mp(this, &Joint3DGizmoPlugin::incremental_update_gizmos));
+ update_timer->set_autostart(true);
+ EditorNode::get_singleton()->call_deferred(SNAME("add_child"), update_timer);
+}
+
+void Joint3DGizmoPlugin::incremental_update_gizmos() {
+ if (!current_gizmos.is_empty()) {
+ update_idx++;
+ update_idx = update_idx % current_gizmos.size();
+ redraw(current_gizmos[update_idx]);
+ }
+}
+
+bool Joint3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<Joint3D>(p_spatial) != nullptr;
+}
+
+String Joint3DGizmoPlugin::get_gizmo_name() const {
+ return "Joint3D";
+}
+
+int Joint3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+void Joint3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ Joint3D *joint = Object::cast_to<Joint3D>(p_gizmo->get_spatial_node());
+
+ p_gizmo->clear();
+
+ Node3D *node_body_a = nullptr;
+ if (!joint->get_node_a().is_empty()) {
+ node_body_a = Object::cast_to<Node3D>(joint->get_node(joint->get_node_a()));
+ }
+
+ Node3D *node_body_b = nullptr;
+ if (!joint->get_node_b().is_empty()) {
+ node_body_b = Object::cast_to<Node3D>(joint->get_node(joint->get_node_b()));
+ }
+
+ if (!node_body_a && !node_body_b) {
+ return;
+ }
+
+ Ref<Material> common_material = get_material("joint_material", p_gizmo);
+ Ref<Material> body_a_material = get_material("joint_body_a_material", p_gizmo);
+ Ref<Material> body_b_material = get_material("joint_body_b_material", p_gizmo);
+
+ Vector<Vector3> points;
+ Vector<Vector3> body_a_points;
+ Vector<Vector3> body_b_points;
+
+ if (Object::cast_to<PinJoint3D>(joint)) {
+ CreatePinJointGizmo(Transform3D(), points);
+ p_gizmo->add_collision_segments(points);
+ p_gizmo->add_lines(points, common_material);
+ }
+
+ HingeJoint3D *hinge = Object::cast_to<HingeJoint3D>(joint);
+ if (hinge) {
+ CreateHingeJointGizmo(
+ Transform3D(),
+ hinge->get_global_transform(),
+ node_body_a ? node_body_a->get_global_transform() : Transform3D(),
+ node_body_b ? node_body_b->get_global_transform() : Transform3D(),
+ hinge->get_param(HingeJoint3D::PARAM_LIMIT_LOWER),
+ hinge->get_param(HingeJoint3D::PARAM_LIMIT_UPPER),
+ hinge->get_flag(HingeJoint3D::FLAG_USE_LIMIT),
+ points,
+ node_body_a ? &body_a_points : nullptr,
+ node_body_b ? &body_b_points : nullptr);
+
+ p_gizmo->add_collision_segments(points);
+ p_gizmo->add_collision_segments(body_a_points);
+ p_gizmo->add_collision_segments(body_b_points);
+
+ p_gizmo->add_lines(points, common_material);
+ p_gizmo->add_lines(body_a_points, body_a_material);
+ p_gizmo->add_lines(body_b_points, body_b_material);
+ }
+
+ SliderJoint3D *slider = Object::cast_to<SliderJoint3D>(joint);
+ if (slider) {
+ CreateSliderJointGizmo(
+ Transform3D(),
+ slider->get_global_transform(),
+ node_body_a ? node_body_a->get_global_transform() : Transform3D(),
+ node_body_b ? node_body_b->get_global_transform() : Transform3D(),
+ slider->get_param(SliderJoint3D::PARAM_ANGULAR_LIMIT_LOWER),
+ slider->get_param(SliderJoint3D::PARAM_ANGULAR_LIMIT_UPPER),
+ slider->get_param(SliderJoint3D::PARAM_LINEAR_LIMIT_LOWER),
+ slider->get_param(SliderJoint3D::PARAM_LINEAR_LIMIT_UPPER),
+ points,
+ node_body_a ? &body_a_points : nullptr,
+ node_body_b ? &body_b_points : nullptr);
+
+ p_gizmo->add_collision_segments(points);
+ p_gizmo->add_collision_segments(body_a_points);
+ p_gizmo->add_collision_segments(body_b_points);
+
+ p_gizmo->add_lines(points, common_material);
+ p_gizmo->add_lines(body_a_points, body_a_material);
+ p_gizmo->add_lines(body_b_points, body_b_material);
+ }
+
+ ConeTwistJoint3D *cone = Object::cast_to<ConeTwistJoint3D>(joint);
+ if (cone) {
+ CreateConeTwistJointGizmo(
+ Transform3D(),
+ cone->get_global_transform(),
+ node_body_a ? node_body_a->get_global_transform() : Transform3D(),
+ node_body_b ? node_body_b->get_global_transform() : Transform3D(),
+ cone->get_param(ConeTwistJoint3D::PARAM_SWING_SPAN),
+ cone->get_param(ConeTwistJoint3D::PARAM_TWIST_SPAN),
+ node_body_a ? &body_a_points : nullptr,
+ node_body_b ? &body_b_points : nullptr);
+
+ p_gizmo->add_collision_segments(body_a_points);
+ p_gizmo->add_collision_segments(body_b_points);
+
+ p_gizmo->add_lines(body_a_points, body_a_material);
+ p_gizmo->add_lines(body_b_points, body_b_material);
+ }
+
+ Generic6DOFJoint3D *gen = Object::cast_to<Generic6DOFJoint3D>(joint);
+ if (gen) {
+ CreateGeneric6DOFJointGizmo(
+ Transform3D(),
+ gen->get_global_transform(),
+ node_body_a ? node_body_a->get_global_transform() : Transform3D(),
+ node_body_b ? node_body_b->get_global_transform() : Transform3D(),
+
+ gen->get_param_x(Generic6DOFJoint3D::PARAM_ANGULAR_LOWER_LIMIT),
+ gen->get_param_x(Generic6DOFJoint3D::PARAM_ANGULAR_UPPER_LIMIT),
+ gen->get_param_x(Generic6DOFJoint3D::PARAM_LINEAR_LOWER_LIMIT),
+ gen->get_param_x(Generic6DOFJoint3D::PARAM_LINEAR_UPPER_LIMIT),
+ gen->get_flag_x(Generic6DOFJoint3D::FLAG_ENABLE_ANGULAR_LIMIT),
+ gen->get_flag_x(Generic6DOFJoint3D::FLAG_ENABLE_LINEAR_LIMIT),
+
+ gen->get_param_y(Generic6DOFJoint3D::PARAM_ANGULAR_LOWER_LIMIT),
+ gen->get_param_y(Generic6DOFJoint3D::PARAM_ANGULAR_UPPER_LIMIT),
+ gen->get_param_y(Generic6DOFJoint3D::PARAM_LINEAR_LOWER_LIMIT),
+ gen->get_param_y(Generic6DOFJoint3D::PARAM_LINEAR_UPPER_LIMIT),
+ gen->get_flag_y(Generic6DOFJoint3D::FLAG_ENABLE_ANGULAR_LIMIT),
+ gen->get_flag_y(Generic6DOFJoint3D::FLAG_ENABLE_LINEAR_LIMIT),
+
+ gen->get_param_z(Generic6DOFJoint3D::PARAM_ANGULAR_LOWER_LIMIT),
+ gen->get_param_z(Generic6DOFJoint3D::PARAM_ANGULAR_UPPER_LIMIT),
+ gen->get_param_z(Generic6DOFJoint3D::PARAM_LINEAR_LOWER_LIMIT),
+ gen->get_param_z(Generic6DOFJoint3D::PARAM_LINEAR_UPPER_LIMIT),
+ gen->get_flag_z(Generic6DOFJoint3D::FLAG_ENABLE_ANGULAR_LIMIT),
+ gen->get_flag_z(Generic6DOFJoint3D::FLAG_ENABLE_LINEAR_LIMIT),
+
+ points,
+ node_body_a ? &body_a_points : nullptr,
+ node_body_a ? &body_b_points : nullptr);
+
+ p_gizmo->add_collision_segments(points);
+ p_gizmo->add_collision_segments(body_a_points);
+ p_gizmo->add_collision_segments(body_b_points);
+
+ p_gizmo->add_lines(points, common_material);
+ p_gizmo->add_lines(body_a_points, body_a_material);
+ p_gizmo->add_lines(body_b_points, body_b_material);
+ }
+}
+
+void Joint3DGizmoPlugin::CreatePinJointGizmo(const Transform3D &p_offset, Vector<Vector3> &r_cursor_points) {
+ float cs = 0.25;
+
+ r_cursor_points.push_back(p_offset.translated(Vector3(+cs, 0, 0)).origin);
+ r_cursor_points.push_back(p_offset.translated(Vector3(-cs, 0, 0)).origin);
+ r_cursor_points.push_back(p_offset.translated(Vector3(0, +cs, 0)).origin);
+ r_cursor_points.push_back(p_offset.translated(Vector3(0, -cs, 0)).origin);
+ r_cursor_points.push_back(p_offset.translated(Vector3(0, 0, +cs)).origin);
+ r_cursor_points.push_back(p_offset.translated(Vector3(0, 0, -cs)).origin);
+}
+
+void Joint3DGizmoPlugin::CreateHingeJointGizmo(const Transform3D &p_offset, const Transform3D &p_trs_joint, const Transform3D &p_trs_body_a, const Transform3D &p_trs_body_b, real_t p_limit_lower, real_t p_limit_upper, bool p_use_limit, Vector<Vector3> &r_common_points, Vector<Vector3> *r_body_a_points, Vector<Vector3> *r_body_b_points) {
+ r_common_points.push_back(p_offset.translated(Vector3(0, 0, 0.5)).origin);
+ r_common_points.push_back(p_offset.translated(Vector3(0, 0, -0.5)).origin);
+
+ if (!p_use_limit) {
+ p_limit_upper = -1;
+ p_limit_lower = 0;
+ }
+
+ if (r_body_a_points) {
+ JointGizmosDrawer::draw_circle(Vector3::AXIS_Z,
+ BODY_A_RADIUS,
+ p_offset,
+ JointGizmosDrawer::look_body_toward_z(p_trs_joint, p_trs_body_a),
+ p_limit_lower,
+ p_limit_upper,
+ *r_body_a_points);
+ }
+
+ if (r_body_b_points) {
+ JointGizmosDrawer::draw_circle(Vector3::AXIS_Z,
+ BODY_B_RADIUS,
+ p_offset,
+ JointGizmosDrawer::look_body_toward_z(p_trs_joint, p_trs_body_b),
+ p_limit_lower,
+ p_limit_upper,
+ *r_body_b_points);
+ }
+}
+
+void Joint3DGizmoPlugin::CreateSliderJointGizmo(const Transform3D &p_offset, const Transform3D &p_trs_joint, const Transform3D &p_trs_body_a, const Transform3D &p_trs_body_b, real_t p_angular_limit_lower, real_t p_angular_limit_upper, real_t p_linear_limit_lower, real_t p_linear_limit_upper, Vector<Vector3> &r_points, Vector<Vector3> *r_body_a_points, Vector<Vector3> *r_body_b_points) {
+ p_linear_limit_lower = -p_linear_limit_lower;
+ p_linear_limit_upper = -p_linear_limit_upper;
+
+ float cs = 0.25;
+ r_points.push_back(p_offset.translated(Vector3(0, 0, 0.5)).origin);
+ r_points.push_back(p_offset.translated(Vector3(0, 0, -0.5)).origin);
+
+ if (p_linear_limit_lower >= p_linear_limit_upper) {
+ r_points.push_back(p_offset.translated(Vector3(p_linear_limit_upper, 0, 0)).origin);
+ r_points.push_back(p_offset.translated(Vector3(p_linear_limit_lower, 0, 0)).origin);
+
+ r_points.push_back(p_offset.translated(Vector3(p_linear_limit_upper, -cs, -cs)).origin);
+ r_points.push_back(p_offset.translated(Vector3(p_linear_limit_upper, -cs, cs)).origin);
+ r_points.push_back(p_offset.translated(Vector3(p_linear_limit_upper, -cs, cs)).origin);
+ r_points.push_back(p_offset.translated(Vector3(p_linear_limit_upper, cs, cs)).origin);
+ r_points.push_back(p_offset.translated(Vector3(p_linear_limit_upper, cs, cs)).origin);
+ r_points.push_back(p_offset.translated(Vector3(p_linear_limit_upper, cs, -cs)).origin);
+ r_points.push_back(p_offset.translated(Vector3(p_linear_limit_upper, cs, -cs)).origin);
+ r_points.push_back(p_offset.translated(Vector3(p_linear_limit_upper, -cs, -cs)).origin);
+
+ r_points.push_back(p_offset.translated(Vector3(p_linear_limit_lower, -cs, -cs)).origin);
+ r_points.push_back(p_offset.translated(Vector3(p_linear_limit_lower, -cs, cs)).origin);
+ r_points.push_back(p_offset.translated(Vector3(p_linear_limit_lower, -cs, cs)).origin);
+ r_points.push_back(p_offset.translated(Vector3(p_linear_limit_lower, cs, cs)).origin);
+ r_points.push_back(p_offset.translated(Vector3(p_linear_limit_lower, cs, cs)).origin);
+ r_points.push_back(p_offset.translated(Vector3(p_linear_limit_lower, cs, -cs)).origin);
+ r_points.push_back(p_offset.translated(Vector3(p_linear_limit_lower, cs, -cs)).origin);
+ r_points.push_back(p_offset.translated(Vector3(p_linear_limit_lower, -cs, -cs)).origin);
+
+ } else {
+ r_points.push_back(p_offset.translated(Vector3(+cs * 2, 0, 0)).origin);
+ r_points.push_back(p_offset.translated(Vector3(-cs * 2, 0, 0)).origin);
+ }
+
+ if (r_body_a_points) {
+ JointGizmosDrawer::draw_circle(
+ Vector3::AXIS_X,
+ BODY_A_RADIUS,
+ p_offset,
+ JointGizmosDrawer::look_body_toward(Vector3::AXIS_X, p_trs_joint, p_trs_body_a),
+ p_angular_limit_lower,
+ p_angular_limit_upper,
+ *r_body_a_points);
+ }
+
+ if (r_body_b_points) {
+ JointGizmosDrawer::draw_circle(
+ Vector3::AXIS_X,
+ BODY_B_RADIUS,
+ p_offset,
+ JointGizmosDrawer::look_body_toward(Vector3::AXIS_X, p_trs_joint, p_trs_body_b),
+ p_angular_limit_lower,
+ p_angular_limit_upper,
+ *r_body_b_points,
+ true);
+ }
+}
+
+void Joint3DGizmoPlugin::CreateConeTwistJointGizmo(const Transform3D &p_offset, const Transform3D &p_trs_joint, const Transform3D &p_trs_body_a, const Transform3D &p_trs_body_b, real_t p_swing, real_t p_twist, Vector<Vector3> *r_body_a_points, Vector<Vector3> *r_body_b_points) {
+ if (r_body_a_points) {
+ JointGizmosDrawer::draw_cone(
+ p_offset,
+ JointGizmosDrawer::look_body(p_trs_joint, p_trs_body_a),
+ p_swing,
+ p_twist,
+ *r_body_a_points);
+ }
+
+ if (r_body_b_points) {
+ JointGizmosDrawer::draw_cone(
+ p_offset,
+ JointGizmosDrawer::look_body(p_trs_joint, p_trs_body_b),
+ p_swing,
+ p_twist,
+ *r_body_b_points);
+ }
+}
+
+void Joint3DGizmoPlugin::CreateGeneric6DOFJointGizmo(
+ const Transform3D &p_offset,
+ const Transform3D &p_trs_joint,
+ const Transform3D &p_trs_body_a,
+ const Transform3D &p_trs_body_b,
+ real_t p_angular_limit_lower_x,
+ real_t p_angular_limit_upper_x,
+ real_t p_linear_limit_lower_x,
+ real_t p_linear_limit_upper_x,
+ bool p_enable_angular_limit_x,
+ bool p_enable_linear_limit_x,
+ real_t p_angular_limit_lower_y,
+ real_t p_angular_limit_upper_y,
+ real_t p_linear_limit_lower_y,
+ real_t p_linear_limit_upper_y,
+ bool p_enable_angular_limit_y,
+ bool p_enable_linear_limit_y,
+ real_t p_angular_limit_lower_z,
+ real_t p_angular_limit_upper_z,
+ real_t p_linear_limit_lower_z,
+ real_t p_linear_limit_upper_z,
+ bool p_enable_angular_limit_z,
+ bool p_enable_linear_limit_z,
+ Vector<Vector3> &r_points,
+ Vector<Vector3> *r_body_a_points,
+ Vector<Vector3> *r_body_b_points) {
+ float cs = 0.25;
+
+ for (int ax = 0; ax < 3; ax++) {
+ float ll = 0;
+ float ul = 0;
+ float lll = 0;
+ float lul = 0;
+
+ int a1 = 0;
+ int a2 = 0;
+ int a3 = 0;
+ bool enable_ang = false;
+ bool enable_lin = false;
+
+ switch (ax) {
+ case 0:
+ ll = p_angular_limit_lower_x;
+ ul = p_angular_limit_upper_x;
+ lll = -p_linear_limit_lower_x;
+ lul = -p_linear_limit_upper_x;
+ enable_ang = p_enable_angular_limit_x;
+ enable_lin = p_enable_linear_limit_x;
+ a1 = 0;
+ a2 = 1;
+ a3 = 2;
+ break;
+ case 1:
+ ll = p_angular_limit_lower_y;
+ ul = p_angular_limit_upper_y;
+ lll = -p_linear_limit_lower_y;
+ lul = -p_linear_limit_upper_y;
+ enable_ang = p_enable_angular_limit_y;
+ enable_lin = p_enable_linear_limit_y;
+ a1 = 1;
+ a2 = 2;
+ a3 = 0;
+ break;
+ case 2:
+ ll = p_angular_limit_lower_z;
+ ul = p_angular_limit_upper_z;
+ lll = -p_linear_limit_lower_z;
+ lul = -p_linear_limit_upper_z;
+ enable_ang = p_enable_angular_limit_z;
+ enable_lin = p_enable_linear_limit_z;
+ a1 = 2;
+ a2 = 0;
+ a3 = 1;
+ break;
+ }
+
+#define ADD_VTX(x, y, z) \
+ { \
+ Vector3 v; \
+ v[a1] = (x); \
+ v[a2] = (y); \
+ v[a3] = (z); \
+ r_points.push_back(p_offset.translated(v).origin); \
+ }
+
+ if (enable_lin && lll >= lul) {
+ ADD_VTX(lul, 0, 0);
+ ADD_VTX(lll, 0, 0);
+
+ ADD_VTX(lul, -cs, -cs);
+ ADD_VTX(lul, -cs, cs);
+ ADD_VTX(lul, -cs, cs);
+ ADD_VTX(lul, cs, cs);
+ ADD_VTX(lul, cs, cs);
+ ADD_VTX(lul, cs, -cs);
+ ADD_VTX(lul, cs, -cs);
+ ADD_VTX(lul, -cs, -cs);
+
+ ADD_VTX(lll, -cs, -cs);
+ ADD_VTX(lll, -cs, cs);
+ ADD_VTX(lll, -cs, cs);
+ ADD_VTX(lll, cs, cs);
+ ADD_VTX(lll, cs, cs);
+ ADD_VTX(lll, cs, -cs);
+ ADD_VTX(lll, cs, -cs);
+ ADD_VTX(lll, -cs, -cs);
+
+ } else {
+ ADD_VTX(+cs * 2, 0, 0);
+ ADD_VTX(-cs * 2, 0, 0);
+ }
+
+ if (!enable_ang) {
+ ll = 0;
+ ul = -1;
+ }
+
+ if (r_body_a_points) {
+ JointGizmosDrawer::draw_circle(
+ static_cast<Vector3::Axis>(ax),
+ BODY_A_RADIUS,
+ p_offset,
+ JointGizmosDrawer::look_body_toward(static_cast<Vector3::Axis>(ax), p_trs_joint, p_trs_body_a),
+ ll,
+ ul,
+ *r_body_a_points,
+ true);
+ }
+
+ if (r_body_b_points) {
+ JointGizmosDrawer::draw_circle(
+ static_cast<Vector3::Axis>(ax),
+ BODY_B_RADIUS,
+ p_offset,
+ JointGizmosDrawer::look_body_toward(static_cast<Vector3::Axis>(ax), p_trs_joint, p_trs_body_b),
+ ll,
+ ul,
+ *r_body_b_points);
+ }
+ }
+
+#undef ADD_VTX
+}
diff --git a/editor/plugins/node_3d_editor_gizmos.h b/editor/plugins/node_3d_editor_gizmos.h
new file mode 100644
index 0000000000..61ee3a95a9
--- /dev/null
+++ b/editor/plugins/node_3d_editor_gizmos.h
@@ -0,0 +1,635 @@
+/*************************************************************************/
+/* node_3d_editor_gizmos.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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. */
+/*************************************************************************/
+
+#ifndef NODE_3D_EDITOR_GIZMOS_H
+#define NODE_3D_EDITOR_GIZMOS_H
+
+#include "core/templates/ordered_hash_map.h"
+#include "scene/3d/node_3d.h"
+#include "scene/3d/skeleton_3d.h"
+
+class Camera3D;
+class Timer;
+class EditorNode3DGizmoPlugin;
+
+class EditorNode3DGizmo : public Node3DGizmo {
+ GDCLASS(EditorNode3DGizmo, Node3DGizmo);
+
+ struct Instance {
+ RID instance;
+ Ref<ArrayMesh> mesh;
+ Ref<Material> material;
+ Ref<SkinReference> skin_reference;
+ bool extra_margin = false;
+ Transform3D xform;
+
+ void create_instance(Node3D *p_base, bool p_hidden = false);
+ };
+
+ bool selected;
+
+ Vector<Vector3> collision_segments;
+ Ref<TriangleMesh> collision_mesh;
+
+ Vector<Vector3> handles;
+ Vector<int> handle_ids;
+ Vector<Vector3> secondary_handles;
+ Vector<int> secondary_handle_ids;
+
+ float selectable_icon_size;
+ bool billboard_handle;
+
+ bool valid;
+ bool hidden;
+ Vector<Instance> instances;
+ Node3D *spatial_node;
+
+ void _set_spatial_node(Node *p_node) { set_spatial_node(Object::cast_to<Node3D>(p_node)); }
+
+protected:
+ static void _bind_methods();
+
+ EditorNode3DGizmoPlugin *gizmo_plugin;
+
+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, const Ref<Material> &p_material = Ref<Material>(), const Transform3D &p_xform = Transform3D(), const Ref<SkinReference> &p_skin_reference = Ref<SkinReference>());
+ void add_collision_segments(const Vector<Vector3> &p_lines);
+ void add_collision_triangles(const Ref<TriangleMesh> &p_tmesh);
+ void add_unscaled_billboard(const Ref<Material> &p_material, float p_scale = 1, const Color &p_modulate = Color(1, 1, 1));
+ void add_handles(const Vector<Vector3> &p_handles, const Ref<Material> &p_material, const Vector<int> &p_ids = Vector<int>(), bool p_billboard = false, bool p_secondary = false);
+ void add_solid_box(Ref<Material> &p_material, Vector3 p_size, Vector3 p_position = Vector3(), const Transform3D &p_xform = Transform3D());
+
+ virtual bool is_handle_highlighted(int p_id) const;
+ virtual String get_handle_name(int p_id) const;
+ virtual Variant get_handle_value(int p_id) const;
+ virtual void set_handle(int p_id, Camera3D *p_camera, const Point2 &p_point) const;
+ virtual void commit_handle(int p_id, const Variant &p_restore, bool p_cancel = false) const;
+
+ virtual int subgizmos_intersect_ray(Camera3D *p_camera, const Vector2 &p_point) const;
+ virtual Vector<int> subgizmos_intersect_frustum(const Camera3D *p_camera, const Vector<Plane> &p_frustum) const;
+ virtual Transform3D get_subgizmo_transform(int p_id) const;
+ virtual void set_subgizmo_transform(int p_id, Transform3D p_transform) const;
+ virtual void commit_subgizmos(const Vector<int> &p_ids, const Vector<Transform3D> &p_restore, bool p_cancel = false) const;
+
+ void set_selected(bool p_selected) { selected = p_selected; }
+ bool is_selected() const { return selected; }
+
+ void set_spatial_node(Node3D *p_node);
+ Node3D *get_spatial_node() const { return spatial_node; }
+ Ref<EditorNode3DGizmoPlugin> get_plugin() const { return gizmo_plugin; }
+ bool intersect_frustum(const Camera3D *p_camera, const Vector<Plane> &p_frustum);
+ void handles_intersect_ray(Camera3D *p_camera, const Vector2 &p_point, bool p_shift_pressed, int &r_id);
+ bool intersect_ray(Camera3D *p_camera, const Point2 &p_point, Vector3 &r_pos, Vector3 &r_normal);
+ bool is_subgizmo_selected(int p_id) const;
+ Vector<int> get_subgizmo_selection() const;
+
+ virtual void clear() override;
+ virtual void create() override;
+ virtual void transform() override;
+ virtual void redraw() override;
+ virtual void free() override;
+
+ virtual bool is_editable() const;
+
+ void set_hidden(bool p_hidden);
+ void set_plugin(EditorNode3DGizmoPlugin *p_plugin);
+
+ EditorNode3DGizmo();
+ ~EditorNode3DGizmo();
+};
+
+class EditorNode3DGizmoPlugin : public Resource {
+ GDCLASS(EditorNode3DGizmoPlugin, Resource);
+
+public:
+ static const int VISIBLE = 0;
+ static const int HIDDEN = 1;
+ static const int ON_TOP = 2;
+
+protected:
+ int current_state;
+ List<EditorNode3DGizmo *> current_gizmos;
+ HashMap<String, Vector<Ref<StandardMaterial3D>>> materials;
+
+ static void _bind_methods();
+ virtual bool has_gizmo(Node3D *p_spatial);
+ virtual Ref<EditorNode3DGizmo> create_gizmo(Node3D *p_spatial);
+
+public:
+ void create_material(const String &p_name, const Color &p_color, bool p_billboard = false, bool p_on_top = false, bool p_use_vertex_color = false);
+ void create_icon_material(const String &p_name, const Ref<Texture2D> &p_texture, bool p_on_top = false, const Color &p_albedo = Color(1, 1, 1, 1));
+ void create_handle_material(const String &p_name, bool p_billboard = false, const Ref<Texture2D> &p_texture = nullptr);
+ void add_material(const String &p_name, Ref<StandardMaterial3D> p_material);
+
+ Ref<StandardMaterial3D> get_material(const String &p_name, const Ref<EditorNode3DGizmo> &p_gizmo = Ref<EditorNode3DGizmo>());
+
+ virtual String get_gizmo_name() const;
+ virtual int get_priority() const;
+ virtual bool can_be_hidden() const;
+ virtual bool is_selectable_when_hidden() const;
+
+ virtual void redraw(EditorNode3DGizmo *p_gizmo);
+ virtual bool is_handle_highlighted(const EditorNode3DGizmo *p_gizmo, int p_id) const;
+ virtual String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const;
+ virtual Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const;
+ virtual void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const;
+ virtual void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) const;
+
+ virtual int subgizmos_intersect_ray(const EditorNode3DGizmo *p_gizmo, Camera3D *p_camera, const Vector2 &p_point) const;
+ virtual Vector<int> subgizmos_intersect_frustum(const EditorNode3DGizmo *p_gizmo, const Camera3D *p_camera, const Vector<Plane> &p_frustum) const;
+ virtual Transform3D get_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id) const;
+ virtual void set_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id, Transform3D p_transform) const;
+ virtual void commit_subgizmos(const EditorNode3DGizmo *p_gizmo, const Vector<int> &p_ids, const Vector<Transform3D> &p_restore, bool p_cancel = false) const;
+
+ Ref<EditorNode3DGizmo> get_gizmo(Node3D *p_spatial);
+ void set_state(int p_state);
+ int get_state() const;
+ void unregister_gizmo(EditorNode3DGizmo *p_gizmo);
+
+ EditorNode3DGizmoPlugin();
+ virtual ~EditorNode3DGizmoPlugin();
+};
+
+class Light3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(Light3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+
+ String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const override;
+ void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ Light3DGizmoPlugin();
+};
+
+class AudioStreamPlayer3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(AudioStreamPlayer3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+
+ String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const override;
+ void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ AudioStreamPlayer3DGizmoPlugin();
+};
+
+class Camera3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(Camera3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+
+ String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const override;
+ void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ Camera3DGizmoPlugin();
+};
+
+class MeshInstance3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(MeshInstance3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ bool can_be_hidden() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ MeshInstance3DGizmoPlugin();
+};
+
+class OccluderInstance3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(OccluderInstance3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ OccluderInstance3DGizmoPlugin();
+};
+
+class Sprite3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(Sprite3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ bool can_be_hidden() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ Sprite3DGizmoPlugin();
+};
+
+class Position3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(Position3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+ Ref<ArrayMesh> pos3d_mesh;
+ Vector<Vector3> cursor_points;
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ Position3DGizmoPlugin();
+};
+
+class Skeleton3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(Skeleton3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ Skeleton3DGizmoPlugin();
+};
+
+class PhysicalBone3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(PhysicalBone3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ PhysicalBone3DGizmoPlugin();
+};
+
+class RayCast3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(RayCast3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ RayCast3DGizmoPlugin();
+};
+
+class SpringArm3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(SpringArm3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ SpringArm3DGizmoPlugin();
+};
+
+class VehicleWheel3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(VehicleWheel3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ VehicleWheel3DGizmoPlugin();
+};
+
+class SoftBody3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(SoftBody3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ bool is_selectable_when_hidden() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) const override;
+ bool is_handle_highlighted(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+
+ SoftBody3DGizmoPlugin();
+};
+
+class VisibleOnScreenNotifier3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(VisibleOnScreenNotifier3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const override;
+ void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) const override;
+
+ VisibleOnScreenNotifier3DGizmoPlugin();
+};
+
+class CPUParticles3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(CPUParticles3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ bool is_selectable_when_hidden() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+ CPUParticles3DGizmoPlugin();
+};
+
+class GPUParticles3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(GPUParticles3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ bool is_selectable_when_hidden() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const override;
+ void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) const override;
+
+ GPUParticles3DGizmoPlugin();
+};
+
+class GPUParticlesCollision3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(GPUParticlesCollision3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const override;
+ void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) const override;
+
+ GPUParticlesCollision3DGizmoPlugin();
+};
+
+class ReflectionProbeGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(ReflectionProbeGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const override;
+ void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) const override;
+
+ ReflectionProbeGizmoPlugin();
+};
+
+class DecalGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(DecalGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const override;
+ void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) const override;
+
+ DecalGizmoPlugin();
+};
+
+class VoxelGIGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(VoxelGIGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const override;
+ void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) const override;
+
+ VoxelGIGizmoPlugin();
+};
+
+class LightmapGIGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(LightmapGIGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const override;
+ void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) const override;
+
+ LightmapGIGizmoPlugin();
+};
+
+class LightmapProbeGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(LightmapProbeGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const override;
+ void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) const override;
+
+ LightmapProbeGizmoPlugin();
+};
+
+class CollisionObject3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(CollisionObject3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ CollisionObject3DGizmoPlugin();
+};
+
+class CollisionShape3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(CollisionShape3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const override;
+ void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) const override;
+
+ CollisionShape3DGizmoPlugin();
+};
+
+class CollisionPolygon3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(CollisionPolygon3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+ CollisionPolygon3DGizmoPlugin();
+};
+
+class NavigationRegion3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(NavigationRegion3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+ struct _EdgeKey {
+ Vector3 from;
+ Vector3 to;
+
+ bool operator<(const _EdgeKey &p_with) const { return from == p_with.from ? to < p_with.to : from < p_with.from; }
+ };
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ NavigationRegion3DGizmoPlugin();
+};
+
+class JointGizmosDrawer {
+public:
+ static Basis look_body(const Transform3D &p_joint_transform, const Transform3D &p_body_transform);
+ static Basis look_body_toward(Vector3::Axis p_axis, const Transform3D &joint_transform, const Transform3D &body_transform);
+ static Basis look_body_toward_x(const Transform3D &p_joint_transform, const Transform3D &p_body_transform);
+ static Basis look_body_toward_y(const Transform3D &p_joint_transform, const Transform3D &p_body_transform);
+ /// Special function just used for physics joints, it returns a basis constrained toward Joint Z axis
+ /// with axis X and Y that are looking toward the body and oriented toward up
+ static Basis look_body_toward_z(const Transform3D &p_joint_transform, const Transform3D &p_body_transform);
+
+ // Draw circle around p_axis
+ static void draw_circle(Vector3::Axis p_axis, real_t p_radius, const Transform3D &p_offset, const Basis &p_base, real_t p_limit_lower, real_t p_limit_upper, Vector<Vector3> &r_points, bool p_inverse = false);
+ static void draw_cone(const Transform3D &p_offset, const Basis &p_base, real_t p_swing, real_t p_twist, Vector<Vector3> &r_points);
+};
+
+class Joint3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(Joint3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+ Timer *update_timer;
+ uint64_t update_idx = 0;
+
+ void incremental_update_gizmos();
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ static void CreatePinJointGizmo(const Transform3D &p_offset, Vector<Vector3> &r_cursor_points);
+ static void CreateHingeJointGizmo(const Transform3D &p_offset, const Transform3D &p_trs_joint, const Transform3D &p_trs_body_a, const Transform3D &p_trs_body_b, real_t p_limit_lower, real_t p_limit_upper, bool p_use_limit, Vector<Vector3> &r_common_points, Vector<Vector3> *r_body_a_points, Vector<Vector3> *r_body_b_points);
+ static void CreateSliderJointGizmo(const Transform3D &p_offset, const Transform3D &p_trs_joint, const Transform3D &p_trs_body_a, const Transform3D &p_trs_body_b, real_t p_angular_limit_lower, real_t p_angular_limit_upper, real_t p_linear_limit_lower, real_t p_linear_limit_upper, Vector<Vector3> &r_points, Vector<Vector3> *r_body_a_points, Vector<Vector3> *r_body_b_points);
+ static void CreateConeTwistJointGizmo(const Transform3D &p_offset, const Transform3D &p_trs_joint, const Transform3D &p_trs_body_a, const Transform3D &p_trs_body_b, real_t p_swing, real_t p_twist, Vector<Vector3> *r_body_a_points, Vector<Vector3> *r_body_b_points);
+ static void CreateGeneric6DOFJointGizmo(
+ const Transform3D &p_offset,
+ const Transform3D &p_trs_joint,
+ const Transform3D &p_trs_body_a,
+ const Transform3D &p_trs_body_b,
+ real_t p_angular_limit_lower_x,
+ real_t p_angular_limit_upper_x,
+ real_t p_linear_limit_lower_x,
+ real_t p_linear_limit_upper_x,
+ bool p_enable_angular_limit_x,
+ bool p_enable_linear_limit_x,
+ real_t p_angular_limit_lower_y,
+ real_t p_angular_limit_upper_y,
+ real_t p_linear_limit_lower_y,
+ real_t p_linear_limit_upper_y,
+ bool p_enable_angular_limit_y,
+ bool p_enable_linear_limit_y,
+ real_t p_angular_limit_lower_z,
+ real_t p_angular_limit_upper_z,
+ real_t p_linear_limit_lower_z,
+ real_t p_linear_limit_upper_z,
+ bool p_enable_angular_limit_z,
+ bool p_enable_linear_limit_z,
+ Vector<Vector3> &r_points,
+ Vector<Vector3> *r_body_a_points,
+ Vector<Vector3> *r_body_b_points);
+
+ Joint3DGizmoPlugin();
+};
+
+#endif // NODE_3D_EDITOR_GIZMOS_H
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index 7290a39463..ebf593e556 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -35,20 +35,20 @@
#include "core/math/camera_matrix.h"
#include "core/math/math_funcs.h"
#include "core/os/keyboard.h"
-#include "core/string/print_string.h"
#include "core/templates/sort_array.h"
#include "editor/debugger/editor_debugger_node.h"
#include "editor/editor_node.h"
-#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
-#include "editor/node_3d_editor_gizmos.h"
#include "editor/plugins/animation_player_editor_plugin.h"
+#include "editor/plugins/node_3d_editor_gizmos.h"
#include "editor/plugins/script_editor_plugin.h"
#include "scene/3d/camera_3d.h"
#include "scene/3d/collision_shape_3d.h"
+#include "scene/3d/light_3d.h"
#include "scene/3d/mesh_instance_3d.h"
#include "scene/3d/physics_body_3d.h"
#include "scene/3d/visual_instance_3d.h"
+#include "scene/3d/world_environment.h"
#include "scene/gui/center_container.h"
#include "scene/gui/subviewport_container.h"
#include "scene/resources/packed_scene.h"
@@ -58,7 +58,6 @@
#define GIZMO_ARROW_SIZE 0.35
#define GIZMO_RING_HALF_WIDTH 0.1
-#define GIZMO_SCALE_DEFAULT 0.15
#define GIZMO_PLANE_SIZE 0.2
#define GIZMO_PLANE_DST 0.3
#define GIZMO_CIRCLE_SIZE 1.1
@@ -436,16 +435,29 @@ Vector3 Node3DEditorViewport::_get_ray(const Vector2 &p_pos) const {
}
void Node3DEditorViewport::_clear_selected() {
- editor_selection->clear();
-}
-
-void Node3DEditorViewport::_select_clicked(bool p_append, bool p_single, bool p_allow_locked) {
- if (clicked.is_null()) {
- return;
+ _edit.gizmo = Ref<EditorNode3DGizmo>();
+ _edit.gizmo_handle = -1;
+ _edit.gizmo_initial_value = Variant();
+
+ Node3D *selected = spatial_editor->get_single_selected_node();
+ Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;
+
+ if (se && se->gizmo.is_valid()) {
+ se->subgizmos.clear();
+ se->gizmo->redraw();
+ se->gizmo.unref();
+ spatial_editor->update_transform_gizmo();
+ } else {
+ editor_selection->clear();
+ Node3DEditor::get_singleton()->edit(nullptr);
}
+}
- Node *node = Object::cast_to<Node>(ObjectDB::get_instance(clicked));
+void Node3DEditorViewport::_select_clicked(bool p_allow_locked) {
+ Node *node = Object::cast_to<Node3D>(ObjectDB::get_instance(clicked));
Node3D *selected = Object::cast_to<Node3D>(node);
+ clicked = ObjectID();
+
if (!selected) {
return;
}
@@ -462,43 +474,27 @@ void Node3DEditorViewport::_select_clicked(bool p_append, bool p_single, bool p_
}
if (p_allow_locked || !_is_node_locked(selected)) {
- _select(selected, clicked_wants_append, true);
- }
-}
-
-void Node3DEditorViewport::_select(Node *p_node, bool p_append, bool p_single) {
- // Add or remove a single node from the selection
- if (p_append && p_single) {
- if (editor_selection->is_selected(p_node)) {
- // Already in the selection, remove it from the selected nodes
- editor_selection->remove_node(p_node);
+ if (clicked_wants_append) {
+ if (editor_selection->is_selected(selected)) {
+ editor_selection->remove_node(selected);
+ } else {
+ editor_selection->add_node(selected);
+ }
} else {
- // Add the item to the selection
- editor_selection->add_node(p_node);
- }
- } else if (p_append && !p_single) {
- // Add the item to the selection
- editor_selection->add_node(p_node);
- } else {
- // No append; single select
- editor_selection->clear();
- editor_selection->add_node(p_node);
- // Reselect
- if (Engine::get_singleton()->is_editor_hint()) {
- editor->call("edit_node", p_node);
+ if (!editor_selection->is_selected(selected)) {
+ editor_selection->clear();
+ editor_selection->add_node(selected);
+ editor->edit_node(selected);
+ }
}
- }
- if (editor_selection->get_selected_node_list().size() == 1) {
- editor->push_item(editor_selection->get_selected_node_list()[0]);
+ if (editor_selection->get_selected_node_list().size() == 1) {
+ editor->edit_node(editor_selection->get_selected_node_list()[0]);
+ }
}
}
-ObjectID Node3DEditorViewport::_select_ray(const Point2 &p_pos, bool p_append, bool &r_includes_current, int *r_gizmo_handle, bool p_alt_select) {
- if (r_gizmo_handle) {
- *r_gizmo_handle = -1;
- }
-
+ObjectID Node3DEditorViewport::_select_ray(const Point2 &p_pos) {
Vector3 ray = _get_ray(p_pos);
Vector3 pos = _get_ray_pos(p_pos);
Vector2 shrinked_pos = p_pos / subviewport_container->get_stretch_shrink();
@@ -514,7 +510,6 @@ ObjectID Node3DEditorViewport::_select_ray(const Point2 &p_pos, bool p_append, b
ObjectID closest;
Node *item = nullptr;
float closest_dist = 1e20;
- int selected_handle = -1;
for (int i = 0; i < instances.size(); i++) {
Node3D *spat = Object::cast_to<Node3D>(ObjectDB::get_instance(instances[i]));
@@ -523,38 +518,40 @@ ObjectID Node3DEditorViewport::_select_ray(const Point2 &p_pos, bool p_append, b
continue;
}
- Ref<EditorNode3DGizmo> seg = spat->get_gizmo();
+ Vector<Ref<Node3DGizmo>> gizmos = spat->get_gizmos();
- if ((!seg.is_valid()) || found_gizmos.has(seg)) {
- continue;
- }
+ for (int j = 0; j < gizmos.size(); j++) {
+ Ref<EditorNode3DGizmo> seg = gizmos[j];
- found_gizmos.insert(seg);
- Vector3 point;
- Vector3 normal;
+ if ((!seg.is_valid()) || found_gizmos.has(seg)) {
+ continue;
+ }
- int handle = -1;
- bool inters = seg->intersect_ray(camera, shrinked_pos, point, normal, &handle, p_alt_select);
+ found_gizmos.insert(seg);
+ Vector3 point;
+ Vector3 normal;
- if (!inters) {
- continue;
- }
+ bool inters = seg->intersect_ray(camera, shrinked_pos, point, normal);
- float dist = pos.distance_to(point);
+ if (!inters) {
+ continue;
+ }
- if (dist < 0) {
- continue;
- }
+ float dist = pos.distance_to(point);
- if (dist < closest_dist) {
- item = Object::cast_to<Node>(spat);
- if (item != edited_scene) {
- item = edited_scene->get_deepest_editable_node(item);
+ if (dist < 0) {
+ continue;
}
- closest = item->get_instance_id();
- closest_dist = dist;
- selected_handle = handle;
+ if (dist < closest_dist) {
+ item = Object::cast_to<Node>(spat);
+ if (item != edited_scene) {
+ item = edited_scene->get_deepest_editable_node(item);
+ }
+
+ closest = item->get_instance_id();
+ closest_dist = dist;
+ }
}
}
@@ -562,23 +559,15 @@ ObjectID Node3DEditorViewport::_select_ray(const Point2 &p_pos, bool p_append, b
return ObjectID();
}
- if (!editor_selection->is_selected(item) || (r_gizmo_handle && selected_handle >= 0)) {
- if (r_gizmo_handle) {
- *r_gizmo_handle = selected_handle;
- }
- }
-
return closest;
}
-void Node3DEditorViewport::_find_items_at_pos(const Point2 &p_pos, bool &r_includes_current, Vector<_RayResult> &results, bool p_alt_select, bool p_include_locked_nodes) {
+void Node3DEditorViewport::_find_items_at_pos(const Point2 &p_pos, Vector<_RayResult> &r_results, bool p_include_locked_nodes) {
Vector3 ray = _get_ray(p_pos);
Vector3 pos = _get_ray_pos(p_pos);
Vector<ObjectID> instances = RenderingServer::get_singleton()->instances_cull_ray(pos, ray, get_tree()->get_root()->get_world_3d()->get_scenario());
- Set<Ref<EditorNode3DGizmo>> found_gizmos;
-
- r_includes_current = false;
+ Set<Node3D *> found_nodes;
for (int i = 0; i < instances.size(); i++) {
Node3D *spat = Object::cast_to<Node3D>(ObjectDB::get_instance(instances[i]));
@@ -587,53 +576,48 @@ void Node3DEditorViewport::_find_items_at_pos(const Point2 &p_pos, bool &r_inclu
continue;
}
- Ref<EditorNode3DGizmo> seg = spat->get_gizmo();
-
- if (!seg.is_valid()) {
+ if (found_nodes.has(spat)) {
continue;
}
- if (found_gizmos.has(seg)) {
+ if (!p_include_locked_nodes && _is_node_locked(spat)) {
continue;
}
- found_gizmos.insert(seg);
- Vector3 point;
- Vector3 normal;
+ Vector<Ref<Node3DGizmo>> gizmos = spat->get_gizmos();
+ for (int j = 0; j < gizmos.size(); j++) {
+ Ref<EditorNode3DGizmo> seg = gizmos[j];
- int handle = -1;
- bool inters = seg->intersect_ray(camera, p_pos, point, normal, nullptr, p_alt_select);
+ if (!seg.is_valid()) {
+ continue;
+ }
- if (!inters) {
- continue;
- }
+ Vector3 point;
+ Vector3 normal;
- float dist = pos.distance_to(point);
+ bool inters = seg->intersect_ray(camera, p_pos, point, normal);
- if (dist < 0) {
- continue;
- }
+ if (!inters) {
+ continue;
+ }
- if (!p_include_locked_nodes && _is_node_locked(spat)) {
- continue;
- }
+ float dist = pos.distance_to(point);
- if (editor_selection->is_selected(spat)) {
- r_includes_current = true;
- }
+ if (dist < 0) {
+ continue;
+ }
- _RayResult res;
- res.item = spat;
- res.depth = dist;
- res.handle = handle;
- results.push_back(res);
- }
+ found_nodes.insert(spat);
- if (results.is_empty()) {
- return;
+ _RayResult res;
+ res.item = spat;
+ res.depth = dist;
+ r_results.push_back(res);
+ break;
+ }
}
- results.sort();
+ r_results.sort();
}
Vector3 Node3DEditorViewport::_get_screen_to_space(const Vector3 &p_vector3) {
@@ -656,6 +640,9 @@ Vector3 Node3DEditorViewport::_get_screen_to_space(const Vector3 &p_vector3) {
void Node3DEditorViewport::_select_region() {
if (cursor.region_begin == cursor.region_end) {
+ if (!clicked_wants_append) {
+ _clear_selected();
+ }
return; //nothing really
}
@@ -702,7 +689,66 @@ void Node3DEditorViewport::_select_region() {
far.d += get_zfar();
frustum.push_back(far);
+ if (spatial_editor->get_single_selected_node()) {
+ Node3D *single_selected = spatial_editor->get_single_selected_node();
+ Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(single_selected);
+
+ Ref<EditorNode3DGizmo> old_gizmo;
+ if (!clicked_wants_append) {
+ se->subgizmos.clear();
+ old_gizmo = se->gizmo;
+ se->gizmo.unref();
+ }
+
+ bool found_subgizmos = false;
+ Vector<Ref<Node3DGizmo>> gizmos = single_selected->get_gizmos();
+ for (int j = 0; j < gizmos.size(); j++) {
+ Ref<EditorNode3DGizmo> seg = gizmos[j];
+ if (!seg.is_valid()) {
+ continue;
+ }
+
+ if (se->gizmo.is_valid() && se->gizmo != seg) {
+ continue;
+ }
+
+ Vector<int> subgizmos = seg->subgizmos_intersect_frustum(camera, frustum);
+ if (!subgizmos.is_empty()) {
+ se->gizmo = seg;
+ for (int i = 0; i < subgizmos.size(); i++) {
+ int subgizmo_id = subgizmos[i];
+ if (!se->subgizmos.has(subgizmo_id)) {
+ se->subgizmos.insert(subgizmo_id, se->gizmo->get_subgizmo_transform(subgizmo_id));
+ }
+ }
+ found_subgizmos = true;
+ break;
+ }
+ }
+
+ if (!clicked_wants_append || found_subgizmos) {
+ if (se->gizmo.is_valid()) {
+ se->gizmo->redraw();
+ }
+
+ if (old_gizmo != se->gizmo && old_gizmo.is_valid()) {
+ old_gizmo->redraw();
+ }
+
+ spatial_editor->update_transform_gizmo();
+ }
+
+ if (found_subgizmos) {
+ return;
+ }
+ }
+
+ if (!clicked_wants_append) {
+ _clear_selected();
+ }
+
Vector<ObjectID> instances = RenderingServer::get_singleton()->instances_cull_convex(frustum, get_tree()->get_root()->get_world_3d()->get_scenario());
+ Set<Node3D *> found_nodes;
Vector<Node *> selected;
Node *edited_scene = get_tree()->get_edited_scene_root();
@@ -713,6 +759,12 @@ void Node3DEditorViewport::_select_region() {
continue;
}
+ if (found_nodes.has(sp)) {
+ continue;
+ }
+
+ found_nodes.insert(sp);
+
Node *item = Object::cast_to<Node>(sp);
if (item != edited_scene) {
item = edited_scene->get_deepest_editable_node(item);
@@ -731,28 +783,31 @@ void Node3DEditorViewport::_select_region() {
item = sel;
}
- if (selected.find(item) != -1) {
- continue;
- }
-
if (_is_node_locked(item)) {
continue;
}
- Ref<EditorNode3DGizmo> seg = sp->get_gizmo();
+ Vector<Ref<Node3DGizmo>> gizmos = sp->get_gizmos();
+ for (int j = 0; j < gizmos.size(); j++) {
+ Ref<EditorNode3DGizmo> seg = gizmos[j];
+ if (!seg.is_valid()) {
+ continue;
+ }
- if (!seg.is_valid()) {
- continue;
+ if (seg->intersect_frustum(camera, frustum)) {
+ selected.push_back(item);
+ }
}
+ }
- if (seg->intersect_frustum(camera, frustum)) {
- selected.push_back(item);
+ for (int i = 0; i < selected.size(); i++) {
+ if (!editor_selection->is_selected(selected[i])) {
+ editor_selection->add_node(selected[i]);
}
}
- bool single = selected.size() == 1;
- for (int i = 0; i < selected.size(); i++) {
- _select(selected[i], true, single);
+ if (editor_selection->get_selected_node_list().size() == 1) {
+ editor->edit_node(editor_selection->get_selected_node_list()[0]);
}
}
@@ -779,21 +834,34 @@ void Node3DEditorViewport::_compute_edit(const Point2 &p_point) {
spatial_editor->update_transform_gizmo();
_edit.center = spatial_editor->get_gizmo_transform().origin;
- List<Node *> &selection = editor_selection->get_selected_node_list();
+ Node3D *selected = spatial_editor->get_single_selected_node();
+ Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;
- for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
- Node3D *sp = Object::cast_to<Node3D>(E->get());
- if (!sp) {
- continue;
+ if (se && se->gizmo.is_valid()) {
+ for (Map<int, Transform3D>::Element *E = se->subgizmos.front(); E; E = E->next()) {
+ int subgizmo_id = E->key();
+ se->subgizmos[subgizmo_id] = se->gizmo->get_subgizmo_transform(subgizmo_id);
}
+ se->original_local = selected->get_transform();
+ se->original = selected->get_global_transform();
+ } else {
+ List<Node *> &selection = editor_selection->get_selected_node_list();
- Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
- if (!se) {
- continue;
- }
+ for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
+ Node3D *sp = Object::cast_to<Node3D>(E->get());
+ if (!sp) {
+ continue;
+ }
+
+ Node3DEditorSelectedItem *sel_item = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
+
+ if (!sel_item) {
+ continue;
+ }
- se->original = se->sp->get_global_gizmo_transform();
- se->original_local = se->sp->get_local_gizmo_transform();
+ sel_item->original_local = sel_item->sp->get_local_gizmo_transform();
+ sel_item->original = sel_item->sp->get_global_gizmo_transform();
+ }
}
}
@@ -829,7 +897,7 @@ static int _get_key_modifier(Ref<InputEventWithModifiers> e) {
return 0;
}
-bool Node3DEditorViewport::_gizmo_select(const Vector2 &p_screenpos, bool p_highlight_only) {
+bool Node3DEditorViewport::_transform_gizmo_select(const Vector2 &p_screenpos, bool p_highlight_only) {
if (!spatial_editor->is_gizmo_visible()) {
return false;
}
@@ -1015,6 +1083,88 @@ bool Node3DEditorViewport::_gizmo_select(const Vector2 &p_screenpos, bool p_high
return false;
}
+void Node3DEditorViewport::_transform_gizmo_apply(Node3D *p_node, const Transform3D &p_transform, bool p_local) {
+ if (p_transform.basis.determinant() == 0) {
+ return;
+ }
+
+ if (p_local) {
+ p_node->set_transform(p_transform);
+ } else {
+ p_node->set_global_transform(p_transform);
+ }
+}
+
+Transform3D Node3DEditorViewport::_compute_transform(TransformMode p_mode, const Transform3D &p_original, const Transform3D &p_original_local, Vector3 p_motion, double p_extra, bool p_local) {
+ switch (p_mode) {
+ case TRANSFORM_SCALE: {
+ if (p_local) {
+ Basis g = p_original.basis.orthonormalized();
+ Vector3 local_motion = g.inverse().xform(p_motion);
+
+ if (_edit.snap || spatial_editor->is_snap_enabled()) {
+ local_motion.snap(Vector3(p_extra, p_extra, p_extra));
+ }
+
+ Vector3 local_scale = p_original_local.basis.get_scale() * (local_motion + Vector3(1, 1, 1));
+ Transform3D local_t = p_original_local;
+ local_t.basis.set_euler_scale(p_original_local.basis.get_rotation_euler(), local_scale);
+ return local_t;
+ } else {
+ Transform3D base = Transform3D(Basis(), _edit.center);
+ if (_edit.snap || spatial_editor->is_snap_enabled()) {
+ p_motion.snap(Vector3(p_extra, p_extra, p_extra));
+ }
+
+ Transform3D r;
+ r.basis.scale(p_motion + Vector3(1, 1, 1));
+ return base * (r * (base.inverse() * p_original));
+ }
+ }
+ case TRANSFORM_TRANSLATE: {
+ if (p_local) {
+ if (_edit.snap || spatial_editor->is_snap_enabled()) {
+ Basis g = p_original.basis.orthonormalized();
+ Vector3 local_motion = g.inverse().xform(p_motion);
+ local_motion.snap(Vector3(p_extra, p_extra, p_extra));
+
+ p_motion = g.xform(local_motion);
+ }
+
+ } else {
+ if (_edit.snap || spatial_editor->is_snap_enabled()) {
+ p_motion.snap(Vector3(p_extra, p_extra, p_extra));
+ }
+ }
+
+ // Apply translation
+ Transform3D t = p_original;
+ t.origin += p_motion;
+ return t;
+ }
+ case TRANSFORM_ROTATE: {
+ if (p_local) {
+ Basis rot = Basis(p_motion, p_extra);
+
+ Vector3 scale = p_original_local.basis.get_scale();
+ Vector3 euler = (p_original_local.get_basis().orthonormalized() * rot).get_euler();
+ Transform3D t;
+ t.basis.set_euler_scale(euler, scale);
+ t.origin = p_original_local.origin;
+ return t;
+ } else {
+ Transform3D r;
+ r.basis.rotate(p_motion, p_extra);
+ Transform3D base = Transform3D(Basis(), _edit.center);
+ return base * r * base.inverse() * p_original;
+ }
+ }
+ default: {
+ ERR_FAIL_V_MSG(Transform3D(), "Invalid mode in '_compute_transform'");
+ }
+ }
+}
+
void Node3DEditorViewport::_surface_mouse_enter() {
if (!surface->has_focus() && (!get_focus_owner() || !get_focus_owner()->is_text_field())) {
surface->grab_focus();
@@ -1038,7 +1188,7 @@ bool Node3DEditorViewport ::_is_node_locked(const Node *p_node) {
}
void Node3DEditorViewport::_list_select(Ref<InputEventMouseButton> b) {
- _find_items_at_pos(b->get_position(), clicked_includes_current, selection_results, b->is_shift_pressed(), spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT);
+ _find_items_at_pos(b->get_position(), selection_results, spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT);
Node *scene = editor->get_edited_scene();
@@ -1058,10 +1208,8 @@ void Node3DEditorViewport::_list_select(Ref<InputEventMouseButton> b) {
selection_results.clear();
if (clicked.is_valid()) {
- _select_clicked(clicked_wants_append, true, spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT);
- clicked = ObjectID();
+ _select_clicked(spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT);
}
-
} else if (!selection_results.is_empty()) {
NodePath root_path = get_tree()->get_edited_scene_root()->get_path();
StringName root_name = root_path.get_name(root_path.get_name_count() - 1);
@@ -1190,7 +1338,20 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
continue;
}
- sp->set_global_transform(se->original);
+ if (se->gizmo.is_valid()) {
+ Vector<int> ids;
+ Vector<Transform3D> restore;
+
+ for (Map<int, Transform3D>::Element *GE = se->subgizmos.front(); GE; GE = GE->next()) {
+ ids.push_back(GE->key());
+ restore.push_back(GE->value());
+ }
+
+ se->gizmo->commit_subgizmos(ids, restore, true);
+ spatial_editor->update_transform_gizmo();
+ } else {
+ sp->set_global_transform(se->original);
+ }
}
surface->update();
set_message(TTR("Transform Aborted."), 3);
@@ -1271,28 +1432,65 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
can_select_gizmos = view_menu->get_popup()->is_item_checked(idx);
}
- if (can_select_gizmos && spatial_editor->get_selected()) {
- Ref<EditorNode3DGizmo> seg = spatial_editor->get_selected()->get_gizmo();
- if (seg.is_valid()) {
- int handle = -1;
- Vector3 point;
- Vector3 normal;
- bool inters = seg->intersect_ray(camera, _edit.mouse_pos, point, normal, &handle, b->is_shift_pressed());
- if (inters && handle != -1) {
+ if (can_select_gizmos && spatial_editor->get_single_selected_node()) {
+ Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(spatial_editor->get_single_selected_node());
+ Vector<Ref<Node3DGizmo>> gizmos = spatial_editor->get_single_selected_node()->get_gizmos();
+
+ bool intersected_handle = false;
+ for (int i = 0; i < gizmos.size(); i++) {
+ Ref<EditorNode3DGizmo> seg = gizmos[i];
+
+ if ((!seg.is_valid())) {
+ continue;
+ }
+
+ int subgizmo_id = seg->subgizmos_intersect_ray(camera, _edit.mouse_pos);
+ if (subgizmo_id != -1) {
+ ERR_CONTINUE(!se);
+ if (b->is_shift_pressed()) {
+ if (se->subgizmos.has(subgizmo_id)) {
+ se->subgizmos.erase(subgizmo_id);
+ } else {
+ se->subgizmos.insert(subgizmo_id, seg->get_subgizmo_transform(subgizmo_id));
+ }
+ } else {
+ se->subgizmos.clear();
+ se->subgizmos.insert(subgizmo_id, seg->get_subgizmo_transform(subgizmo_id));
+ }
+
+ if (se->subgizmos.is_empty()) {
+ se->gizmo = Ref<EditorNode3DGizmo>();
+ } else {
+ se->gizmo = seg;
+ }
+
+ seg->redraw();
+ spatial_editor->update_transform_gizmo();
+ intersected_handle = true;
+ break;
+ }
+
+ int gizmo_handle = -1;
+ seg->handles_intersect_ray(camera, _edit.mouse_pos, b->is_shift_pressed(), gizmo_handle);
+ if (gizmo_handle != -1) {
_edit.gizmo = seg;
- _edit.gizmo_handle = handle;
- _edit.gizmo_initial_value = seg->get_handle_value(handle);
+ _edit.gizmo_handle = gizmo_handle;
+ _edit.gizmo_initial_value = seg->get_handle_value(gizmo_handle);
+ intersected_handle = true;
break;
}
}
+
+ if (intersected_handle) {
+ break;
+ }
}
- if (_gizmo_select(_edit.mouse_pos)) {
+ if (_transform_gizmo_select(_edit.mouse_pos)) {
break;
}
clicked = ObjectID();
- clicked_includes_current = false;
if ((spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT && b->is_command_pressed()) || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE) {
/* HANDLE ROTATION */
@@ -1325,40 +1523,19 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
break;
}
- // todo scale
-
- int gizmo_handle = -1;
-
- clicked = _select_ray(b->get_position(), b->is_shift_pressed(), clicked_includes_current, &gizmo_handle, b->is_shift_pressed());
+ clicked = _select_ray(b->get_position());
//clicking is always deferred to either move or release
clicked_wants_append = b->is_shift_pressed();
if (clicked.is_null()) {
- if (!clicked_wants_append) {
- _clear_selected();
- }
-
//default to regionselect
cursor.region_select = true;
cursor.region_begin = b->get_position();
cursor.region_end = b->get_position();
}
- if (clicked.is_valid() && gizmo_handle >= 0) {
- Node3D *spa = Object::cast_to<Node3D>(ObjectDB::get_instance(clicked));
- if (spa) {
- Ref<EditorNode3DGizmo> seg = spa->get_gizmo();
- if (seg.is_valid()) {
- _edit.gizmo = seg;
- _edit.gizmo_handle = gizmo_handle;
- _edit.gizmo_initial_value = seg->get_handle_value(gizmo_handle);
- break;
- }
- }
- }
-
surface->update();
} else {
if (_edit.gizmo.is_valid()) {
@@ -1366,47 +1543,57 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
_edit.gizmo = Ref<EditorNode3DGizmo>();
break;
}
+
if (clicked.is_valid()) {
- _select_clicked(clicked_wants_append, true);
- // Processing was deferred.
- clicked = ObjectID();
+ _select_clicked(false);
}
if (cursor.region_select) {
- if (!clicked_wants_append) {
- _clear_selected();
- }
-
_select_region();
cursor.region_select = false;
surface->update();
}
if (_edit.mode != TRANSFORM_NONE) {
- static const char *_transform_name[4] = { "None", "Rotate", "Translate", "Scale" };
- undo_redo->create_action(_transform_name[_edit.mode]);
+ Node3D *selected = spatial_editor->get_single_selected_node();
+ Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;
- List<Node *> &selection = editor_selection->get_selected_node_list();
+ if (se && se->gizmo.is_valid()) {
+ Vector<int> ids;
+ Vector<Transform3D> restore;
- for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
- Node3D *sp = Object::cast_to<Node3D>(E->get());
- if (!sp) {
- continue;
+ for (Map<int, Transform3D>::Element *GE = se->subgizmos.front(); GE; GE = GE->next()) {
+ ids.push_back(GE->key());
+ restore.push_back(GE->value());
}
- Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
- if (!se) {
- continue;
- }
+ se->gizmo->commit_subgizmos(ids, restore, false);
+ spatial_editor->update_transform_gizmo();
+ } else {
+ static const char *_transform_name[4] = { "None", "Rotate", "Translate", "Scale" };
+ undo_redo->create_action(_transform_name[_edit.mode]);
+
+ List<Node *> &selection = editor_selection->get_selected_node_list();
+
+ for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
+ Node3D *sp = Object::cast_to<Node3D>(E->get());
+ if (!sp) {
+ continue;
+ }
+
+ Node3DEditorSelectedItem *sel_item = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
+ if (!sel_item) {
+ continue;
+ }
- undo_redo->add_do_method(sp, "set_global_transform", sp->get_global_gizmo_transform());
- undo_redo->add_undo_method(sp, "set_global_transform", se->original);
+ undo_redo->add_do_method(sp, "set_global_transform", sp->get_global_gizmo_transform());
+ undo_redo->add_undo_method(sp, "set_global_transform", sel_item->original);
+ }
+ undo_redo->commit_action();
}
- undo_redo->commit_action();
_edit.mode = TRANSFORM_NONE;
set_message("");
}
-
surface->update();
}
@@ -1421,31 +1608,39 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
if (m.is_valid()) {
_edit.mouse_pos = m->get_position();
- if (spatial_editor->get_selected()) {
- Ref<EditorNode3DGizmo> seg = spatial_editor->get_selected()->get_gizmo();
- if (seg.is_valid()) {
- int selected_handle = -1;
-
- int handle = -1;
- Vector3 point;
- Vector3 normal;
- bool inters = seg->intersect_ray(camera, _edit.mouse_pos, point, normal, &handle, false);
- if (inters && handle != -1) {
- selected_handle = handle;
+ if (spatial_editor->get_single_selected_node()) {
+ Vector<Ref<Node3DGizmo>> gizmos = spatial_editor->get_single_selected_node()->get_gizmos();
+
+ Ref<EditorNode3DGizmo> found_gizmo;
+ int found_handle = -1;
+
+ for (int i = 0; i < gizmos.size(); i++) {
+ Ref<EditorNode3DGizmo> seg = gizmos[i];
+ if (!seg.is_valid()) {
+ continue;
}
- if (selected_handle != spatial_editor->get_over_gizmo_handle()) {
- spatial_editor->set_over_gizmo_handle(selected_handle);
- spatial_editor->get_selected()->update_gizmo();
- if (selected_handle != -1) {
- spatial_editor->select_gizmo_highlight_axis(-1);
- }
+ seg->handles_intersect_ray(camera, _edit.mouse_pos, false, found_handle);
+
+ if (found_handle != -1) {
+ found_gizmo = seg;
+ break;
}
}
+
+ if (found_gizmo.is_valid()) {
+ spatial_editor->select_gizmo_highlight_axis(-1);
+ }
+
+ if (found_gizmo != spatial_editor->get_current_hover_gizmo() || found_handle != spatial_editor->get_current_hover_gizmo_handle()) {
+ spatial_editor->set_current_hover_gizmo(found_gizmo);
+ spatial_editor->set_current_hover_gizmo_handle(found_handle);
+ spatial_editor->get_single_selected_node()->update_gizmos();
+ }
}
- if (spatial_editor->get_over_gizmo_handle() == -1 && !(m->get_button_mask() & 1) && !_edit.gizmo.is_valid()) {
- _gizmo_select(_edit.mouse_pos, true);
+ if (spatial_editor->get_current_hover_gizmo().is_null() && !(m->get_button_mask() & 1) && !_edit.gizmo.is_valid()) {
+ _transform_gizmo_select(_edit.mouse_pos, true);
}
NavigationScheme nav_scheme = (NavigationScheme)EditorSettings::get_singleton()->get("editors/3d/navigation/navigation_scheme").operator int();
@@ -1469,11 +1664,6 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
} else {
const bool movement_threshold_passed = _edit.original_mouse_pos.distance_to(_edit.mouse_pos) > 8 * EDSCALE;
if (clicked.is_valid() && movement_threshold_passed) {
- if (!clicked_includes_current) {
- _select_clicked(clicked_wants_append, true);
- // Processing was deferred.
- }
-
_compute_edit(_edit.mouse_pos);
clicked = ObjectID();
@@ -1568,8 +1758,6 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
motion = Vector3(scale, scale, scale);
}
- List<Node *> &selection = editor_selection->get_selected_node_list();
-
// Disable local transformation for TRANSFORM_VIEW
bool local_coords = (spatial_editor->are_local_coords_enabled() && _edit.plane != TRANSFORM_VIEW);
@@ -1582,6 +1770,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
set_message(TTR("Scaling: ") + "(" + String::num(motion_snapped.x, snap_step_decimals) + ", " +
String::num(motion_snapped.y, snap_step_decimals) + ", " + String::num(motion_snapped.z, snap_step_decimals) + ")");
+ List<Node *> &selection = editor_selection->get_selected_node_list();
for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
Node3D *sp = Object::cast_to<Node3D>(E->get());
if (!sp) {
@@ -1597,44 +1786,22 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
continue;
}
- Transform3D original = se->original;
- Transform3D original_local = se->original_local;
- Transform3D base = Transform3D(Basis(), _edit.center);
- Transform3D t;
- Vector3 local_scale;
-
- if (local_coords) {
- Basis g = original.basis.orthonormalized();
- Vector3 local_motion = g.inverse().xform(motion);
-
- if (_edit.snap || spatial_editor->is_snap_enabled()) {
- local_motion.snap(Vector3(snap, snap, snap));
- }
-
- local_scale = original_local.basis.get_scale() * (local_motion + Vector3(1, 1, 1));
-
- // Prevent scaling to 0 it would break the gizmo
- Basis check = original_local.basis;
- check.scale(local_scale);
- if (check.determinant() != 0) {
- // Apply scale
- sp->set_scale(local_scale);
+ if (se->gizmo.is_valid()) {
+ for (Map<int, Transform3D>::Element *GE = se->subgizmos.front(); GE; GE = GE->next()) {
+ Transform3D xform = GE->get();
+ Transform3D new_xform = _compute_transform(TRANSFORM_SCALE, se->original * xform, xform, motion, snap, local_coords);
+ if (!local_coords) {
+ new_xform = se->original.inverse() * new_xform;
+ }
+ se->gizmo->set_subgizmo_transform(GE->key(), new_xform);
}
-
} else {
- if (_edit.snap || spatial_editor->is_snap_enabled()) {
- motion.snap(Vector3(snap, snap, snap));
- }
-
- Transform3D r;
- r.basis.scale(motion + Vector3(1, 1, 1));
- t = base * (r * (base.inverse() * original));
-
- // Apply scale
- sp->set_global_transform(t);
+ Transform3D new_xform = _compute_transform(TRANSFORM_SCALE, se->original, se->original_local, motion, snap, local_coords);
+ _transform_gizmo_apply(se->sp, new_xform, local_coords);
}
}
+ spatial_editor->update_transform_gizmo();
surface->update();
} break;
@@ -1691,8 +1858,6 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
}
}
- List<Node *> &selection = editor_selection->get_selected_node_list();
-
// Disable local transformation for TRANSFORM_VIEW
bool local_coords = (spatial_editor->are_local_coords_enabled() && _edit.plane != TRANSFORM_VIEW);
@@ -1704,6 +1869,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
set_message(TTR("Translating: ") + "(" + String::num(motion_snapped.x, snap_step_decimals) + ", " +
String::num(motion_snapped.y, snap_step_decimals) + ", " + String::num(motion_snapped.z, snap_step_decimals) + ")");
+ List<Node *> &selection = editor_selection->get_selected_node_list();
for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
Node3D *sp = Object::cast_to<Node3D>(E->get());
if (!sp) {
@@ -1719,30 +1885,20 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
continue;
}
- Transform3D original = se->original;
- Transform3D t;
-
- if (local_coords) {
- if (_edit.snap || spatial_editor->is_snap_enabled()) {
- Basis g = original.basis.orthonormalized();
- Vector3 local_motion = g.inverse().xform(motion);
- local_motion.snap(Vector3(snap, snap, snap));
-
- motion = g.xform(local_motion);
+ if (se->gizmo.is_valid()) {
+ for (Map<int, Transform3D>::Element *GE = se->subgizmos.front(); GE; GE = GE->next()) {
+ Transform3D xform = GE->get();
+ Transform3D new_xform = _compute_transform(TRANSFORM_TRANSLATE, se->original * xform, xform, motion, snap, local_coords);
+ new_xform = se->original.inverse() * new_xform;
+ se->gizmo->set_subgizmo_transform(GE->key(), new_xform);
}
-
} else {
- if (_edit.snap || spatial_editor->is_snap_enabled()) {
- motion.snap(Vector3(snap, snap, snap));
- }
+ Transform3D new_xform = _compute_transform(TRANSFORM_TRANSLATE, se->original, se->original_local, motion, snap, local_coords);
+ _transform_gizmo_apply(se->sp, new_xform, false);
}
-
- // Apply translation
- t = original;
- t.origin += motion;
- sp->set_global_transform(t);
}
+ spatial_editor->update_transform_gizmo();
surface->update();
} break;
@@ -1796,10 +1952,9 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
set_message(vformat(TTR("Rotating %s degrees."), String::num(angle, snap_step_decimals)));
angle = Math::deg2rad(angle);
- List<Node *> &selection = editor_selection->get_selected_node_list();
-
bool local_coords = (spatial_editor->are_local_coords_enabled() && _edit.plane != TRANSFORM_VIEW); // Disable local transformation for TRANSFORM_VIEW
+ List<Node *> &selection = editor_selection->get_selected_node_list();
for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
Node3D *sp = Object::cast_to<Node3D>(E->get());
if (!sp) {
@@ -1815,32 +1970,24 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
continue;
}
- Transform3D t;
-
- if (local_coords) {
- Transform3D original_local = se->original_local;
- Basis rot = Basis(axis, angle);
-
- t.basis = original_local.get_basis().orthonormalized() * rot;
- t.origin = original_local.origin;
-
- // Apply rotation
- sp->set_transform(t);
- sp->set_scale(original_local.basis.get_scale()); // re-apply original scale
+ Vector3 compute_axis = local_coords ? axis : plane.normal;
+ if (se->gizmo.is_valid()) {
+ for (Map<int, Transform3D>::Element *GE = se->subgizmos.front(); GE; GE = GE->next()) {
+ Transform3D xform = GE->get();
+ Transform3D new_xform = _compute_transform(TRANSFORM_ROTATE, se->original * xform, xform, compute_axis, angle, local_coords);
+ if (!local_coords) {
+ new_xform = se->original.inverse() * new_xform;
+ }
+ se->gizmo->set_subgizmo_transform(GE->key(), new_xform);
+ }
} else {
- Transform3D original = se->original;
- Transform3D r;
- Transform3D base = Transform3D(Basis(), _edit.center);
-
- r.basis.rotate(plane.normal, angle);
- t = base * r * base.inverse() * original;
-
- // Apply rotation
- sp->set_global_transform(t);
+ Transform3D new_xform = _compute_transform(TRANSFORM_ROTATE, se->original, se->original_local, compute_axis, angle, local_coords);
+ _transform_gizmo_apply(se->sp, new_xform, local_coords);
}
}
+ spatial_editor->update_transform_gizmo();
surface->update();
} break;
@@ -3310,8 +3457,7 @@ void Node3DEditorViewport::_selection_result_pressed(int p_result) {
clicked = selection_results[p_result].item->get_instance_id();
if (clicked.is_valid()) {
- _select_clicked(clicked_wants_append, true, spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT);
- clicked = ObjectID();
+ _select_clicked(spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT);
}
}
@@ -3606,6 +3752,13 @@ void Node3DEditorViewport::focus_selection() {
continue;
}
+ if (se->gizmo.is_valid()) {
+ for (Map<int, Transform3D>::Element *GE = se->subgizmos.front(); GE; GE = GE->next()) {
+ center += se->gizmo->get_subgizmo_transform(GE->key()).origin;
+ count++;
+ }
+ }
+
center += sp->get_global_gizmo_transform().origin;
count++;
}
@@ -3644,34 +3797,39 @@ Vector3 Node3DEditorViewport::_get_instance_position(const Point2 &p_pos) const
continue;
}
- Ref<EditorNode3DGizmo> seg = mesh_instance->get_gizmo();
+ Vector<Ref<Node3DGizmo>> gizmos = mesh_instance->get_gizmos();
- if ((!seg.is_valid()) || found_gizmos.has(seg)) {
- continue;
- }
+ for (int j = 0; j < gizmos.size(); j++) {
+ Ref<EditorNode3DGizmo> seg = gizmos[j];
- found_gizmos.insert(seg);
+ if ((!seg.is_valid()) || found_gizmos.has(seg)) {
+ continue;
+ }
- Vector3 hit_point;
- Vector3 hit_normal;
- bool inters = seg->intersect_ray(camera, p_pos, hit_point, hit_normal, nullptr, false);
+ found_gizmos.insert(seg);
- if (!inters) {
- continue;
- }
+ Vector3 hit_point;
+ Vector3 hit_normal;
+ bool inters = seg->intersect_ray(camera, p_pos, hit_point, hit_normal);
- float dist = world_pos.distance_to(hit_point);
+ if (!inters) {
+ continue;
+ }
- if (dist < 0) {
- continue;
- }
+ float dist = world_pos.distance_to(hit_point);
- if (dist < closest_dist) {
- closest_dist = dist;
- point = hit_point;
- normal = hit_normal;
+ if (dist < 0) {
+ continue;
+ }
+
+ if (dist < closest_dist) {
+ closest_dist = dist;
+ point = hit_point;
+ normal = hit_normal;
+ }
}
}
+
Vector3 offset = Vector3();
for (int i = 0; i < 3; i++) {
if (normal[i] > 0.0) {
@@ -3972,9 +4130,8 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito
_edit.mode = TRANSFORM_NONE;
_edit.plane = TRANSFORM_VIEW;
- _edit.edited_gizmo = 0;
_edit.snap = true;
- _edit.gizmo_handle = 0;
+ _edit.gizmo_handle = -1;
index = p_index;
editor = p_editor;
@@ -3982,7 +4139,6 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito
editor_selection = editor->get_editor_selection();
undo_redo = editor->get_undo_redo();
- clicked_includes_current = false;
orthogonal = false;
auto_orthogonal = false;
lock_rotation = false;
@@ -4005,7 +4161,7 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito
surface->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
surface->set_clip_contents(true);
camera = memnew(Camera3D);
- camera->set_disable_gizmo(true);
+ camera->set_disable_gizmos(true);
camera->set_cull_mask(((1 << 20) - 1) | (1 << (GIZMO_BASE_LAYER + p_index)) | (1 << GIZMO_EDIT_LAYER) | (1 << GIZMO_GRID_LAYER) | (1 << MISC_TOOL_LAYER));
viewport->add_child(camera);
camera->make_current();
@@ -4574,43 +4730,54 @@ void Node3DEditor::select_gizmo_highlight_axis(int p_axis) {
}
void Node3DEditor::update_transform_gizmo() {
- List<Node *> &selection = editor_selection->get_selected_node_list();
- AABB center;
- bool first = true;
+ int count = 0;
+ bool local_gizmo_coords = are_local_coords_enabled();
+ Vector3 gizmo_center;
Basis gizmo_basis;
- bool local_gizmo_coords = are_local_coords_enabled();
- for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
- Node3D *sp = Object::cast_to<Node3D>(E->get());
- if (!sp) {
- continue;
- }
+ Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;
- Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
- if (!se) {
- continue;
+ if (se && se->gizmo.is_valid()) {
+ for (Map<int, Transform3D>::Element *E = se->subgizmos.front(); E; E = E->next()) {
+ Transform3D xf = se->sp->get_global_transform() * se->gizmo->get_subgizmo_transform(E->key());
+ gizmo_center += xf.origin;
+ if (count == 0 && local_gizmo_coords) {
+ gizmo_basis = xf.basis;
+ gizmo_basis.orthonormalize();
+ }
+ count++;
}
+ } else {
+ List<Node *> &selection = editor_selection->get_selected_node_list();
+ for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
+ Node3D *sp = Object::cast_to<Node3D>(E->get());
+ if (!sp) {
+ continue;
+ }
- Transform3D xf = se->sp->get_global_gizmo_transform();
+ if (sp->has_meta("_edit_lock_")) {
+ continue;
+ }
+
+ Node3DEditorSelectedItem *sel_item = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
+ if (!sel_item) {
+ continue;
+ }
- if (first) {
- center.position = xf.origin;
- first = false;
- if (local_gizmo_coords) {
+ Transform3D xf = sel_item->sp->get_global_transform();
+ gizmo_center += xf.origin;
+ if (count == 0 && local_gizmo_coords) {
gizmo_basis = xf.basis;
gizmo_basis.orthonormalize();
}
- } else {
- center.expand_to(xf.origin);
- gizmo_basis = Basis();
+ count++;
}
}
- Vector3 pcenter = center.position + center.size * 0.5;
- gizmo.visible = !first;
- gizmo.transform.origin = pcenter;
- gizmo.transform.basis = gizmo_basis;
+ gizmo.visible = count > 0;
+ gizmo.transform.origin = (count > 0) ? gizmo_center / count : Vector3();
+ gizmo.transform.basis = (count == 1) ? gizmo_basis : Basis();
for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {
viewports[i]->update_transform_gizmo_view();
@@ -4621,7 +4788,7 @@ void _update_all_gizmos(Node *p_node) {
for (int i = p_node->get_child_count() - 1; 0 <= i; --i) {
Node3D *spatial_node = Object::cast_to<Node3D>(p_node->get_child(i));
if (spatial_node) {
- spatial_node->update_gizmo();
+ spatial_node->update_gizmos();
}
_update_all_gizmos(p_node->get_child(i));
@@ -4940,22 +5107,38 @@ void Node3DEditor::set_state(const Dictionary &p_state) {
void Node3DEditor::edit(Node3D *p_spatial) {
if (p_spatial != selected) {
if (selected) {
- Ref<EditorNode3DGizmo> seg = selected->get_gizmo();
- if (seg.is_valid()) {
+ Vector<Ref<Node3DGizmo>> gizmos = selected->get_gizmos();
+ for (int i = 0; i < gizmos.size(); i++) {
+ Ref<EditorNode3DGizmo> seg = gizmos[i];
+ if (!seg.is_valid()) {
+ continue;
+ }
seg->set_selected(false);
- selected->update_gizmo();
}
+
+ Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected);
+ if (se) {
+ se->gizmo.unref();
+ se->subgizmos.clear();
+ }
+
+ selected->update_gizmos();
}
selected = p_spatial;
- over_gizmo_handle = -1;
+ current_hover_gizmo = Ref<EditorNode3DGizmo>();
+ current_hover_gizmo_handle = -1;
if (selected) {
- Ref<EditorNode3DGizmo> seg = selected->get_gizmo();
- if (seg.is_valid()) {
+ Vector<Ref<Node3DGizmo>> gizmos = selected->get_gizmos();
+ for (int i = 0; i < gizmos.size(); i++) {
+ Ref<EditorNode3DGizmo> seg = gizmos[i];
+ if (!seg.is_valid()) {
+ continue;
+ }
seg->set_selected(true);
- selected->update_gizmo();
}
+ selected->update_gizmos();
}
}
}
@@ -6027,13 +6210,27 @@ void Node3DEditor::update_grid() {
_init_grid();
}
-bool Node3DEditor::is_any_freelook_active() const {
- for (unsigned int i = 0; i < VIEWPORTS_COUNT; ++i) {
- if (viewports[i]->is_freelook_active()) {
- return true;
+void Node3DEditor::_selection_changed() {
+ _refresh_menu_icons();
+ if (selected && editor_selection->get_selected_node_list().size() != 1) {
+ Vector<Ref<Node3DGizmo>> gizmos = selected->get_gizmos();
+ for (int i = 0; i < gizmos.size(); i++) {
+ Ref<EditorNode3DGizmo> seg = gizmos[i];
+ if (!seg.is_valid()) {
+ continue;
+ }
+ seg->set_selected(false);
+ }
+
+ Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected);
+ if (se) {
+ se->gizmo.unref();
+ se->subgizmos.clear();
}
+ selected->update_gizmos();
+ selected = nullptr;
}
- return false;
+ update_transform_gizmo();
}
void Node3DEditor::_refresh_menu_icons() {
@@ -6325,7 +6522,7 @@ void Node3DEditor::_notification(int p_what) {
get_tree()->connect("node_removed", callable_mp(this, &Node3DEditor::_node_removed));
get_tree()->connect("node_added", callable_mp(this, &Node3DEditor::_node_added));
EditorNode::get_singleton()->get_scene_tree_dock()->get_tree_editor()->connect("node_changed", callable_mp(this, &Node3DEditor::_refresh_menu_icons));
- editor_selection->connect("selection_changed", callable_mp(this, &Node3DEditor::_refresh_menu_icons));
+ editor_selection->connect("selection_changed", callable_mp(this, &Node3DEditor::_selection_changed));
editor->connect("stop_pressed", callable_mp(this, &Node3DEditor::_update_camera_override_button), make_binds(false));
editor->connect("play_pressed", callable_mp(this, &Node3DEditor::_update_camera_override_button), make_binds(true));
@@ -6384,6 +6581,34 @@ void Node3DEditor::_notification(int p_what) {
}
}
+bool Node3DEditor::is_subgizmo_selected(int p_id) {
+ Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;
+ if (se) {
+ return se->subgizmos.has(p_id);
+ }
+ return false;
+}
+
+bool Node3DEditor::is_current_selected_gizmo(const EditorNode3DGizmo *p_gizmo) {
+ Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;
+ if (se) {
+ return se->gizmo == p_gizmo;
+ }
+ return false;
+}
+
+Vector<int> Node3DEditor::get_subgizmo_selection() {
+ Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;
+
+ Vector<int> ret;
+ if (se) {
+ for (Map<int, Transform3D>::Element *E = se->subgizmos.front(); E; E = E->next()) {
+ ret.push_back(E->key());
+ }
+ }
+ return ret;
+}
+
void Node3DEditor::add_control_to_menu_panel(Control *p_control) {
hbc_menu->add_child(p_control);
}
@@ -6411,23 +6636,43 @@ void Node3DEditor::_request_gizmo(Object *p_obj) {
if (!sp) {
return;
}
- if (editor->get_edited_scene() && (sp == editor->get_edited_scene() || (sp->get_owner() && editor->get_edited_scene()->is_ancestor_of(sp)))) {
- Ref<EditorNode3DGizmo> seg;
+ bool is_selected = (sp == selected);
+
+ if (editor->get_edited_scene() && (sp == editor->get_edited_scene() || (sp->get_owner() && editor->get_edited_scene()->is_ancestor_of(sp)))) {
for (int i = 0; i < gizmo_plugins_by_priority.size(); ++i) {
- seg = gizmo_plugins_by_priority.write[i]->get_gizmo(sp);
+ Ref<EditorNode3DGizmo> seg = gizmo_plugins_by_priority.write[i]->get_gizmo(sp);
if (seg.is_valid()) {
- sp->set_gizmo(seg);
+ sp->add_gizmo(seg);
- if (sp == selected) {
- seg->set_selected(true);
- selected->update_gizmo();
+ if (is_selected != seg->is_selected()) {
+ seg->set_selected(is_selected);
}
-
- break;
}
}
+ sp->update_gizmos();
+ }
+}
+
+void Node3DEditor::_clear_subgizmo_selection(Object *p_obj) {
+ Node3D *sp = nullptr;
+ if (p_obj) {
+ sp = Object::cast_to<Node3D>(p_obj);
+ } else {
+ sp = selected;
+ }
+
+ if (!sp) {
+ return;
+ }
+
+ Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
+ if (se) {
+ se->subgizmos.clear();
+ se->gizmo.unref();
+ sp->update_gizmos();
+ update_transform_gizmo();
}
}
@@ -6516,7 +6761,13 @@ void Node3DEditor::_node_removed(Node *p_node) {
}
if (p_node == selected) {
+ Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected);
+ if (se) {
+ se->gizmo.unref();
+ se->subgizmos.clear();
+ }
selected = nullptr;
+ update_transform_gizmo();
}
}
@@ -6554,6 +6805,7 @@ void Node3DEditor::_bind_methods() {
ClassDB::bind_method("_unhandled_key_input", &Node3DEditor::_unhandled_key_input);
ClassDB::bind_method("_get_editor_data", &Node3DEditor::_get_editor_data);
ClassDB::bind_method("_request_gizmo", &Node3DEditor::_request_gizmo);
+ ClassDB::bind_method("_clear_subgizmo_selection", &Node3DEditor::_clear_subgizmo_selection);
ClassDB::bind_method("_refresh_menu_icons", &Node3DEditor::_refresh_menu_icons);
ADD_SIGNAL(MethodInfo("transform_key_request"));
@@ -7130,7 +7382,7 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) {
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::FLOAT, "editors/3d/manipulator_gizmo_opacity", PROPERTY_HINT_RANGE, "0,1,0.01"));
EDITOR_DEF("editors/3d/navigation/show_viewport_rotation_gizmo", true);
- over_gizmo_handle = -1;
+ current_hover_gizmo_handle = -1;
{
//sun popup
@@ -7444,303 +7696,3 @@ Node3DEditorPlugin::Node3DEditorPlugin(EditorNode *p_node) {
Node3DEditorPlugin::~Node3DEditorPlugin() {
}
-
-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_DEF("editors/3d_gizmos/gizmo_colors/instantiated", Color(0.7, 0.7, 0.7, 0.6));
-
- Vector<Ref<StandardMaterial3D>> mats;
-
- for (int i = 0; i < 4; i++) {
- bool selected = i % 2 == 1;
- bool instantiated = i < 2;
-
- Ref<StandardMaterial3D> material = Ref<StandardMaterial3D>(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<Texture2D> &p_texture, bool p_on_top, const Color &p_albedo) {
- Color instantiated_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/instantiated", Color(0.7, 0.7, 0.7, 0.6));
-
- Vector<Ref<StandardMaterial3D>> icons;
-
- for (int i = 0; i < 4; i++) {
- bool selected = i % 2 == 1;
- bool instantiated = i < 2;
-
- Ref<StandardMaterial3D> icon = Ref<StandardMaterial3D>(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<Texture2D> &p_icon) {
- Ref<StandardMaterial3D> handle_material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D));
-
- handle_material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
- handle_material->set_flag(StandardMaterial3D::FLAG_USE_POINT_SIZE, true);
- Ref<Texture2D> handle_t = p_icon != nullptr ? p_icon : Node3DEditor::get_singleton()->get_theme_icon(SNAME("Editor3DHandle"), SNAME("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<Ref<StandardMaterial3D>>();
- materials[p_name].push_back(handle_material);
-}
-
-void EditorNode3DGizmoPlugin::add_material(const String &p_name, Ref<StandardMaterial3D> p_material) {
- materials[p_name] = Vector<Ref<StandardMaterial3D>>();
- materials[p_name].push_back(p_material);
-}
-
-Ref<StandardMaterial3D> EditorNode3DGizmoPlugin::get_material(const String &p_name, const Ref<EditorNode3DGizmo> &p_gizmo) {
- ERR_FAIL_COND_V(!materials.has(p_name), Ref<StandardMaterial3D>());
- ERR_FAIL_COND_V(materials[p_name].size() == 0, Ref<StandardMaterial3D>());
-
- 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<StandardMaterial3D> 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 {
- if (get_script_instance() && get_script_instance()->has_method("_get_gizmo_name")) {
- return get_script_instance()->call("_get_gizmo_name");
- }
- return TTR("Nameless gizmo");
-}
-
-int EditorNode3DGizmoPlugin::get_priority() const {
- if (get_script_instance() && get_script_instance()->has_method("_get_priority")) {
- return get_script_instance()->call("_get_priority");
- }
- return 0;
-}
-
-Ref<EditorNode3DGizmo> 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<EditorNode3DGizmo> ref = create_gizmo(p_spatial);
-
- if (ref.is_null()) {
- return ref;
- }
-
- ref->set_plugin(this);
- ref->set_spatial_node(p_spatial);
- ref->set_hidden(current_state == HIDDEN);
-
- current_gizmos.push_back(ref.ptr());
- return ref;
-}
-
-void EditorNode3DGizmoPlugin::_bind_methods() {
-#define GIZMO_REF PropertyInfo(Variant::OBJECT, "gizmo", PROPERTY_HINT_RESOURCE_TYPE, "EditorNode3DGizmo")
-
- BIND_VMETHOD(MethodInfo(Variant::BOOL, "_has_gizmo", PropertyInfo(Variant::OBJECT, "spatial", PROPERTY_HINT_RESOURCE_TYPE, "Node3D")));
- BIND_VMETHOD(MethodInfo(GIZMO_REF, "_create_gizmo", PropertyInfo(Variant::OBJECT, "spatial", PROPERTY_HINT_RESOURCE_TYPE, "Node3D")));
-
- 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<EditorNode3DGizmo>()));
-
- BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_gizmo_name"));
- BIND_VMETHOD(MethodInfo(Variant::INT, "_get_priority"));
- BIND_VMETHOD(MethodInfo(Variant::BOOL, "_can_be_hidden"));
- BIND_VMETHOD(MethodInfo(Variant::BOOL, "_is_selectable_when_hidden"));
-
- BIND_VMETHOD(MethodInfo("_redraw", GIZMO_REF));
- BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_handle_name", GIZMO_REF, PropertyInfo(Variant::INT, "index")));
-
- MethodInfo hvget(Variant::NIL, "_get_handle_value", GIZMO_REF, PropertyInfo(Variant::INT, "index"));
- hvget.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
- BIND_VMETHOD(hvget);
-
- BIND_VMETHOD(MethodInfo("_set_handle", GIZMO_REF, PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Camera3D"), PropertyInfo(Variant::VECTOR2, "point")));
- MethodInfo cm = MethodInfo("_commit_handle", GIZMO_REF, PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::NIL, "restore"), PropertyInfo(Variant::BOOL, "cancel"));
- cm.default_arguments.push_back(false);
- BIND_VMETHOD(cm);
-
- BIND_VMETHOD(MethodInfo(Variant::BOOL, "_is_handle_highlighted", GIZMO_REF, PropertyInfo(Variant::INT, "index")));
-
-#undef GIZMO_REF
-}
-
-bool EditorNode3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
- if (get_script_instance() && get_script_instance()->has_method("_has_gizmo")) {
- return get_script_instance()->call("_has_gizmo", p_spatial);
- }
- return false;
-}
-
-Ref<EditorNode3DGizmo> EditorNode3DGizmoPlugin::create_gizmo(Node3D *p_spatial) {
- if (get_script_instance() && get_script_instance()->has_method("_create_gizmo")) {
- return get_script_instance()->call("_create_gizmo", p_spatial);
- }
-
- Ref<EditorNode3DGizmo> ref;
- if (has_gizmo(p_spatial)) {
- ref.instantiate();
- }
- return ref;
-}
-
-bool EditorNode3DGizmoPlugin::can_be_hidden() const {
- if (get_script_instance() && get_script_instance()->has_method("_can_be_hidden")) {
- return get_script_instance()->call("_can_be_hidden");
- }
- return true;
-}
-
-bool EditorNode3DGizmoPlugin::is_selectable_when_hidden() const {
- if (get_script_instance() && get_script_instance()->has_method("_is_selectable_when_hidden")) {
- return get_script_instance()->call("_is_selectable_when_hidden");
- }
- return false;
-}
-
-void EditorNode3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
- if (get_script_instance() && get_script_instance()->has_method("_redraw")) {
- Ref<EditorNode3DGizmo> ref(p_gizmo);
- get_script_instance()->call("_redraw", ref);
- }
-}
-
-String EditorNode3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_idx) const {
- if (get_script_instance() && get_script_instance()->has_method("_get_handle_name")) {
- return get_script_instance()->call("_get_handle_name", p_gizmo, p_idx);
- }
- return "";
-}
-
-Variant EditorNode3DGizmoPlugin::get_handle_value(EditorNode3DGizmo *p_gizmo, int p_idx) const {
- if (get_script_instance() && get_script_instance()->has_method("_get_handle_value")) {
- return get_script_instance()->call("_get_handle_value", p_gizmo, p_idx);
- }
- return Variant();
-}
-
-void EditorNode3DGizmoPlugin::set_handle(EditorNode3DGizmo *p_gizmo, int p_idx, Camera3D *p_camera, const Point2 &p_point) {
- if (get_script_instance() && get_script_instance()->has_method("_set_handle")) {
- get_script_instance()->call("_set_handle", p_gizmo, p_idx, p_camera, p_point);
- }
-}
-
-void EditorNode3DGizmoPlugin::commit_handle(EditorNode3DGizmo *p_gizmo, int p_idx, const Variant &p_restore, bool p_cancel) {
- if (get_script_instance() && get_script_instance()->has_method("_commit_handle")) {
- get_script_instance()->call("_commit_handle", p_gizmo, p_idx, p_restore, p_cancel);
- }
-}
-
-bool EditorNode3DGizmoPlugin::is_handle_highlighted(const EditorNode3DGizmo *p_gizmo, int p_idx) const {
- if (get_script_instance() && get_script_instance()->has_method("_is_handle_highlighted")) {
- return get_script_instance()->call("_is_handle_highlighted", p_gizmo, p_idx);
- }
- return false;
-}
-
-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_spatial_node()->set_gizmo(nullptr);
- }
- if (Node3DEditor::get_singleton()) {
- Node3DEditor::get_singleton()->update_all_gizmos();
- }
-}
diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h
index cc65cb53ca..576e4c93d3 100644
--- a/editor/plugins/node_3d_editor_plugin.h
+++ b/editor/plugins/node_3d_editor_plugin.h
@@ -34,6 +34,7 @@
#include "editor/editor_node.h"
#include "editor/editor_plugin.h"
#include "editor/editor_scale.h"
+#include "editor/plugins/node_3d_editor_gizmos.h"
#include "scene/3d/light_3d.h"
#include "scene/3d/visual_instance_3d.h"
#include "scene/3d/world_environment.h"
@@ -43,96 +44,10 @@
class Camera3D;
class Node3DEditor;
-class EditorNode3DGizmoPlugin;
class Node3DEditorViewport;
class SubViewportContainer;
-
-class EditorNode3DGizmo : public Node3DGizmo {
- GDCLASS(EditorNode3DGizmo, Node3DGizmo);
-
- bool selected;
- bool instantiated;
-
-public:
- void set_selected(bool p_selected) { selected = p_selected; }
- bool is_selected() const { return selected; }
-
- struct Instance {
- RID instance;
- Ref<ArrayMesh> mesh;
- Ref<Material> material;
- Ref<SkinReference> skin_reference;
- RID skeleton;
- bool billboard = false;
- bool unscaled = false;
- bool can_intersect = false;
- bool extra_margin = false;
-
- void create_instance(Node3D *p_base, bool p_hidden = false);
- };
-
- Vector<Vector3> collision_segments;
- Ref<TriangleMesh> collision_mesh;
-
- struct Handle {
- Vector3 pos;
- bool billboard = false;
- };
-
- Vector<Vector3> handles;
- Vector<Vector3> secondary_handles;
- float selectable_icon_size;
- bool billboard_handle;
-
- bool valid;
- bool hidden;
- Node3D *base;
- Vector<Instance> instances;
- Node3D *spatial_node;
- EditorNode3DGizmoPlugin *gizmo_plugin;
-
- void _set_spatial_node(Node *p_node) { set_spatial_node(Object::cast_to<Node3D>(p_node)); }
-
-protected:
- static void _bind_methods();
-
-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);
- void add_unscaled_billboard(const Ref<Material> &p_material, float p_scale = 1, const Color &p_modulate = Color(1, 1, 1));
- void add_handles(const Vector<Vector3> &p_handles, const Ref<Material> &p_material, bool p_billboard = false, bool p_secondary = false);
- void add_solid_box(Ref<Material> &p_material, Vector3 p_size, Vector3 p_position = Vector3());
-
- virtual bool is_handle_highlighted(int p_idx) const;
- virtual String get_handle_name(int p_idx) const;
- virtual Variant get_handle_value(int p_idx);
- virtual void set_handle(int p_idx, Camera3D *p_camera, const Point2 &p_point);
- virtual void commit_handle(int p_idx, const Variant &p_restore, bool p_cancel = false);
-
- void set_spatial_node(Node3D *p_node);
- Node3D *get_spatial_node() const { return spatial_node; }
- Ref<EditorNode3DGizmoPlugin> get_plugin() const { return gizmo_plugin; }
- Vector3 get_handle_pos(int p_idx) const;
- bool intersect_frustum(const Camera3D *p_camera, const Vector<Plane> &p_frustum);
- bool intersect_ray(Camera3D *p_camera, const Point2 &p_point, Vector3 &r_pos, Vector3 &r_normal, int *r_gizmo_handle = nullptr, bool p_sec_first = false);
-
- virtual void clear() override;
- virtual void create() override;
- virtual void transform() override;
- virtual void redraw() override;
- virtual void free() override;
-
- virtual bool is_editable() const;
-
- void set_hidden(bool p_hidden);
- void set_plugin(EditorNode3DGizmoPlugin *p_plugin);
-
- EditorNode3DGizmo();
- ~EditorNode3DGizmo();
-};
+class DirectionalLight3D;
+class WorldEnvironment;
class ViewportRotationControl : public Control {
GDCLASS(ViewportRotationControl, Control);
@@ -307,17 +222,15 @@ private:
struct _RayResult {
Node3D *item = nullptr;
float depth = 0;
- int handle = 0;
_FORCE_INLINE_ bool operator<(const _RayResult &p_rr) const { return depth < p_rr.depth; }
};
void _update_name();
void _compute_edit(const Point2 &p_point);
void _clear_selected();
- void _select_clicked(bool p_append, bool p_single, bool p_allow_locked = false);
- void _select(Node *p_node, bool p_append, bool p_single);
- ObjectID _select_ray(const Point2 &p_pos, bool p_append, bool &r_includes_current, int *r_gizmo_handle = nullptr, bool p_alt_select = false);
- void _find_items_at_pos(const Point2 &p_pos, bool &r_includes_current, Vector<_RayResult> &results, bool p_alt_select = false, bool p_include_locked_nodes = false);
+ void _select_clicked(bool p_allow_locked);
+ ObjectID _select_ray(const Point2 &p_pos);
+ void _find_items_at_pos(const Point2 &p_pos, Vector<_RayResult> &r_results, bool p_include_locked);
Vector3 _get_ray_pos(const Vector2 &p_pos) const;
Vector3 _get_ray(const Vector2 &p_pos) const;
Point2 _point_to_screen(const Vector3 &p_point);
@@ -329,7 +242,8 @@ private:
Vector3 _get_screen_to_space(const Vector3 &p_vector3);
void _select_region();
- bool _gizmo_select(const Vector2 &p_screenpos, bool p_highlight_only = false);
+ bool _transform_gizmo_select(const Vector2 &p_screenpos, bool p_highlight_only = false);
+ void _transform_gizmo_apply(Node3D *p_node, const Transform3D &p_transform, bool p_local);
void _nav_pan(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative);
void _nav_zoom(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative);
@@ -342,7 +256,6 @@ private:
ObjectID clicked;
Vector<_RayResult> selection_results;
- bool clicked_includes_current;
bool clicked_wants_append;
PopupMenu *selection_menu;
@@ -383,15 +296,12 @@ private:
Vector3 click_ray;
Vector3 click_ray_pos;
Vector3 center;
- Vector3 orig_gizmo_pos;
- int edited_gizmo = 0;
Point2 mouse_pos;
Point2 original_mouse_pos;
bool snap = false;
Ref<EditorNode3DGizmo> gizmo;
int gizmo_handle = 0;
Variant gizmo_initial_value;
- Vector3 gizmo_initial_pos;
} _edit;
struct Cursor {
@@ -472,6 +382,8 @@ private:
void _project_settings_changed();
+ Transform3D _compute_transform(TransformMode p_mode, const Transform3D &p_original, const Transform3D &p_original_local, Vector3 p_motion, double p_extra, bool p_local);
+
protected:
void _notification(int p_what);
static void _bind_methods();
@@ -512,6 +424,8 @@ public:
Node3D *sp;
RID sbox_instance;
RID sbox_instance_xray;
+ Ref<EditorNode3DGizmo> gizmo;
+ Map<int, Transform3D> subgizmos; // map ID -> initial transform
Node3DEditorSelectedItem() {
sp = nullptr;
@@ -617,7 +531,9 @@ private:
Ref<StandardMaterial3D> plane_gizmo_color_hl[3];
Ref<ShaderMaterial> rotate_gizmo_color_hl[3];
- int over_gizmo_handle;
+ Ref<Node3DGizmo> current_hover_gizmo;
+ int current_hover_gizmo_handle;
+
float snap_translate_value;
float snap_rotate_value;
float snap_scale_value;
@@ -688,7 +604,6 @@ private:
LineEdit *snap_translate;
LineEdit *snap_rotate;
LineEdit *snap_scale;
- PanelContainer *menu_panel;
LineEdit *xform_translate[3];
LineEdit *xform_rotate[3];
@@ -734,6 +649,7 @@ private:
Node3D *selected;
void _request_gizmo(Object *p_obj);
+ void _clear_subgizmo_selection(Object *p_obj = nullptr);
static Node3DEditor *singleton;
@@ -746,8 +662,7 @@ private:
Node3DEditor();
- bool is_any_freelook_active() const;
-
+ void _selection_changed();
void _refresh_menu_icons();
// Preview Sun and Environment
@@ -861,10 +776,16 @@ public:
VSplitContainer *get_shader_split();
HSplitContainer *get_palette_split();
- Node3D *get_selected() { return selected; }
+ Node3D *get_single_selected_node() { return selected; }
+ bool is_current_selected_gizmo(const EditorNode3DGizmo *p_gizmo);
+ bool is_subgizmo_selected(int p_id);
+ Vector<int> get_subgizmo_selection();
+
+ Ref<EditorNode3DGizmo> get_current_hover_gizmo() const { return current_hover_gizmo; }
+ void set_current_hover_gizmo(Ref<EditorNode3DGizmo> p_gizmo) { current_hover_gizmo = p_gizmo; }
- int get_over_gizmo_handle() const { return over_gizmo_handle; }
- void set_over_gizmo_handle(int idx) { over_gizmo_handle = idx; }
+ void set_current_hover_gizmo_handle(int p_id) { current_hover_gizmo_handle = p_id; }
+ int get_current_hover_gizmo_handle() const { return current_hover_gizmo_handle; }
void set_can_preview(Camera3D *p_preview);
@@ -907,50 +828,4 @@ public:
~Node3DEditorPlugin();
};
-class EditorNode3DGizmoPlugin : public Resource {
- GDCLASS(EditorNode3DGizmoPlugin, Resource);
-
-public:
- static const int VISIBLE = 0;
- static const int HIDDEN = 1;
- static const int ON_TOP = 2;
-
-protected:
- int current_state;
- List<EditorNode3DGizmo *> current_gizmos;
- HashMap<String, Vector<Ref<StandardMaterial3D>>> materials;
-
- static void _bind_methods();
- virtual bool has_gizmo(Node3D *p_spatial);
- virtual Ref<EditorNode3DGizmo> create_gizmo(Node3D *p_spatial);
-
-public:
- void create_material(const String &p_name, const Color &p_color, bool p_billboard = false, bool p_on_top = false, bool p_use_vertex_color = false);
- void create_icon_material(const String &p_name, const Ref<Texture2D> &p_texture, bool p_on_top = false, const Color &p_albedo = Color(1, 1, 1, 1));
- void create_handle_material(const String &p_name, bool p_billboard = false, const Ref<Texture2D> &p_texture = nullptr);
- void add_material(const String &p_name, Ref<StandardMaterial3D> p_material);
-
- Ref<StandardMaterial3D> get_material(const String &p_name, const Ref<EditorNode3DGizmo> &p_gizmo = Ref<EditorNode3DGizmo>());
-
- virtual String get_gizmo_name() const;
- virtual int get_priority() const;
- virtual bool can_be_hidden() const;
- virtual bool is_selectable_when_hidden() const;
-
- virtual void redraw(EditorNode3DGizmo *p_gizmo);
- virtual String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_idx) const;
- virtual Variant get_handle_value(EditorNode3DGizmo *p_gizmo, int p_idx) const;
- virtual void set_handle(EditorNode3DGizmo *p_gizmo, int p_idx, Camera3D *p_camera, const Point2 &p_point);
- virtual void commit_handle(EditorNode3DGizmo *p_gizmo, int p_idx, const Variant &p_restore, bool p_cancel = false);
- virtual bool is_handle_highlighted(const EditorNode3DGizmo *p_gizmo, int p_idx) const;
-
- Ref<EditorNode3DGizmo> get_gizmo(Node3D *p_spatial);
- void set_state(int p_state);
- int get_state() const;
- void unregister_gizmo(EditorNode3DGizmo *p_gizmo);
-
- EditorNode3DGizmoPlugin();
- virtual ~EditorNode3DGizmoPlugin();
-};
-
#endif // NODE_3D_EDITOR_PLUGIN_H
diff --git a/editor/plugins/path_3d_editor_plugin.cpp b/editor/plugins/path_3d_editor_plugin.cpp
index 0a95986fe2..63b89aea35 100644
--- a/editor/plugins/path_3d_editor_plugin.cpp
+++ b/editor/plugins/path_3d_editor_plugin.cpp
@@ -36,20 +36,20 @@
#include "node_3d_editor_plugin.h"
#include "scene/resources/curve.h"
-String Path3DGizmo::get_handle_name(int p_idx) const {
+String Path3DGizmo::get_handle_name(int p_id) const {
Ref<Curve3D> c = path->get_curve();
if (c.is_null()) {
return "";
}
- if (p_idx < c->get_point_count()) {
- return TTR("Curve Point #") + itos(p_idx);
+ if (p_id < c->get_point_count()) {
+ return TTR("Curve Point #") + itos(p_id);
}
- p_idx = p_idx - c->get_point_count() + 1;
+ p_id = p_id - c->get_point_count() + 1;
- int idx = p_idx / 2;
- int t = p_idx % 2;
+ int idx = p_id / 2;
+ int t = p_id % 2;
String n = TTR("Curve Point #") + itos(idx);
if (t == 0) {
n += " In";
@@ -60,21 +60,21 @@ String Path3DGizmo::get_handle_name(int p_idx) const {
return n;
}
-Variant Path3DGizmo::get_handle_value(int p_idx) {
+Variant Path3DGizmo::get_handle_value(int p_id) const {
Ref<Curve3D> c = path->get_curve();
if (c.is_null()) {
return Variant();
}
- if (p_idx < c->get_point_count()) {
- original = c->get_point_position(p_idx);
+ if (p_id < c->get_point_count()) {
+ original = c->get_point_position(p_id);
return original;
}
- p_idx = p_idx - c->get_point_count() + 1;
+ p_id = p_id - c->get_point_count() + 1;
- int idx = p_idx / 2;
- int t = p_idx % 2;
+ int idx = p_id / 2;
+ int t = p_id % 2;
Vector3 ofs;
if (t == 0) {
@@ -88,7 +88,7 @@ Variant Path3DGizmo::get_handle_value(int p_idx) {
return ofs;
}
-void Path3DGizmo::set_handle(int p_idx, Camera3D *p_camera, const Point2 &p_point) {
+void Path3DGizmo::set_handle(int p_id, Camera3D *p_camera, const Point2 &p_point) const {
Ref<Curve3D> c = path->get_curve();
if (c.is_null()) {
return;
@@ -100,7 +100,7 @@ void Path3DGizmo::set_handle(int p_idx, Camera3D *p_camera, const Point2 &p_poin
Vector3 ray_dir = p_camera->project_ray_normal(p_point);
// Setting curve point positions
- if (p_idx < c->get_point_count()) {
+ if (p_id < c->get_point_count()) {
Plane p(gt.xform(original), p_camera->get_transform().basis.get_axis(2));
Vector3 inters;
@@ -112,16 +112,16 @@ void Path3DGizmo::set_handle(int p_idx, Camera3D *p_camera, const Point2 &p_poin
}
Vector3 local = gi.xform(inters);
- c->set_point_position(p_idx, local);
+ c->set_point_position(p_id, local);
}
return;
}
- p_idx = p_idx - c->get_point_count() + 1;
+ p_id = p_id - c->get_point_count() + 1;
- int idx = p_idx / 2;
- int t = p_idx % 2;
+ int idx = p_id / 2;
+ int t = p_id % 2;
Vector3 base = c->get_point_position(idx);
@@ -157,7 +157,7 @@ void Path3DGizmo::set_handle(int p_idx, Camera3D *p_camera, const Point2 &p_poin
}
}
-void Path3DGizmo::commit_handle(int p_idx, const Variant &p_restore, bool p_cancel) {
+void Path3DGizmo::commit_handle(int p_id, const Variant &p_restore, bool p_cancel) const {
Ref<Curve3D> c = path->get_curve();
if (c.is_null()) {
return;
@@ -165,27 +165,27 @@ void Path3DGizmo::commit_handle(int p_idx, const Variant &p_restore, bool p_canc
UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
- if (p_idx < c->get_point_count()) {
+ if (p_id < c->get_point_count()) {
if (p_cancel) {
- c->set_point_position(p_idx, p_restore);
+ c->set_point_position(p_id, p_restore);
return;
}
ur->create_action(TTR("Set Curve Point Position"));
- ur->add_do_method(c.ptr(), "set_point_position", p_idx, c->get_point_position(p_idx));
- ur->add_undo_method(c.ptr(), "set_point_position", p_idx, p_restore);
+ ur->add_do_method(c.ptr(), "set_point_position", p_id, c->get_point_position(p_id));
+ ur->add_undo_method(c.ptr(), "set_point_position", p_id, p_restore);
ur->commit_action();
return;
}
- p_idx = p_idx - c->get_point_count() + 1;
+ p_id = p_id - c->get_point_count() + 1;
- int idx = p_idx / 2;
- int t = p_idx % 2;
+ int idx = p_id / 2;
+ int t = p_id % 2;
if (t == 0) {
if (p_cancel) {
- c->set_point_in(p_idx, p_restore);
+ c->set_point_in(p_id, p_restore);
return;
}
@@ -282,7 +282,7 @@ void Path3DGizmo::redraw() {
add_handles(handles, handles_material);
}
if (sec_handles.size()) {
- add_handles(sec_handles, sec_handles_material, false, true);
+ add_handles(sec_handles, sec_handles_material, Vector<int>(), false, true);
}
}
}
diff --git a/editor/plugins/path_3d_editor_plugin.h b/editor/plugins/path_3d_editor_plugin.h
index 13870d7591..5902500526 100644
--- a/editor/plugins/path_3d_editor_plugin.h
+++ b/editor/plugins/path_3d_editor_plugin.h
@@ -31,7 +31,9 @@
#ifndef PATH_EDITOR_PLUGIN_H
#define PATH_EDITOR_PLUGIN_H
-#include "editor/node_3d_editor_gizmos.h"
+#include "editor/editor_plugin.h"
+#include "editor/plugins/node_3d_editor_gizmos.h"
+#include "scene/3d/camera_3d.h"
#include "scene/3d/path_3d.h"
class Path3DGizmo : public EditorNode3DGizmo {
@@ -44,9 +46,9 @@ class Path3DGizmo : public EditorNode3DGizmo {
public:
virtual String get_handle_name(int p_idx) const override;
- virtual Variant get_handle_value(int p_idx) override;
- virtual void set_handle(int p_idx, Camera3D *p_camera, const Point2 &p_point) override;
- virtual void commit_handle(int p_idx, const Variant &p_restore, bool p_cancel = false) override;
+ virtual Variant get_handle_value(int p_id) const override;
+ virtual void set_handle(int p_id, Camera3D *p_camera, const Point2 &p_point) const override;
+ virtual void commit_handle(int p_id, const Variant &p_restore, bool p_cancel = false) const override;
virtual void redraw() override;
Path3DGizmo(Path3D *p_path = nullptr);