Browse Source

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.
jfons 4 years ago
parent
commit
cfb555a081
41 changed files with 1712 additions and 871 deletions
  1. 6 6
      core/math/triangle_mesh.cpp
  2. 100 27
      doc/classes/EditorNode3DGizmo.xml
  3. 80 13
      doc/classes/EditorNode3DGizmoPlugin.xml
  4. 33 6
      doc/classes/Node3D.xml
  5. 580 119
      editor/plugins/node_3d_editor_gizmos.cpp
  6. 200 57
      editor/plugins/node_3d_editor_gizmos.h
  7. 428 272
      editor/plugins/node_3d_editor_plugin.cpp
  8. 26 151
      editor/plugins/node_3d_editor_plugin.h
  9. 28 28
      editor/plugins/path_3d_editor_plugin.cpp
  10. 6 4
      editor/plugins/path_3d_editor_plugin.h
  11. 23 21
      modules/csg/csg_gizmos.cpp
  12. 11 11
      modules/csg/csg_gizmos.h
  13. 26 26
      modules/csg/csg_shape.cpp
  14. 2 2
      modules/navigation/navigation_mesh_editor_plugin.cpp
  15. 2 2
      scene/3d/audio_stream_player_3d.cpp
  16. 4 4
      scene/3d/camera_3d.cpp
  17. 3 3
      scene/3d/collision_polygon_3d.cpp
  18. 3 3
      scene/3d/collision_shape_3d.cpp
  19. 1 1
      scene/3d/decal.cpp
  20. 1 1
      scene/3d/gpu_particles_3d.cpp
  21. 10 10
      scene/3d/gpu_particles_collision_3d.cpp
  22. 2 2
      scene/3d/light_3d.cpp
  23. 1 1
      scene/3d/lightmap_gi.cpp
  24. 2 2
      scene/3d/mesh_instance_3d.cpp
  25. 3 3
      scene/3d/navigation_region_3d.cpp
  26. 88 53
      scene/3d/node_3d.cpp
  27. 12 8
      scene/3d/node_3d.h
  28. 2 2
      scene/3d/occluder_instance_3d.cpp
  29. 1 1
      scene/3d/path_3d.cpp
  30. 3 9
      scene/3d/physics_body_3d.cpp
  31. 10 10
      scene/3d/physics_joint_3d.cpp
  32. 3 3
      scene/3d/ray_cast_3d.cpp
  33. 2 2
      scene/3d/reflection_probe.cpp
  34. 1 1
      scene/3d/skeleton_3d.cpp
  35. 1 1
      scene/3d/spring_arm_3d.cpp
  36. 1 1
      scene/3d/sprite_3d.cpp
  37. 2 2
      scene/3d/vehicle_body_3d.cpp
  38. 1 1
      scene/3d/visible_on_screen_notifier_3d.cpp
  39. 2 2
      scene/3d/voxel_gi.cpp
  40. 1 0
      scene/scene_string_names.cpp
  41. 1 0
      scene/scene_string_names.h

+ 6 - 6
core/math/triangle_mesh.cpp

@@ -32,9 +32,9 @@
 
 #include "core/templates/sort_array.h"
 
-int TriangleMesh::_create_bvh(BVH *p_bvh, BVH **p_bb, int p_from, int p_size, int p_depth, int &max_depth, int &max_alloc) {
-	if (p_depth > max_depth) {
-		max_depth = p_depth;
+int TriangleMesh::_create_bvh(BVH *p_bvh, BVH **p_bb, int p_from, int p_size, int p_depth, int &r_max_depth, int &r_max_alloc) {
+	if (p_depth > r_max_depth) {
+		r_max_depth = p_depth;
 	}
 
 	if (p_size == 1) {
@@ -70,10 +70,10 @@ int TriangleMesh::_create_bvh(BVH *p_bvh, BVH **p_bb, int p_from, int p_size, in
 		} break;
 	}
 
-	int left = _create_bvh(p_bvh, p_bb, p_from, p_size / 2, p_depth + 1, max_depth, max_alloc);
-	int right = _create_bvh(p_bvh, p_bb, p_from + p_size / 2, p_size - p_size / 2, p_depth + 1, max_depth, max_alloc);
+	int left = _create_bvh(p_bvh, p_bb, p_from, p_size / 2, p_depth + 1, r_max_depth, r_max_alloc);
+	int right = _create_bvh(p_bvh, p_bb, p_from + p_size / 2, p_size - p_size / 2, p_depth + 1, r_max_depth, r_max_alloc);
 
-	int index = max_alloc++;
+	int index = r_max_alloc++;
 	BVH *_new = &p_bvh[index];
 	_new->aabb = aabb;
 	_new->center = aabb.position + aabb.size * 0.5;

+ 100 - 27
doc/classes/EditorNode3DGizmo.xml

@@ -1,10 +1,10 @@
 <?xml version="1.0" encoding="UTF-8" ?>
 <class name="EditorNode3DGizmo" inherits="Node3DGizmo" version="4.0">
 	<brief_description>
-		Custom gizmo for editing Node3D objects.
+		Gizmo for editing Node3D objects.
 	</brief_description>
 	<description>
-		Custom gizmo that is used for providing custom visualization and editing (handles) for Node3D objects. See [EditorNode3DGizmoPlugin] for more information.
+		Gizmo that is used for providing custom visualization and editing (handles and subgizmos) for Node3D objects. Can be overridden to create custom gizmos, but for simple gizmos creating a [EditorNode3DGizmoPlugin] is usually recommended.
 	</description>
 	<tutorials>
 	</tutorials>
@@ -12,64 +12,119 @@
 		<method name="_commit_handle" qualifiers="virtual">
 			<return type="void">
 			</return>
-			<argument index="0" name="index" type="int">
+			<argument index="0" name="id" type="int">
 			</argument>
 			<argument index="1" name="restore" type="Variant">
 			</argument>
 			<argument index="2" name="cancel" type="bool" default="false">
 			</argument>
 			<description>
-				Commit a handle being edited (handles must have been previously added by [method add_handles]).
-				If the [code]cancel[/code] parameter is [code]true[/code], an option to restore the edited value to the original is provided.
+				Override this method to commit a handle being edited (handles must have been previously added by [method add_handles]). This usually means creating an [UndoRedo] action for the change, using the current handle value as "do" and the [code]restore[/code] argument as "undo".
+				If the [code]cancel[/code] argument is [code]true[/code], the [code]restore[/code] value should be directly set, without any [UndoRedo] action.
+			</description>
+		</method>
+		<method name="_commit_subgizmos" qualifiers="virtual">
+			<return type="void">
+			</return>
+			<argument index="0" name="ids" type="PackedInt32Array">
+			</argument>
+			<argument index="1" name="restore" type="Array">
+			</argument>
+			<argument index="2" name="cancel" type="bool" default="false">
+			</argument>
+			<description>
+				Override this method to commit a group of subgizmos being edited (see [method _subgizmos_intersect_ray] and [method _subgizmos_intersect_frustum]). This usually means creating an [UndoRedo] action for the change, using the current transforms as "do" and the [code]restore[/code] transforms as "undo".
+				If the [code]cancel[/code] argument is [code]true[/code], the [code]restore[/code] transforms should be directly set, without any [UndoRedo] action.
 			</description>
 		</method>
 		<method name="_get_handle_name" qualifiers="virtual">
 			<return type="String">
 			</return>
-			<argument index="0" name="index" type="int">
+			<argument index="0" name="id" type="int">
 			</argument>
 			<description>
-				Gets the name of an edited handle (handles must have been previously added by [method add_handles]).
+				Override this method to return the name of an edited handle (handles must have been previously added by [method add_handles]).
 				Handles can be named for reference to the user when editing.
 			</description>
 		</method>
 		<method name="_get_handle_value" qualifiers="virtual">
 			<return type="Variant">
 			</return>
-			<argument index="0" name="index" type="int">
+			<argument index="0" name="id" type="int">
 			</argument>
 			<description>
-				Gets actual value of a handle. This value can be anything and used for eventually undoing the motion when calling [method _commit_handle].
+				Override this method to return the current value of a handle. This value will be requested at the start of an edit and used as the [code]restore[/code] argument in [method _commit_handle].
+			</description>
+		</method>
+		<method name="_get_subgizmo_transform" qualifiers="virtual">
+			<return type="Transform3D">
+			</return>
+			<argument index="0" name="id" type="int">
+			</argument>
+			<description>
+				Override this method to return the current transform of a subgizmo. This transform will be requested at the start of an edit and used as the [code]restore[/code] argument in [method _commit_subgizmos].
 			</description>
 		</method>
 		<method name="_is_handle_highlighted" qualifiers="virtual">
 			<return type="bool">
 			</return>
-			<argument index="0" name="index" type="int">
+			<argument index="0" name="id" type="int">
 			</argument>
 			<description>
-				Returns [code]true[/code] if the handle at index [code]index[/code] is highlighted by being hovered with the mouse.
+				Override this method to return [code]true[/code] whenever the given handle should be highlighted in the editor.
 			</description>
 		</method>
 		<method name="_redraw" qualifiers="virtual">
 			<return type="void">
 			</return>
 			<description>
-				This function is called when the [Node3D] this gizmo refers to changes (the [method Node3D.update_gizmo] is called).
+				Override this method to add all the gizmo elements whenever a gizmo update is requested. It's common to call [method clear] at the beginning of this method and then add visual elements depending on the node's properties.
 			</description>
 		</method>
 		<method name="_set_handle" qualifiers="virtual">
 			<return type="void">
 			</return>
-			<argument index="0" name="index" type="int">
+			<argument index="0" name="id" type="int">
 			</argument>
 			<argument index="1" name="camera" type="Camera3D">
 			</argument>
 			<argument index="2" name="point" type="Vector2">
 			</argument>
 			<description>
-				This function is used when the user drags a gizmo handle (previously added with [method add_handles]) in screen coordinates.
-				The [Camera3D] is also provided so screen coordinates can be converted to raycasts.
+				Override this method to update the node properties when the user drags a gizmo handle (previously added with [method add_handles]). The provided [code]point[/code] is the mouse position in screen coordinates and the [code]camera[/code] can be used to convert it to raycasts.
+			</description>
+		</method>
+		<method name="_set_subgizmo_transform" qualifiers="virtual">
+			<return type="void">
+			</return>
+			<argument index="0" name="id" type="int">
+			</argument>
+			<argument index="1" name="transform" type="Transform3D">
+			</argument>
+			<description>
+				Override this method to update the node properties during subgizmo editing (see [method _subgizmos_intersect_ray] and [method _subgizmos_intersect_frustum]). The [code]transform[/code] is given in the Node3D's local coordinate system.
+			</description>
+		</method>
+		<method name="_subgizmos_intersect_frustum" qualifiers="virtual">
+			<return type="PackedInt32Array">
+			</return>
+			<argument index="0" name="camera" type="Camera3D">
+			</argument>
+			<argument index="1" name="frustum" type="Array">
+			</argument>
+			<description>
+				Override this method to allow selecting subgizmos using mouse drag box selection. Given a [code]camera[/code] and a [code]frustum[/code], this method should return which subgizmos are contained within the frustum. The [code]frustum[/code] argument consists of an [code]Array[/code] with all the [code]Plane[/code]s that make up the selection frustum. The returned value should contain a list of unique subgizmo identifiers, which can have any non-negative value and will be used in other virtual methods like [method _get_subgizmo_transform] or [method _commit_subgizmos].
+			</description>
+		</method>
+		<method name="_subgizmos_intersect_ray" qualifiers="virtual">
+			<return type="int">
+			</return>
+			<argument index="0" name="camera" type="Camera3D">
+			</argument>
+			<argument index="1" name="point" type="Vector2">
+			</argument>
+			<description>
+				Override this method to allow selecting subgizmos using mouse clicks. Given a [code]camera[/code] and a [code]point[/code] in screen coordinates, this method should return which subgizmo should be selected. The returned value should be a unique subgizmo identifier, which can have any non-negative value and will be used in other virtual methods like [method _get_subgizmo_transform] or [method _commit_subgizmos].
 			</description>
 		</method>
 		<method name="add_collision_segments">
@@ -78,7 +133,7 @@
 			<argument index="0" name="segments" type="PackedVector3Array">
 			</argument>
 			<description>
-				Adds the specified [code]segments[/code] to the gizmo's collision shape for picking. Call this function during [method _redraw].
+				Adds the specified [code]segments[/code] to the gizmo's collision shape for picking. Call this method during [method _redraw].
 			</description>
 		</method>
 		<method name="add_collision_triangles">
@@ -87,7 +142,7 @@
 			<argument index="0" name="triangles" type="TriangleMesh">
 			</argument>
 			<description>
-				Adds collision triangles to the gizmo for picking. A [TriangleMesh] can be generated from a regular [Mesh] too. Call this function during [method _redraw].
+				Adds collision triangles to the gizmo for picking. A [TriangleMesh] can be generated from a regular [Mesh] too. Call this method during [method _redraw].
 			</description>
 		</method>
 		<method name="add_handles">
@@ -97,13 +152,15 @@
 			</argument>
 			<argument index="1" name="material" type="Material">
 			</argument>
-			<argument index="2" name="billboard" type="bool" default="false">
+			<argument index="2" name="ids" type="PackedInt32Array">
 			</argument>
-			<argument index="3" name="secondary" type="bool" default="false">
+			<argument index="3" name="billboard" type="bool" default="false">
+			</argument>
+			<argument index="4" name="secondary" type="bool" default="false">
 			</argument>
 			<description>
-				Adds a list of handles (points) which can be used to deform the object being edited.
-				There are virtual functions which will be called upon editing of these handles. Call this function during [method _redraw].
+				Adds a list of handles (points) which can be used to edit the properties of the gizmo's Node3D. The [code]ids[/code] argument can be used to specify a custom identifier for each handle, if an empty [code]Array[/code] is passed, the ids will be assigned automatically from the [code]handles[/code] argument order.
+				There are virtual methods which will be called upon editing of these handles. Call this method during [method _redraw].
 			</description>
 		</method>
 		<method name="add_lines">
@@ -118,7 +175,7 @@
 			<argument index="3" name="modulate" type="Color" default="Color(1, 1, 1, 1)">
 			</argument>
 			<description>
-				Adds lines to the gizmo (as sets of 2 points), with a given material. The lines are used for visualizing the gizmo. Call this function during [method _redraw].
+				Adds lines to the gizmo (as sets of 2 points), with a given material. The lines are used for visualizing the gizmo. Call this method during [method _redraw].
 			</description>
 		</method>
 		<method name="add_mesh">
@@ -126,14 +183,14 @@
 			</return>
 			<argument index="0" name="mesh" type="ArrayMesh">
 			</argument>
-			<argument index="1" name="billboard" type="bool" default="false">
+			<argument index="1" name="material" type="Material" default="null">
 			</argument>
-			<argument index="2" name="skeleton" type="SkinReference" default="null">
+			<argument index="2" name="transform" type="Transform3D" default="Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)">
 			</argument>
-			<argument index="3" name="material" type="Material" default="null">
+			<argument index="3" name="skeleton" type="SkinReference" default="null">
 			</argument>
 			<description>
-				Adds a mesh to the gizmo with the specified [code]billboard[/code] state, [code]skeleton[/code] and [code]material[/code]. If [code]billboard[/code] is [code]true[/code], the mesh will rotate to always face the camera. Call this function during [method _redraw].
+				Adds a mesh to the gizmo with the specified [code]material[/code], local [code]transform[/code] and [code]skeleton[/code]. Call this method during [method _redraw].
 			</description>
 		</method>
 		<method name="add_unscaled_billboard">
@@ -146,7 +203,7 @@
 			<argument index="2" name="modulate" type="Color" default="Color(1, 1, 1, 1)">
 			</argument>
 			<description>
-				Adds an unscaled billboard for visualization. Call this function during [method _redraw].
+				Adds an unscaled billboard for visualization and selection. Call this method during [method _redraw].
 			</description>
 		</method>
 		<method name="clear">
@@ -170,6 +227,22 @@
 				Returns the Node3D node associated with this gizmo.
 			</description>
 		</method>
+		<method name="get_subgizmo_selection" qualifiers="const">
+			<return type="PackedInt32Array">
+			</return>
+			<description>
+				Returns a list of the currently selected subgizmos. Can be used to highlight selected elements during [method _redraw].
+			</description>
+		</method>
+		<method name="is_subgizmo_selected" qualifiers="const">
+			<return type="bool">
+			</return>
+			<argument index="0" name="arg0" type="int">
+			</argument>
+			<description>
+				Returns [code]true[/code] if the given subgizmo is currently selected. Can be used to highlight selected elements during [method _redraw].
+			</description>
+		</method>
 		<method name="set_hidden">
 			<return type="void">
 			</return>

+ 80 - 13
doc/classes/EditorNode3DGizmoPlugin.xml

@@ -14,7 +14,7 @@
 			<return type="bool">
 			</return>
 			<description>
-				Override this method to define whether the gizmo can be hidden or not. Returns [code]true[/code] if not overridden.
+				Override this method to define whether the gizmos handled by this plugin can be hidden or not. Returns [code]true[/code] if not overridden.
 			</description>
 		</method>
 		<method name="_commit_handle" qualifiers="virtual">
@@ -22,14 +22,31 @@
 			</return>
 			<argument index="0" name="gizmo" type="EditorNode3DGizmo">
 			</argument>
-			<argument index="1" name="index" type="int">
+			<argument index="1" name="id" type="int">
 			</argument>
 			<argument index="2" name="restore" type="Variant">
 			</argument>
 			<argument index="3" name="cancel" type="bool" default="false">
 			</argument>
 			<description>
-				Override this method to commit gizmo handles. Called for this plugin's active gizmos.
+				Override this method to commit a handle being edited (handles must have been previously added by [method EditorNode3DGizmo.add_handles] during [method _redraw]). This usually means creating an [UndoRedo] action for the change, using the current handle value as "do" and the [code]restore[/code] argument as "undo".
+				If the [code]cancel[/code] argument is [code]true[/code], the [code]restore[/code] value should be directly set, without any [UndoRedo] action. Called for this plugin's active gizmos.
+			</description>
+		</method>
+		<method name="_commit_subgizmos" qualifiers="virtual">
+			<return type="void">
+			</return>
+			<argument index="0" name="gizmo" type="EditorNode3DGizmo">
+			</argument>
+			<argument index="1" name="ids" type="PackedInt32Array">
+			</argument>
+			<argument index="2" name="restore" type="Array">
+			</argument>
+			<argument index="3" name="cancel" type="bool" default="false">
+			</argument>
+			<description>
+				Override this method to commit a group of subgizmos being edited (see [method _subgizmos_intersect_ray] and [method _subgizmos_intersect_frustum]). This usually means creating an [UndoRedo] action for the change, using the current transforms as "do" and the [code]restore[/code] transforms as "undo".
+				If the [code]cancel[/code] argument is [code]true[/code], the [code]restore[/code] transforms should be directly set, without any [UndoRedo] action. Called for this plugin's active gizmos.
 			</description>
 		</method>
 		<method name="_create_gizmo" qualifiers="virtual">
@@ -53,7 +70,7 @@
 			</return>
 			<argument index="0" name="gizmo" type="EditorNode3DGizmo">
 			</argument>
-			<argument index="1" name="index" type="int">
+			<argument index="1" name="id" type="int">
 			</argument>
 			<description>
 				Override this method to provide gizmo's handle names. Called for this plugin's active gizmos.
@@ -64,18 +81,29 @@
 			</return>
 			<argument index="0" name="gizmo" type="EditorNode3DGizmo">
 			</argument>
-			<argument index="1" name="index" type="int">
+			<argument index="1" name="id" type="int">
 			</argument>
 			<description>
-				Gets actual value of a handle from gizmo. Called for this plugin's active gizmos.
+				Override this method to return the current value of a handle. This value will be requested at the start of an edit and used as the [code]restore[/code] argument in [method _commit_handle]. Called for this plugin's active gizmos.
 			</description>
 		</method>
 		<method name="_get_priority" qualifiers="virtual">
 			<return type="int">
 			</return>
 			<description>
-				Override this method to set the gizmo's priority. Higher values correspond to higher priority. If a gizmo with higher priority conflicts with another gizmo, only the gizmo with higher priority will be used.
-				All built-in editor gizmos return a priority of [code]-1[/code]. If not overridden, this method will return [code]0[/code], which means custom gizmos will automatically override built-in gizmos.
+				Override this method to set the gizmo's priority. Gizmos with higher priority will have precedence when processing inputs like handles or subgizmos selection.
+				All built-in editor gizmos return a priority of [code]-1[/code]. If not overridden, this method will return [code]0[/code], which means custom gizmos will automatically get higher priority than built-in gizmos.
+			</description>
+		</method>
+		<method name="_get_subgizmo_transform" qualifiers="virtual">
+			<return type="Transform3D">
+			</return>
+			<argument index="0" name="gizmo" type="EditorNode3DGizmo">
+			</argument>
+			<argument index="1" name="id" type="int">
+			</argument>
+			<description>
+				Override this method to return the current transform of a subgizmo. This transform will be requested at the start of an edit and used in the [code]restore[/code] argument in [method _commit_subgizmos]. Called for this plugin's active gizmos.
 			</description>
 		</method>
 		<method name="_has_gizmo" qualifiers="virtual">
@@ -92,10 +120,10 @@
 			</return>
 			<argument index="0" name="gizmo" type="EditorNode3DGizmo">
 			</argument>
-			<argument index="1" name="index" type="int">
+			<argument index="1" name="id" type="int">
 			</argument>
 			<description>
-				Gets whether a handle is highlighted or not. Called for this plugin's active gizmos.
+				Override this method to return [code]true[/code] whenever to given handle should be highlighted in the editor. Called for this plugin's active gizmos.
 			</description>
 		</method>
 		<method name="_is_selectable_when_hidden" qualifiers="virtual">
@@ -111,7 +139,7 @@
 			<argument index="0" name="gizmo" type="EditorNode3DGizmo">
 			</argument>
 			<description>
-				Callback to redraw the provided gizmo. Called for this plugin's active gizmos.
+				Override this method to add all the gizmo elements whenever a gizmo update is requested. It's common to call [method EditorNode3DGizmo.clear] at the beginning of this method and then add visual elements depending on the node's properties.
 			</description>
 		</method>
 		<method name="_set_handle" qualifiers="virtual">
@@ -119,14 +147,53 @@
 			</return>
 			<argument index="0" name="gizmo" type="EditorNode3DGizmo">
 			</argument>
-			<argument index="1" name="index" type="int">
+			<argument index="1" name="id" type="int">
 			</argument>
 			<argument index="2" name="camera" type="Camera3D">
 			</argument>
 			<argument index="3" name="point" type="Vector2">
 			</argument>
 			<description>
-				Update the value of a handle after it has been updated. Called for this plugin's active gizmos.
+				Override this method to update the node's properties when the user drags a gizmo handle (previously added with [method EditorNode3DGizmo.add_handles]). The provided [code]point[/code] is the mouse position in screen coordinates and the [code]camera[/code] can be used to convert it to raycasts. Called for this plugin's active gizmos.
+			</description>
+		</method>
+		<method name="_set_subgizmo_transform" qualifiers="virtual">
+			<return type="void">
+			</return>
+			<argument index="0" name="gizmo" type="EditorNode3DGizmo">
+			</argument>
+			<argument index="1" name="id" type="int">
+			</argument>
+			<argument index="2" name="transform" type="Transform3D">
+			</argument>
+			<description>
+				Override this method to update the node properties during subgizmo editing (see [method _subgizmos_intersect_ray] and [method _subgizmos_intersect_frustum]). The [code]transform[/code] is given in the Node3D's local coordinate system.  Called for this plugin's active gizmos.
+			</description>
+		</method>
+		<method name="_subgizmos_intersect_frustum" qualifiers="virtual">
+			<return type="PackedInt32Array">
+			</return>
+			<argument index="0" name="gizmo" type="EditorNode3DGizmo">
+			</argument>
+			<argument index="1" name="camera" type="Camera3D">
+			</argument>
+			<argument index="2" name="frustum" type="Array">
+			</argument>
+			<description>
+				Override this method to allow selecting subgizmos using mouse drag box selection. Given a [code]camera[/code] and a [code]frustum[/code], this method should return which subgizmos are contained within the frustum. The [code]frustum[/code] argument consists of an [code]Array[/code] with all the [code]Plane[/code]s that make up the selection frustum. The returned value should contain a list of unique subgizmo identifiers, these identifiers can have any non-negative value and will be used in other virtual methods like [method _get_subgizmo_transform] or [method _commit_subgizmos].  Called for this plugin's active gizmos.
+			</description>
+		</method>
+		<method name="_subgizmos_intersect_ray" qualifiers="virtual">
+			<return type="int">
+			</return>
+			<argument index="0" name="gizmo" type="EditorNode3DGizmo">
+			</argument>
+			<argument index="1" name="camera" type="Camera3D">
+			</argument>
+			<argument index="2" name="point" type="Vector2">
+			</argument>
+			<description>
+				Override this method to allow selecting subgizmos using mouse clicks. Given a [code]camera[/code] and a [code]point[/code] in screen coordinates, this method should return which subgizmo should be selected. The returned value should be a unique subgizmo identifier, which can have any non-negative value and will be used in other virtual methods like [method _get_subgizmo_transform] or [method _commit_subgizmos]. Called for this plugin's active gizmos.
 			</description>
 		</method>
 		<method name="add_material">

+ 33 - 6
doc/classes/Node3D.xml

@@ -13,6 +13,29 @@
 		<link title="All 3D Demos">https://github.com/godotengine/godot-demo-projects/tree/master/3d</link>
 	</tutorials>
 	<methods>
+		<method name="add_gizmo">
+			<return type="void">
+			</return>
+			<argument index="0" name="gizmo" type="Node3DGizmo">
+			</argument>
+			<description>
+				Attach a gizmo to this [code]Node3D[/code].
+			</description>
+		</method>
+		<method name="clear_gizmos">
+			<return type="void">
+			</return>
+			<description>
+				Clear all gizmos attached to this [code]Node3D[/code].
+			</description>
+		</method>
+		<method name="clear_subgizmo_selection">
+			<return type="void">
+			</return>
+			<description>
+				Clears subgizmo selection for this node in the editor. Useful when subgizmo IDs become invalid after a property change.
+			</description>
+		</method>
 		<method name="force_update_transform">
 			<return type="void">
 			</return>
@@ -20,6 +43,13 @@
 				Forces the transform to update. Transform changes in physics are not instant for performance reasons. Transforms are accumulated and then set. Use this if you need an up-to-date transform when doing physics operations.
 			</description>
 		</method>
+		<method name="get_gizmos" qualifiers="const">
+			<return type="Array">
+			</return>
+			<description>
+				Returns all the gizmos attached to this [code]Node3D[/code].
+			</description>
+		</method>
 		<method name="get_parent_node_3d" qualifiers="const">
 			<return type="Node3D">
 			</return>
@@ -276,18 +306,15 @@
 				Changes the node's position by the given offset [Vector3] in local space.
 			</description>
 		</method>
-		<method name="update_gizmo">
+		<method name="update_gizmos">
 			<return type="void">
 			</return>
 			<description>
-				Updates the [Node3DGizmo] of this node.
+				Updates all the [Node3DGizmo]s attached to this node.
 			</description>
 		</method>
 	</methods>
 	<members>
-		<member name="gizmo" type="Node3DGizmo" setter="set_gizmo" getter="get_gizmo">
-			The [Node3DGizmo] for this node. Used for example in [EditorNode3DGizmo] as custom visualization and editing handles in Editor.
-		</member>
 		<member name="global_transform" type="Transform3D" setter="set_global_transform" getter="get_global_transform">
 			World3D space (global) [Transform3D] of this node.
 		</member>
@@ -324,7 +351,7 @@
 	<constants>
 		<constant name="NOTIFICATION_TRANSFORM_CHANGED" value="2000">
 			Node3D nodes receives this notification when their global transform changes. This means that either the current or a parent node changed its transform.
-			In order for [constant NOTIFICATION_TRANSFORM_CHANGED] to work, users first need to ask for it, with [method set_notify_transform]. The notification is also sent if the node is in the editor context and it has a valid gizmo.
+			In order for [constant NOTIFICATION_TRANSFORM_CHANGED] to work, users first need to ask for it, with [method set_notify_transform]. The notification is also sent if the node is in the editor context and it has at least one valid gizmo.
 		</constant>
 		<constant name="NOTIFICATION_ENTER_WORLD" value="41">
 			Node3D nodes receives this notification when they are registered to new [World3D] resource.

File diff suppressed because it is too large
+ 580 - 119
editor/plugins/node_3d_editor_gizmos.cpp


+ 200 - 57
editor/node_3d_editor_gizmos.h → editor/plugins/node_3d_editor_gizmos.h

@@ -28,13 +28,156 @@
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
 /*************************************************************************/
 
-#ifndef SPATIAL_EDITOR_GIZMOS_H
-#define SPATIAL_EDITOR_GIZMOS_H
+#ifndef NODE_3D_EDITOR_GIZMOS_H
+#define NODE_3D_EDITOR_GIZMOS_H
 
-#include "editor/plugins/node_3d_editor_plugin.h"
-#include "scene/3d/camera_3d.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);
@@ -44,10 +187,10 @@ public:
 	String get_gizmo_name() const override;
 	int get_priority() const override;
 
-	String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_idx) const override;
-	Variant get_handle_value(EditorNode3DGizmo *p_gizmo, int p_idx) const override;
-	void set_handle(EditorNode3DGizmo *p_gizmo, int p_idx, Camera3D *p_camera, const Point2 &p_point) override;
-	void commit_handle(EditorNode3DGizmo *p_gizmo, int p_idx, const Variant &p_restore, bool p_cancel = false) 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();
@@ -61,10 +204,10 @@ public:
 	String get_gizmo_name() const override;
 	int get_priority() const override;
 
-	String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_idx) const override;
-	Variant get_handle_value(EditorNode3DGizmo *p_gizmo, int p_idx) const override;
-	void set_handle(EditorNode3DGizmo *p_gizmo, int p_idx, Camera3D *p_camera, const Point2 &p_point) override;
-	void commit_handle(EditorNode3DGizmo *p_gizmo, int p_idx, const Variant &p_restore, bool p_cancel = false) 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();
@@ -78,10 +221,10 @@ public:
 	String get_gizmo_name() const override;
 	int get_priority() const override;
 
-	String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_idx) const override;
-	Variant get_handle_value(EditorNode3DGizmo *p_gizmo, int p_idx) const override;
-	void set_handle(EditorNode3DGizmo *p_gizmo, int p_idx, Camera3D *p_camera, const Point2 &p_point) override;
-	void commit_handle(EditorNode3DGizmo *p_gizmo, int p_idx, const Variant &p_restore, bool p_cancel = false) 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();
@@ -210,10 +353,10 @@ public:
 	bool is_selectable_when_hidden() const override;
 	void redraw(EditorNode3DGizmo *p_gizmo) override;
 
-	String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_idx) const override;
-	Variant get_handle_value(EditorNode3DGizmo *p_gizmo, int p_idx) const override;
-	void commit_handle(EditorNode3DGizmo *p_gizmo, int p_idx, const Variant &p_restore, bool p_cancel) override;
-	bool is_handle_highlighted(const EditorNode3DGizmo *p_gizmo, int idx) 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 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();
 };
@@ -227,10 +370,10 @@ public:
 	int get_priority() const override;
 	void redraw(EditorNode3DGizmo *p_gizmo) override;
 
-	String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_idx) const override;
-	Variant get_handle_value(EditorNode3DGizmo *p_gizmo, int p_idx) const override;
-	void set_handle(EditorNode3DGizmo *p_gizmo, int p_idx, Camera3D *p_camera, const Point2 &p_point) override;
-	void commit_handle(EditorNode3DGizmo *p_gizmo, int p_idx, const Variant &p_restore, bool p_cancel = false) 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();
 };
@@ -257,10 +400,10 @@ public:
 	bool is_selectable_when_hidden() const override;
 	void redraw(EditorNode3DGizmo *p_gizmo) override;
 
-	String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_idx) const override;
-	Variant get_handle_value(EditorNode3DGizmo *p_gizmo, int p_idx) const override;
-	void set_handle(EditorNode3DGizmo *p_gizmo, int p_idx, Camera3D *p_camera, const Point2 &p_point) override;
-	void commit_handle(EditorNode3DGizmo *p_gizmo, int p_idx, const Variant &p_restore, bool p_cancel = false) 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();
 };
@@ -274,10 +417,10 @@ public:
 	int get_priority() const override;
 	void redraw(EditorNode3DGizmo *p_gizmo) override;
 
-	String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_idx) const override;
-	Variant get_handle_value(EditorNode3DGizmo *p_gizmo, int p_idx) const override;
-	void set_handle(EditorNode3DGizmo *p_gizmo, int p_idx, Camera3D *p_camera, const Point2 &p_point) override;
-	void commit_handle(EditorNode3DGizmo *p_gizmo, int p_idx, const Variant &p_restore, bool p_cancel = false) 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();
 };
@@ -291,10 +434,10 @@ public:
 	int get_priority() const override;
 	void redraw(EditorNode3DGizmo *p_gizmo) override;
 
-	String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_idx) const override;
-	Variant get_handle_value(EditorNode3DGizmo *p_gizmo, int p_idx) const override;
-	void set_handle(EditorNode3DGizmo *p_gizmo, int p_idx, Camera3D *p_camera, const Point2 &p_point) override;
-	void commit_handle(EditorNode3DGizmo *p_gizmo, int p_idx, const Variant &p_restore, bool p_cancel = false) 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();
 };
@@ -308,10 +451,10 @@ public:
 	int get_priority() const override;
 	void redraw(EditorNode3DGizmo *p_gizmo) override;
 
-	String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_idx) const override;
-	Variant get_handle_value(EditorNode3DGizmo *p_gizmo, int p_idx) const override;
-	void set_handle(EditorNode3DGizmo *p_gizmo, int p_idx, Camera3D *p_camera, const Point2 &p_point) override;
-	void commit_handle(EditorNode3DGizmo *p_gizmo, int p_idx, const Variant &p_restore, bool p_cancel = false) 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();
 };
@@ -325,10 +468,10 @@ public:
 	int get_priority() const override;
 	void redraw(EditorNode3DGizmo *p_gizmo) override;
 
-	String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_idx) const override;
-	Variant get_handle_value(EditorNode3DGizmo *p_gizmo, int p_idx) const override;
-	void set_handle(EditorNode3DGizmo *p_gizmo, int p_idx, Camera3D *p_camera, const Point2 &p_point) override;
-	void commit_handle(EditorNode3DGizmo *p_gizmo, int p_idx, const Variant &p_restore, bool p_cancel = false) 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();
 };
@@ -342,10 +485,10 @@ public:
 	int get_priority() const override;
 	void redraw(EditorNode3DGizmo *p_gizmo) override;
 
-	String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_idx) const override;
-	Variant get_handle_value(EditorNode3DGizmo *p_gizmo, int p_idx) const override;
-	void set_handle(EditorNode3DGizmo *p_gizmo, int p_idx, Camera3D *p_camera, const Point2 &p_point) override;
-	void commit_handle(EditorNode3DGizmo *p_gizmo, int p_idx, const Variant &p_restore, bool p_cancel = false) 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();
 };
@@ -359,10 +502,10 @@ public:
 	int get_priority() const override;
 	void redraw(EditorNode3DGizmo *p_gizmo) override;
 
-	String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_idx) const override;
-	Variant get_handle_value(EditorNode3DGizmo *p_gizmo, int p_idx) const override;
-	void set_handle(EditorNode3DGizmo *p_gizmo, int p_idx, Camera3D *p_camera, const Point2 &p_point) override;
-	void commit_handle(EditorNode3DGizmo *p_gizmo, int p_idx, const Variant &p_restore, bool p_cancel = false) 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();
 };
@@ -388,10 +531,10 @@ public:
 	int get_priority() const override;
 	void redraw(EditorNode3DGizmo *p_gizmo) override;
 
-	String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_idx) const override;
-	Variant get_handle_value(EditorNode3DGizmo *p_gizmo, int p_idx) const override;
-	void set_handle(EditorNode3DGizmo *p_gizmo, int p_idx, Camera3D *p_camera, const Point2 &p_point) override;
-	void commit_handle(EditorNode3DGizmo *p_gizmo, int p_idx, const Variant &p_restore, bool p_cancel = false) 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();
 };
@@ -489,4 +632,4 @@ public:
 	Joint3DGizmoPlugin();
 };
 
-#endif // SPATIAL_EDITOR_GIZMOS_H
+#endif // NODE_3D_EDITOR_GIZMOS_H

File diff suppressed because it is too large
+ 428 - 272
editor/plugins/node_3d_editor_plugin.cpp


+ 26 - 151
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

+ 28 - 28
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);
 		}
 	}
 }

+ 6 - 4
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);

+ 23 - 21
modules/csg/csg_gizmos.cpp

@@ -29,6 +29,8 @@
 /*************************************************************************/
 
 #include "csg_gizmos.h"
+#include "editor/plugins/node_3d_editor_plugin.h"
+#include "scene/3d/camera_3d.h"
 
 ///////////
 
@@ -48,7 +50,7 @@ CSGShape3DGizmoPlugin::CSGShape3DGizmoPlugin() {
 	create_handle_material("handles");
 }
 
-String CSGShape3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_idx) const {
+String CSGShape3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
 	CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_spatial_node());
 
 	if (Object::cast_to<CSGSphere3D>(cs)) {
@@ -60,17 +62,17 @@ String CSGShape3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo,
 	}
 
 	if (Object::cast_to<CSGCylinder3D>(cs)) {
-		return p_idx == 0 ? "Radius" : "Height";
+		return p_id == 0 ? "Radius" : "Height";
 	}
 
 	if (Object::cast_to<CSGTorus3D>(cs)) {
-		return p_idx == 0 ? "InnerRadius" : "OuterRadius";
+		return p_id == 0 ? "InnerRadius" : "OuterRadius";
 	}
 
 	return "";
 }
 
-Variant CSGShape3DGizmoPlugin::get_handle_value(EditorNode3DGizmo *p_gizmo, int p_idx) const {
+Variant CSGShape3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
 	CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_spatial_node());
 
 	if (Object::cast_to<CSGSphere3D>(cs)) {
@@ -85,18 +87,18 @@ Variant CSGShape3DGizmoPlugin::get_handle_value(EditorNode3DGizmo *p_gizmo, int
 
 	if (Object::cast_to<CSGCylinder3D>(cs)) {
 		CSGCylinder3D *s = Object::cast_to<CSGCylinder3D>(cs);
-		return p_idx == 0 ? s->get_radius() : s->get_height();
+		return p_id == 0 ? s->get_radius() : s->get_height();
 	}
 
 	if (Object::cast_to<CSGTorus3D>(cs)) {
 		CSGTorus3D *s = Object::cast_to<CSGTorus3D>(cs);
-		return p_idx == 0 ? s->get_inner_radius() : s->get_outer_radius();
+		return p_id == 0 ? s->get_inner_radius() : s->get_outer_radius();
 	}
 
 	return Variant();
 }
 
-void CSGShape3DGizmoPlugin::set_handle(EditorNode3DGizmo *p_gizmo, int p_idx, Camera3D *p_camera, const Point2 &p_point) {
+void CSGShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const {
 	CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_spatial_node());
 
 	Transform3D gt = cs->get_global_transform();
@@ -129,10 +131,10 @@ void CSGShape3DGizmoPlugin::set_handle(EditorNode3DGizmo *p_gizmo, int p_idx, Ca
 		CSGBox3D *s = Object::cast_to<CSGBox3D>(cs);
 
 		Vector3 axis;
-		axis[p_idx] = 1.0;
+		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_idx];
+		float d = ra[p_id];
 		if (Node3DEditor::get_singleton()->is_snap_enabled()) {
 			d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
 		}
@@ -142,7 +144,7 @@ void CSGShape3DGizmoPlugin::set_handle(EditorNode3DGizmo *p_gizmo, int p_idx, Ca
 		}
 
 		Vector3 h = s->get_size();
-		h[p_idx] = d * 2;
+		h[p_id] = d * 2;
 		s->set_size(h);
 	}
 
@@ -150,7 +152,7 @@ void CSGShape3DGizmoPlugin::set_handle(EditorNode3DGizmo *p_gizmo, int p_idx, Ca
 		CSGCylinder3D *s = Object::cast_to<CSGCylinder3D>(cs);
 
 		Vector3 axis;
-		axis[p_idx == 0 ? 0 : 1] = 1.0;
+		axis[p_id == 0 ? 0 : 1] = 1.0;
 		Vector3 ra, rb;
 		Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb);
 		float d = axis.dot(ra);
@@ -162,9 +164,9 @@ void CSGShape3DGizmoPlugin::set_handle(EditorNode3DGizmo *p_gizmo, int p_idx, Ca
 			d = 0.001;
 		}
 
-		if (p_idx == 0) {
+		if (p_id == 0) {
 			s->set_radius(d);
-		} else if (p_idx == 1) {
+		} else if (p_id == 1) {
 			s->set_height(d * 2.0);
 		}
 	}
@@ -185,15 +187,15 @@ void CSGShape3DGizmoPlugin::set_handle(EditorNode3DGizmo *p_gizmo, int p_idx, Ca
 			d = 0.001;
 		}
 
-		if (p_idx == 0) {
+		if (p_id == 0) {
 			s->set_inner_radius(d);
-		} else if (p_idx == 1) {
+		} else if (p_id == 1) {
 			s->set_outer_radius(d);
 		}
 	}
 }
 
-void CSGShape3DGizmoPlugin::commit_handle(EditorNode3DGizmo *p_gizmo, int p_idx, const Variant &p_restore, bool p_cancel) {
+void CSGShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) const {
 	CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_spatial_node());
 
 	if (Object::cast_to<CSGSphere3D>(cs)) {
@@ -227,7 +229,7 @@ void CSGShape3DGizmoPlugin::commit_handle(EditorNode3DGizmo *p_gizmo, int p_idx,
 	if (Object::cast_to<CSGCylinder3D>(cs)) {
 		CSGCylinder3D *s = Object::cast_to<CSGCylinder3D>(cs);
 		if (p_cancel) {
-			if (p_idx == 0) {
+			if (p_id == 0) {
 				s->set_radius(p_restore);
 			} else {
 				s->set_height(p_restore);
@@ -236,7 +238,7 @@ void CSGShape3DGizmoPlugin::commit_handle(EditorNode3DGizmo *p_gizmo, int p_idx,
 		}
 
 		UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
-		if (p_idx == 0) {
+		if (p_id == 0) {
 			ur->create_action(TTR("Change Cylinder Radius"));
 			ur->add_do_method(s, "set_radius", s->get_radius());
 			ur->add_undo_method(s, "set_radius", p_restore);
@@ -252,7 +254,7 @@ void CSGShape3DGizmoPlugin::commit_handle(EditorNode3DGizmo *p_gizmo, int p_idx,
 	if (Object::cast_to<CSGTorus3D>(cs)) {
 		CSGTorus3D *s = Object::cast_to<CSGTorus3D>(cs);
 		if (p_cancel) {
-			if (p_idx == 0) {
+			if (p_id == 0) {
 				s->set_inner_radius(p_restore);
 			} else {
 				s->set_outer_radius(p_restore);
@@ -261,7 +263,7 @@ void CSGShape3DGizmoPlugin::commit_handle(EditorNode3DGizmo *p_gizmo, int p_idx,
 		}
 
 		UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
-		if (p_idx == 0) {
+		if (p_id == 0) {
 			ur->create_action(TTR("Change Torus Inner Radius"));
 			ur->add_do_method(s, "set_inner_radius", s->get_inner_radius());
 			ur->add_undo_method(s, "set_inner_radius", p_restore);
@@ -356,7 +358,7 @@ void CSGShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
 				break;
 		}
 
-		p_gizmo->add_mesh(mesh, false, Ref<SkinReference>(), solid_material);
+		p_gizmo->add_mesh(mesh, solid_material);
 	}
 
 	if (Object::cast_to<CSGSphere3D>(cs)) {

+ 11 - 11
modules/csg/csg_gizmos.h

@@ -33,22 +33,22 @@
 
 #include "csg_shape.h"
 #include "editor/editor_plugin.h"
-#include "editor/node_3d_editor_gizmos.h"
+#include "editor/plugins/node_3d_editor_gizmos.h"
 
 class CSGShape3DGizmoPlugin : public EditorNode3DGizmoPlugin {
 	GDCLASS(CSGShape3DGizmoPlugin, 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_idx) const override;
-	Variant get_handle_value(EditorNode3DGizmo *p_gizmo, int p_idx) const override;
-	void set_handle(EditorNode3DGizmo *p_gizmo, int p_idx, Camera3D *p_camera, const Point2 &p_point) override;
-	void commit_handle(EditorNode3DGizmo *p_gizmo, int p_idx, const Variant &p_restore, bool p_cancel) override;
+	virtual bool has_gizmo(Node3D *p_spatial) override;
+	virtual String get_gizmo_name() const override;
+	virtual int get_priority() const override;
+	virtual bool is_selectable_when_hidden() const override;
+	virtual void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+	virtual String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+	virtual Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+	virtual void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const override;
+	virtual void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) const override;
 
 	CSGShape3DGizmoPlugin();
 };

+ 26 - 26
modules/csg/csg_shape.cpp

@@ -548,7 +548,7 @@ void CSGShape3D::_notification(int p_what) {
 void CSGShape3D::set_operation(Operation p_operation) {
 	operation = p_operation;
 	_make_dirty();
-	update_gizmo();
+	update_gizmos();
 }
 
 CSGShape3D::Operation CSGShape3D::get_operation() const {
@@ -845,7 +845,7 @@ CSGBrush *CSGMesh3D::_build_brush() {
 
 void CSGMesh3D::_mesh_changed() {
 	_make_dirty();
-	update_gizmo();
+	update_gizmos();
 }
 
 void CSGMesh3D::set_material(const Ref<Material> &p_material) {
@@ -1034,7 +1034,7 @@ void CSGSphere3D::set_radius(const float p_radius) {
 	ERR_FAIL_COND(p_radius <= 0);
 	radius = p_radius;
 	_make_dirty();
-	update_gizmo();
+	update_gizmos();
 }
 
 float CSGSphere3D::get_radius() const {
@@ -1044,7 +1044,7 @@ float CSGSphere3D::get_radius() const {
 void CSGSphere3D::set_radial_segments(const int p_radial_segments) {
 	radial_segments = p_radial_segments > 4 ? p_radial_segments : 4;
 	_make_dirty();
-	update_gizmo();
+	update_gizmos();
 }
 
 int CSGSphere3D::get_radial_segments() const {
@@ -1054,7 +1054,7 @@ int CSGSphere3D::get_radial_segments() const {
 void CSGSphere3D::set_rings(const int p_rings) {
 	rings = p_rings > 1 ? p_rings : 1;
 	_make_dirty();
-	update_gizmo();
+	update_gizmos();
 }
 
 int CSGSphere3D::get_rings() const {
@@ -1203,7 +1203,7 @@ void CSGBox3D::_bind_methods() {
 void CSGBox3D::set_size(const Vector3 &p_size) {
 	size = p_size;
 	_make_dirty();
-	update_gizmo();
+	update_gizmos();
 }
 
 Vector3 CSGBox3D::get_size() const {
@@ -1213,7 +1213,7 @@ Vector3 CSGBox3D::get_size() const {
 void CSGBox3D::set_material(const Ref<Material> &p_material) {
 	material = p_material;
 	_make_dirty();
-	update_gizmo();
+	update_gizmos();
 }
 
 Ref<Material> CSGBox3D::get_material() const {
@@ -1384,7 +1384,7 @@ void CSGCylinder3D::_bind_methods() {
 void CSGCylinder3D::set_radius(const float p_radius) {
 	radius = p_radius;
 	_make_dirty();
-	update_gizmo();
+	update_gizmos();
 }
 
 float CSGCylinder3D::get_radius() const {
@@ -1394,7 +1394,7 @@ float CSGCylinder3D::get_radius() const {
 void CSGCylinder3D::set_height(const float p_height) {
 	height = p_height;
 	_make_dirty();
-	update_gizmo();
+	update_gizmos();
 }
 
 float CSGCylinder3D::get_height() const {
@@ -1405,7 +1405,7 @@ void CSGCylinder3D::set_sides(const int p_sides) {
 	ERR_FAIL_COND(p_sides < 3);
 	sides = p_sides;
 	_make_dirty();
-	update_gizmo();
+	update_gizmos();
 }
 
 int CSGCylinder3D::get_sides() const {
@@ -1415,7 +1415,7 @@ int CSGCylinder3D::get_sides() const {
 void CSGCylinder3D::set_cone(const bool p_cone) {
 	cone = p_cone;
 	_make_dirty();
-	update_gizmo();
+	update_gizmos();
 }
 
 bool CSGCylinder3D::is_cone() const {
@@ -1603,7 +1603,7 @@ void CSGTorus3D::_bind_methods() {
 void CSGTorus3D::set_inner_radius(const float p_inner_radius) {
 	inner_radius = p_inner_radius;
 	_make_dirty();
-	update_gizmo();
+	update_gizmos();
 }
 
 float CSGTorus3D::get_inner_radius() const {
@@ -1613,7 +1613,7 @@ float CSGTorus3D::get_inner_radius() const {
 void CSGTorus3D::set_outer_radius(const float p_outer_radius) {
 	outer_radius = p_outer_radius;
 	_make_dirty();
-	update_gizmo();
+	update_gizmos();
 }
 
 float CSGTorus3D::get_outer_radius() const {
@@ -1624,7 +1624,7 @@ void CSGTorus3D::set_sides(const int p_sides) {
 	ERR_FAIL_COND(p_sides < 3);
 	sides = p_sides;
 	_make_dirty();
-	update_gizmo();
+	update_gizmos();
 }
 
 int CSGTorus3D::get_sides() const {
@@ -1635,7 +1635,7 @@ void CSGTorus3D::set_ring_sides(const int p_ring_sides) {
 	ERR_FAIL_COND(p_ring_sides < 3);
 	ring_sides = p_ring_sides;
 	_make_dirty();
-	update_gizmo();
+	update_gizmos();
 }
 
 int CSGTorus3D::get_ring_sides() const {
@@ -2172,7 +2172,7 @@ void CSGPolygon3D::_validate_property(PropertyInfo &property) const {
 
 void CSGPolygon3D::_path_changed() {
 	_make_dirty();
-	update_gizmo();
+	update_gizmos();
 }
 
 void CSGPolygon3D::_path_exited() {
@@ -2248,7 +2248,7 @@ void CSGPolygon3D::_bind_methods() {
 void CSGPolygon3D::set_polygon(const Vector<Vector2> &p_polygon) {
 	polygon = p_polygon;
 	_make_dirty();
-	update_gizmo();
+	update_gizmos();
 }
 
 Vector<Vector2> CSGPolygon3D::get_polygon() const {
@@ -2258,7 +2258,7 @@ Vector<Vector2> CSGPolygon3D::get_polygon() const {
 void CSGPolygon3D::set_mode(Mode p_mode) {
 	mode = p_mode;
 	_make_dirty();
-	update_gizmo();
+	update_gizmos();
 	notify_property_list_changed();
 }
 
@@ -2270,7 +2270,7 @@ void CSGPolygon3D::set_depth(const float p_depth) {
 	ERR_FAIL_COND(p_depth < 0.001);
 	depth = p_depth;
 	_make_dirty();
-	update_gizmo();
+	update_gizmos();
 }
 
 float CSGPolygon3D::get_depth() const {
@@ -2290,7 +2290,7 @@ void CSGPolygon3D::set_spin_degrees(const float p_spin_degrees) {
 	ERR_FAIL_COND(p_spin_degrees < 0.01 || p_spin_degrees > 360);
 	spin_degrees = p_spin_degrees;
 	_make_dirty();
-	update_gizmo();
+	update_gizmos();
 }
 
 float CSGPolygon3D::get_spin_degrees() const {
@@ -2301,7 +2301,7 @@ void CSGPolygon3D::set_spin_sides(const int p_spin_sides) {
 	ERR_FAIL_COND(p_spin_sides < 3);
 	spin_sides = p_spin_sides;
 	_make_dirty();
-	update_gizmo();
+	update_gizmos();
 }
 
 int CSGPolygon3D::get_spin_sides() const {
@@ -2311,7 +2311,7 @@ int CSGPolygon3D::get_spin_sides() const {
 void CSGPolygon3D::set_path_node(const NodePath &p_path) {
 	path_node = p_path;
 	_make_dirty();
-	update_gizmo();
+	update_gizmos();
 }
 
 NodePath CSGPolygon3D::get_path_node() const {
@@ -2322,7 +2322,7 @@ void CSGPolygon3D::set_path_interval(float p_interval) {
 	ERR_FAIL_COND_MSG(p_interval < 0.001, "Path interval cannot be smaller than 0.001.");
 	path_interval = p_interval;
 	_make_dirty();
-	update_gizmo();
+	update_gizmos();
 }
 
 float CSGPolygon3D::get_path_interval() const {
@@ -2332,7 +2332,7 @@ float CSGPolygon3D::get_path_interval() const {
 void CSGPolygon3D::set_path_rotation(PathRotation p_rotation) {
 	path_rotation = p_rotation;
 	_make_dirty();
-	update_gizmo();
+	update_gizmos();
 }
 
 CSGPolygon3D::PathRotation CSGPolygon3D::get_path_rotation() const {
@@ -2342,7 +2342,7 @@ CSGPolygon3D::PathRotation CSGPolygon3D::get_path_rotation() const {
 void CSGPolygon3D::set_path_local(bool p_enable) {
 	path_local = p_enable;
 	_make_dirty();
-	update_gizmo();
+	update_gizmos();
 }
 
 bool CSGPolygon3D::is_path_local() const {
@@ -2352,7 +2352,7 @@ bool CSGPolygon3D::is_path_local() const {
 void CSGPolygon3D::set_path_joined(bool p_enable) {
 	path_joined = p_enable;
 	_make_dirty();
-	update_gizmo();
+	update_gizmos();
 }
 
 bool CSGPolygon3D::is_path_joined() const {

+ 2 - 2
modules/navigation/navigation_mesh_editor_plugin.cpp

@@ -65,7 +65,7 @@ void NavigationMeshEditor::_bake_pressed() {
 	NavigationMeshGenerator::get_singleton()->clear(node->get_navigation_mesh());
 	NavigationMeshGenerator::get_singleton()->bake(node->get_navigation_mesh(), node);
 
-	node->update_gizmo();
+	node->update_gizmos();
 }
 
 void NavigationMeshEditor::_clear_pressed() {
@@ -77,7 +77,7 @@ void NavigationMeshEditor::_clear_pressed() {
 	bake_info->set_text("");
 
 	if (node) {
-		node->update_gizmo();
+		node->update_gizmos();
 	}
 }
 

+ 2 - 2
scene/3d/audio_stream_player_3d.cpp

@@ -794,7 +794,7 @@ uint32_t AudioStreamPlayer3D::get_area_mask() const {
 
 void AudioStreamPlayer3D::set_emission_angle_enabled(bool p_enable) {
 	emission_angle_enabled = p_enable;
-	update_gizmo();
+	update_gizmos();
 }
 
 bool AudioStreamPlayer3D::is_emission_angle_enabled() const {
@@ -804,7 +804,7 @@ bool AudioStreamPlayer3D::is_emission_angle_enabled() const {
 void AudioStreamPlayer3D::set_emission_angle(float p_angle) {
 	ERR_FAIL_COND(p_angle < 0 || p_angle > 90);
 	emission_angle = p_angle;
-	update_gizmo();
+	update_gizmos();
 }
 
 float AudioStreamPlayer3D::get_emission_angle() const {

+ 4 - 4
scene/3d/camera_3d.cpp

@@ -164,7 +164,7 @@ void Camera3D::set_perspective(float p_fovy_degrees, float p_z_near, float p_z_f
 	mode = PROJECTION_PERSPECTIVE;
 
 	RenderingServer::get_singleton()->camera_set_perspective(camera, fov, near, far);
-	update_gizmo();
+	update_gizmos();
 	force_change = false;
 }
 
@@ -181,7 +181,7 @@ void Camera3D::set_orthogonal(float p_size, float p_z_near, float p_z_far) {
 	force_change = false;
 
 	RenderingServer::get_singleton()->camera_set_orthogonal(camera, size, near, far);
-	update_gizmo();
+	update_gizmos();
 }
 
 void Camera3D::set_frustum(float p_size, Vector2 p_offset, float p_z_near, float p_z_far) {
@@ -198,7 +198,7 @@ void Camera3D::set_frustum(float p_size, Vector2 p_offset, float p_z_near, float
 	force_change = false;
 
 	RenderingServer::get_singleton()->camera_set_frustum(camera, size, frustum_offset, near, far);
-	update_gizmo();
+	update_gizmos();
 }
 
 void Camera3D::set_projection(Camera3D::Projection p_mode) {
@@ -755,7 +755,7 @@ void ClippedCamera3D::_notification(int p_what) {
 	}
 
 	if (p_what == NOTIFICATION_LOCAL_TRANSFORM_CHANGED) {
-		update_gizmo();
+		update_gizmos();
 	}
 }
 

+ 3 - 3
scene/3d/collision_polygon_3d.cpp

@@ -122,7 +122,7 @@ void CollisionPolygon3D::set_polygon(const Vector<Point2> &p_polygon) {
 		_build_polygon();
 	}
 	update_configuration_warnings();
-	update_gizmo();
+	update_gizmos();
 }
 
 Vector<Point2> CollisionPolygon3D::get_polygon() const {
@@ -136,7 +136,7 @@ AABB CollisionPolygon3D::get_item_rect() const {
 void CollisionPolygon3D::set_depth(real_t p_depth) {
 	depth = p_depth;
 	_build_polygon();
-	update_gizmo();
+	update_gizmos();
 }
 
 real_t CollisionPolygon3D::get_depth() const {
@@ -145,7 +145,7 @@ real_t CollisionPolygon3D::get_depth() const {
 
 void CollisionPolygon3D::set_disabled(bool p_disabled) {
 	disabled = p_disabled;
-	update_gizmo();
+	update_gizmos();
 
 	if (parent) {
 		parent->shape_owner_set_disabled(owner_id, p_disabled);

+ 3 - 3
scene/3d/collision_shape_3d.cpp

@@ -117,7 +117,7 @@ void CollisionShape3D::_notification(int p_what) {
 }
 
 void CollisionShape3D::resource_changed(RES res) {
-	update_gizmo();
+	update_gizmos();
 }
 
 TypedArray<String> CollisionShape3D::get_configuration_warnings() const {
@@ -166,7 +166,7 @@ void CollisionShape3D::set_shape(const Ref<Shape3D> &p_shape) {
 	if (!shape.is_null()) {
 		shape->register_owner(this);
 	}
-	update_gizmo();
+	update_gizmos();
 	if (parent) {
 		parent->shape_owner_clear_shapes(owner_id);
 		if (shape.is_valid()) {
@@ -187,7 +187,7 @@ Ref<Shape3D> CollisionShape3D::get_shape() const {
 
 void CollisionShape3D::set_disabled(bool p_disabled) {
 	disabled = p_disabled;
-	update_gizmo();
+	update_gizmos();
 	if (parent) {
 		parent->shape_owner_set_disabled(owner_id, p_disabled);
 	}

+ 1 - 1
scene/3d/decal.cpp

@@ -33,7 +33,7 @@
 void Decal::set_extents(const Vector3 &p_extents) {
 	extents = p_extents;
 	RS::get_singleton()->decal_set_extents(decal, p_extents);
-	update_gizmo();
+	update_gizmos();
 }
 
 Vector3 Decal::get_extents() const {

+ 1 - 1
scene/3d/gpu_particles_3d.cpp

@@ -99,7 +99,7 @@ void GPUParticles3D::set_randomness_ratio(float p_ratio) {
 void GPUParticles3D::set_visibility_aabb(const AABB &p_aabb) {
 	visibility_aabb = p_aabb;
 	RS::get_singleton()->particles_set_custom_aabb(particles, visibility_aabb);
-	update_gizmo();
+	update_gizmos();
 }
 
 void GPUParticles3D::set_use_local_coordinates(bool p_enable) {

+ 10 - 10
scene/3d/gpu_particles_collision_3d.cpp

@@ -73,7 +73,7 @@ void GPUParticlesCollisionSphere::_bind_methods() {
 void GPUParticlesCollisionSphere::set_radius(float p_radius) {
 	radius = p_radius;
 	RS::get_singleton()->particles_collision_set_sphere_radius(_get_collision(), radius);
-	update_gizmo();
+	update_gizmos();
 }
 
 float GPUParticlesCollisionSphere::get_radius() const {
@@ -103,7 +103,7 @@ void GPUParticlesCollisionBox::_bind_methods() {
 void GPUParticlesCollisionBox::set_extents(const Vector3 &p_extents) {
 	extents = p_extents;
 	RS::get_singleton()->particles_collision_set_box_extents(_get_collision(), extents);
-	update_gizmo();
+	update_gizmos();
 }
 
 Vector3 GPUParticlesCollisionBox::get_extents() const {
@@ -545,7 +545,7 @@ float GPUParticlesCollisionSDF::get_thickness() const {
 void GPUParticlesCollisionSDF::set_extents(const Vector3 &p_extents) {
 	extents = p_extents;
 	RS::get_singleton()->particles_collision_set_box_extents(_get_collision(), extents);
-	update_gizmo();
+	update_gizmos();
 }
 
 Vector3 GPUParticlesCollisionSDF::get_extents() const {
@@ -554,7 +554,7 @@ Vector3 GPUParticlesCollisionSDF::get_extents() const {
 
 void GPUParticlesCollisionSDF::set_resolution(Resolution p_resolution) {
 	resolution = p_resolution;
-	update_gizmo();
+	update_gizmos();
 }
 
 GPUParticlesCollisionSDF::Resolution GPUParticlesCollisionSDF::get_resolution() const {
@@ -680,7 +680,7 @@ float GPUParticlesCollisionHeightField::get_follow_camera_push_ratio() const {
 void GPUParticlesCollisionHeightField::set_extents(const Vector3 &p_extents) {
 	extents = p_extents;
 	RS::get_singleton()->particles_collision_set_box_extents(_get_collision(), extents);
-	update_gizmo();
+	update_gizmos();
 	RS::get_singleton()->particles_collision_height_field_update(_get_collision());
 }
 
@@ -691,7 +691,7 @@ Vector3 GPUParticlesCollisionHeightField::get_extents() const {
 void GPUParticlesCollisionHeightField::set_resolution(Resolution p_resolution) {
 	resolution = p_resolution;
 	RS::get_singleton()->particles_collision_set_height_field_resolution(_get_collision(), RS::ParticlesCollisionHeightfieldResolution(resolution));
-	update_gizmo();
+	update_gizmos();
 	RS::get_singleton()->particles_collision_height_field_update(_get_collision());
 }
 
@@ -761,7 +761,7 @@ float GPUParticlesAttractor3D::get_attenuation() const {
 void GPUParticlesAttractor3D::set_directionality(float p_directionality) {
 	directionality = p_directionality;
 	RS::get_singleton()->particles_collision_set_attractor_directionality(collision, p_directionality);
-	update_gizmo();
+	update_gizmos();
 }
 
 float GPUParticlesAttractor3D::get_directionality() const {
@@ -808,7 +808,7 @@ void GPUParticlesAttractorSphere::_bind_methods() {
 void GPUParticlesAttractorSphere::set_radius(float p_radius) {
 	radius = p_radius;
 	RS::get_singleton()->particles_collision_set_sphere_radius(_get_collision(), radius);
-	update_gizmo();
+	update_gizmos();
 }
 
 float GPUParticlesAttractorSphere::get_radius() const {
@@ -838,7 +838,7 @@ void GPUParticlesAttractorBox::_bind_methods() {
 void GPUParticlesAttractorBox::set_extents(const Vector3 &p_extents) {
 	extents = p_extents;
 	RS::get_singleton()->particles_collision_set_box_extents(_get_collision(), extents);
-	update_gizmo();
+	update_gizmos();
 }
 
 Vector3 GPUParticlesAttractorBox::get_extents() const {
@@ -872,7 +872,7 @@ void GPUParticlesAttractorVectorField::_bind_methods() {
 void GPUParticlesAttractorVectorField::set_extents(const Vector3 &p_extents) {
 	extents = p_extents;
 	RS::get_singleton()->particles_collision_set_box_extents(_get_collision(), extents);
-	update_gizmo();
+	update_gizmos();
 }
 
 Vector3 GPUParticlesAttractorVectorField::get_extents() const {

+ 2 - 2
scene/3d/light_3d.cpp

@@ -45,7 +45,7 @@ void Light3D::set_param(Param p_param, float p_value) {
 	RS::get_singleton()->light_set_param(light, RS::LightParam(p_param), p_value);
 
 	if (p_param == PARAM_SPOT_ANGLE || p_param == PARAM_RANGE) {
-		update_gizmo();
+		update_gizmos();
 
 		if (p_param == PARAM_SPOT_ANGLE) {
 			update_configuration_warnings();
@@ -95,7 +95,7 @@ void Light3D::set_color(const Color &p_color) {
 	color = p_color;
 	RS::get_singleton()->light_set_color(light, p_color);
 	// The gizmo color depends on the light color, so update it.
-	update_gizmo();
+	update_gizmos();
 }
 
 Color Light3D::get_color() const {

+ 1 - 1
scene/3d/lightmap_gi.cpp

@@ -1250,7 +1250,7 @@ void LightmapGI::set_light_data(const Ref<LightmapGIData> &p_data) {
 		}
 	}
 
-	update_gizmo();
+	update_gizmos();
 }
 
 Ref<LightmapGIData> LightmapGI::get_light_data() const {

+ 2 - 2
scene/3d/mesh_instance_3d.cpp

@@ -133,7 +133,7 @@ void MeshInstance3D::set_mesh(const Ref<Mesh> &p_mesh) {
 		set_base(RID());
 	}
 
-	update_gizmo();
+	update_gizmos();
 
 	notify_property_list_changed();
 }
@@ -356,7 +356,7 @@ Ref<Material> MeshInstance3D::get_active_material(int p_surface) const {
 void MeshInstance3D::_mesh_changed() {
 	ERR_FAIL_COND(mesh.is_null());
 	surface_override_materials.resize(mesh->get_surface_count());
-	update_gizmo();
+	update_gizmos();
 }
 
 void MeshInstance3D::create_debug_tangents() {

+ 3 - 3
scene/3d/navigation_region_3d.cpp

@@ -59,7 +59,7 @@ void NavigationRegion3D::set_enabled(bool p_enabled) {
 		}
 	}
 
-	update_gizmo();
+	update_gizmos();
 }
 
 bool NavigationRegion3D::is_enabled() const {
@@ -134,7 +134,7 @@ void NavigationRegion3D::set_navigation_mesh(const Ref<NavigationMesh> &p_navmes
 
 	emit_signal(SNAME("navigation_mesh_changed"));
 
-	update_gizmo();
+	update_gizmos();
 	update_configuration_warnings();
 }
 
@@ -211,7 +211,7 @@ void NavigationRegion3D::_bind_methods() {
 }
 
 void NavigationRegion3D::_navigation_changed() {
-	update_gizmo();
+	update_gizmos();
 	update_configuration_warnings();
 }
 

+ 88 - 53
scene/3d/node_3d.cpp

@@ -76,7 +76,7 @@ Node3DGizmo::Node3DGizmo() {
 
 void Node3D::_notify_dirty() {
 #ifdef TOOLS_ENABLED
-	if ((data.gizmo.is_valid() || data.notify_transform) && !data.ignore_notification && !xform_change.in_list()) {
+	if ((!data.gizmos.is_empty() || data.notify_transform) && !data.ignore_notification && !xform_change.in_list()) {
 #else
 	if (data.notify_transform && !data.ignore_notification && !xform_change.in_list()) {
 
@@ -110,7 +110,7 @@ void Node3D::_propagate_transform_changed(Node3D *p_origin) {
 		E->get()->_propagate_transform_changed(p_origin);
 	}
 #ifdef TOOLS_ENABLED
-	if ((data.gizmo.is_valid() || data.notify_transform) && !data.ignore_notification && !xform_change.in_list()) {
+	if ((!data.gizmos.is_empty() || data.notify_transform) && !data.ignore_notification && !xform_change.in_list()) {
 #else
 	if (data.notify_transform && !data.ignore_notification && !xform_change.in_list()) {
 #endif
@@ -181,15 +181,14 @@ void Node3D::_notification(int p_what) {
 			}
 #ifdef TOOLS_ENABLED
 			if (Engine::get_singleton()->is_editor_hint() && get_tree()->is_node_being_edited(this)) {
-				//get_scene()->call_group(SceneMainLoop::GROUP_CALL_REALTIME,SceneStringNames::get_singleton()->_spatial_editor_group,SceneStringNames::get_singleton()->_request_gizmo,this);
 				get_tree()->call_group_flags(0, SceneStringNames::get_singleton()->_spatial_editor_group, SceneStringNames::get_singleton()->_request_gizmo, this);
-				if (!data.gizmo_disabled) {
-					if (data.gizmo.is_valid()) {
-						data.gizmo->create();
+				if (!data.gizmos_disabled) {
+					for (int i = 0; i < data.gizmos.size(); i++) {
+						data.gizmos.write[i]->create();
 						if (is_visible_in_tree()) {
-							data.gizmo->redraw();
+							data.gizmos.write[i]->redraw();
 						}
-						data.gizmo->transform();
+						data.gizmos.write[i]->transform();
 					}
 				}
 			}
@@ -198,10 +197,7 @@ void Node3D::_notification(int p_what) {
 		} break;
 		case NOTIFICATION_EXIT_WORLD: {
 #ifdef TOOLS_ENABLED
-			if (data.gizmo.is_valid()) {
-				data.gizmo->free();
-				data.gizmo.unref();
-			}
+			clear_gizmos();
 #endif
 
 			if (get_script_instance()) {
@@ -215,8 +211,8 @@ void Node3D::_notification(int p_what) {
 
 		case NOTIFICATION_TRANSFORM_CHANGED: {
 #ifdef TOOLS_ENABLED
-			if (data.gizmo.is_valid()) {
-				data.gizmo->transform();
+			for (int i = 0; i < data.gizmos.size(); i++) {
+				data.gizmos.write[i]->transform();
 			}
 #endif
 		} break;
@@ -368,80 +364,119 @@ Vector3 Node3D::get_scale() const {
 	return data.scale;
 }
 
-void Node3D::update_gizmo() {
+void Node3D::update_gizmos() {
 #ifdef TOOLS_ENABLED
 	if (!is_inside_world()) {
 		return;
 	}
-	if (!data.gizmo.is_valid()) {
-		get_tree()->call_group_flags(SceneTree::GROUP_CALL_REALTIME, SceneStringNames::get_singleton()->_spatial_editor_group, SceneStringNames::get_singleton()->_request_gizmo, this);
+
+	if (data.gizmos.is_empty()) {
+		return;
 	}
-	if (!data.gizmo.is_valid()) {
+	data.gizmos_dirty = true;
+	MessageQueue::get_singleton()->push_callable(callable_mp(this, &Node3D::_update_gizmos));
+#endif
+}
+
+void Node3D::clear_subgizmo_selection() {
+#ifdef TOOLS_ENABLED
+	if (!is_inside_world()) {
 		return;
 	}
-	if (data.gizmo_dirty) {
+
+	if (data.gizmos.is_empty()) {
 		return;
 	}
-	data.gizmo_dirty = true;
-	MessageQueue::get_singleton()->push_call(this, "_update_gizmo");
+
+	if (Engine::get_singleton()->is_editor_hint() && get_tree()->is_node_being_edited(this)) {
+		get_tree()->call_group_flags(0, SceneStringNames::get_singleton()->_spatial_editor_group, SceneStringNames::get_singleton()->_clear_subgizmo_selection, this);
+	}
 #endif
 }
 
-void Node3D::set_gizmo(const Ref<Node3DGizmo> &p_gizmo) {
+void Node3D::add_gizmo(Ref<Node3DGizmo> p_gizmo) {
 #ifdef TOOLS_ENABLED
 
-	if (data.gizmo_disabled) {
+	if (data.gizmos_disabled || p_gizmo.is_null()) {
 		return;
 	}
-	if (data.gizmo.is_valid() && is_inside_world()) {
-		data.gizmo->free();
-	}
-	data.gizmo = p_gizmo;
-	if (data.gizmo.is_valid() && is_inside_world()) {
-		data.gizmo->create();
+	data.gizmos.push_back(p_gizmo);
+
+	if (p_gizmo.is_valid() && is_inside_world()) {
+		p_gizmo->create();
 		if (is_visible_in_tree()) {
-			data.gizmo->redraw();
+			p_gizmo->redraw();
 		}
-		data.gizmo->transform();
+		p_gizmo->transform();
 	}
+#endif
+}
+
+void Node3D::remove_gizmo(Ref<Node3DGizmo> p_gizmo) {
+#ifdef TOOLS_ENABLED
 
+	int idx = data.gizmos.find(p_gizmo);
+	if (idx != -1) {
+		p_gizmo->free();
+		data.gizmos.remove(idx);
+	}
+#endif
+}
+
+void Node3D::clear_gizmos() {
+#ifdef TOOLS_ENABLED
+	for (int i = 0; i < data.gizmos.size(); i++) {
+		data.gizmos.write[i]->free();
+	}
+	data.gizmos.clear();
+#endif
+}
+
+Array Node3D::get_gizmos_bind() const {
+	Array ret;
+
+#ifdef TOOLS_ENABLED
+	for (int i = 0; i < data.gizmos.size(); i++) {
+		ret.push_back(Variant(data.gizmos[i].ptr()));
+	}
 #endif
+
+	return ret;
 }
 
-Ref<Node3DGizmo> Node3D::get_gizmo() const {
+Vector<Ref<Node3DGizmo>> Node3D::get_gizmos() const {
 #ifdef TOOLS_ENABLED
 
-	return data.gizmo;
+	return data.gizmos;
 #else
 
-	return Ref<Node3DGizmo>();
+	return Vector<Ref<Node3DGizmo>>();
 #endif
 }
 
-void Node3D::_update_gizmo() {
+void Node3D::_update_gizmos() {
 #ifdef TOOLS_ENABLED
-	if (!is_inside_world()) {
+	if (data.gizmos_disabled || !is_inside_world() || !data.gizmos_dirty) {
 		return;
 	}
-	data.gizmo_dirty = false;
-	if (data.gizmo.is_valid()) {
+	data.gizmos_dirty = false;
+	for (int i = 0; i < data.gizmos.size(); i++) {
 		if (is_visible_in_tree()) {
-			data.gizmo->redraw();
+			data.gizmos.write[i]->redraw();
 		} else {
-			data.gizmo->clear();
+			data.gizmos.write[i]->clear();
 		}
 	}
 #endif
 }
 
 #ifdef TOOLS_ENABLED
-void Node3D::set_disable_gizmo(bool p_enabled) {
-	data.gizmo_disabled = p_enabled;
-	if (!p_enabled && data.gizmo.is_valid()) {
-		data.gizmo = Ref<Node3DGizmo>();
+void Node3D::set_disable_gizmos(bool p_enabled) {
+	data.gizmos_disabled = p_enabled;
+	if (!p_enabled) {
+		clear_gizmos();
 	}
 }
-
 #endif
 
 void Node3D::set_disable_scale(bool p_enabled) {
@@ -486,8 +521,9 @@ void Node3D::_propagate_visibility_changed() {
 	notification(NOTIFICATION_VISIBILITY_CHANGED);
 	emit_signal(SceneStringNames::get_singleton()->visibility_changed);
 #ifdef TOOLS_ENABLED
-	if (data.gizmo.is_valid()) {
-		_update_gizmo();
+	if (!data.gizmos.is_empty()) {
+		data.gizmos_dirty = true;
+		_update_gizmos();
 	}
 #endif
 
@@ -758,11 +794,11 @@ void Node3D::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_visibility_parent", "path"), &Node3D::set_visibility_parent);
 	ClassDB::bind_method(D_METHOD("get_visibility_parent"), &Node3D::get_visibility_parent);
 
-	ClassDB::bind_method(D_METHOD("_update_gizmo"), &Node3D::_update_gizmo);
-
-	ClassDB::bind_method(D_METHOD("update_gizmo"), &Node3D::update_gizmo);
-	ClassDB::bind_method(D_METHOD("set_gizmo", "gizmo"), &Node3D::set_gizmo);
-	ClassDB::bind_method(D_METHOD("get_gizmo"), &Node3D::get_gizmo);
+	ClassDB::bind_method(D_METHOD("update_gizmos"), &Node3D::update_gizmos);
+	ClassDB::bind_method(D_METHOD("add_gizmo", "gizmo"), &Node3D::add_gizmo);
+	ClassDB::bind_method(D_METHOD("get_gizmos"), &Node3D::get_gizmos_bind);
+	ClassDB::bind_method(D_METHOD("clear_gizmos"), &Node3D::clear_gizmos);
+	ClassDB::bind_method(D_METHOD("clear_subgizmo_selection"), &Node3D::clear_subgizmo_selection);
 
 	ClassDB::bind_method(D_METHOD("set_visible", "visible"), &Node3D::set_visible);
 	ClassDB::bind_method(D_METHOD("is_visible"), &Node3D::is_visible);
@@ -813,7 +849,6 @@ void Node3D::_bind_methods() {
 	ADD_GROUP("Visibility", "");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "visible"), "set_visible", "is_visible");
 	ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "visibility_parent", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "GeometryInstance3D"), "set_visibility_parent", "get_visibility_parent");
-	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "gizmo", PROPERTY_HINT_RESOURCE_TYPE, "Node3DGizmo", PROPERTY_USAGE_NONE), "set_gizmo", "get_gizmo");
 
 	ADD_SIGNAL(MethodInfo("visibility_changed"));
 }

+ 12 - 8
scene/3d/node_3d.h

@@ -90,16 +90,16 @@ class Node3D : public Node {
 		bool disable_scale = false;
 
 #ifdef TOOLS_ENABLED
-		Ref<Node3DGizmo> gizmo;
-		bool gizmo_disabled = false;
-		bool gizmo_dirty = false;
+		Vector<Ref<Node3DGizmo>> gizmos;
+		bool gizmos_disabled = false;
+		bool gizmos_dirty = false;
 #endif
 
 	} data;
 
 	NodePath visibility_parent_path;
 
-	void _update_gizmo();
+	void _update_gizmos();
 	void _notify_dirty();
 	void _propagate_transform_changed(Node3D *p_origin);
 
@@ -154,10 +154,14 @@ public:
 	void set_disable_scale(bool p_enabled);
 	bool is_scale_disabled() const;
 
-	void set_disable_gizmo(bool p_enabled);
-	void update_gizmo();
-	void set_gizmo(const Ref<Node3DGizmo> &p_gizmo);
-	Ref<Node3DGizmo> get_gizmo() const;
+	void set_disable_gizmos(bool p_enabled);
+	void update_gizmos();
+	void clear_subgizmo_selection();
+	Vector<Ref<Node3DGizmo>> get_gizmos() const;
+	Array get_gizmos_bind() const;
+	void add_gizmo(Ref<Node3DGizmo> p_gizmo);
+	void remove_gizmo(Ref<Node3DGizmo> p_gizmo);
+	void clear_gizmos();
 
 	_FORCE_INLINE_ bool is_inside_world() const { return data.inside_world; }
 

+ 2 - 2
scene/3d/occluder_instance_3d.cpp

@@ -173,12 +173,12 @@ void OccluderInstance3D::set_occluder(const Ref<Occluder3D> &p_occluder) {
 		set_base(RID());
 	}
 
-	update_gizmo();
+	update_gizmos();
 	update_configuration_warnings();
 }
 
 void OccluderInstance3D::_occluder_changed() {
-	update_gizmo();
+	update_gizmos();
 	update_configuration_warnings();
 }
 

+ 1 - 1
scene/3d/path_3d.cpp

@@ -38,7 +38,7 @@ void Path3D::_notification(int p_what) {
 
 void Path3D::_curve_changed() {
 	if (is_inside_tree() && Engine::get_singleton()->is_editor_hint()) {
-		update_gizmo();
+		update_gizmos();
 	}
 	if (is_inside_tree()) {
 		emit_signal(SNAME("curve_changed"));

+ 3 - 9
scene/3d/physics_body_3d.cpp

@@ -2181,9 +2181,7 @@ bool PhysicalBone3D::_set(const StringName &p_name, const Variant &p_value) {
 	if (joint_data) {
 		if (joint_data->_set(p_name, p_value, joint)) {
 #ifdef TOOLS_ENABLED
-			if (get_gizmo().is_valid()) {
-				get_gizmo()->redraw();
-			}
+			update_gizmos();
 #endif
 			return true;
 		}
@@ -2371,9 +2369,7 @@ void PhysicalBone3D::_update_joint_offset() {
 	set_ignore_transform_notification(false);
 
 #ifdef TOOLS_ENABLED
-	if (get_gizmo().is_valid()) {
-		get_gizmo()->redraw();
-	}
+	update_gizmos();
 #endif
 }
 
@@ -2540,9 +2536,7 @@ void PhysicalBone3D::set_joint_type(JointType p_joint_type) {
 
 #ifdef TOOLS_ENABLED
 	notify_property_list_changed();
-	if (get_gizmo().is_valid()) {
-		get_gizmo()->redraw();
-	}
+	update_gizmos();
 #endif
 }
 

+ 10 - 10
scene/3d/physics_joint_3d.cpp

@@ -348,7 +348,7 @@ void HingeJoint3D::set_param(Param p_param, real_t p_value) {
 		PhysicsServer3D::get_singleton()->hinge_joint_set_param(get_joint(), PhysicsServer3D::HingeJointParam(p_param), p_value);
 	}
 
-	update_gizmo();
+	update_gizmos();
 }
 
 real_t HingeJoint3D::get_param(Param p_param) const {
@@ -363,7 +363,7 @@ void HingeJoint3D::set_flag(Flag p_flag, bool p_value) {
 		PhysicsServer3D::get_singleton()->hinge_joint_set_flag(get_joint(), PhysicsServer3D::HingeJointFlag(p_flag), p_value);
 	}
 
-	update_gizmo();
+	update_gizmos();
 }
 
 bool HingeJoint3D::get_flag(Flag p_flag) const {
@@ -497,7 +497,7 @@ void SliderJoint3D::set_param(Param p_param, real_t p_value) {
 	if (is_configured()) {
 		PhysicsServer3D::get_singleton()->slider_joint_set_param(get_joint(), PhysicsServer3D::SliderJointParam(p_param), p_value);
 	}
-	update_gizmo();
+	update_gizmos();
 }
 
 real_t SliderJoint3D::get_param(Param p_param) const {
@@ -602,7 +602,7 @@ void ConeTwistJoint3D::set_param(Param p_param, real_t p_value) {
 		PhysicsServer3D::get_singleton()->cone_twist_joint_set_param(get_joint(), PhysicsServer3D::ConeTwistJointParam(p_param), p_value);
 	}
 
-	update_gizmo();
+	update_gizmos();
 }
 
 real_t ConeTwistJoint3D::get_param(Param p_param) const {
@@ -857,7 +857,7 @@ void Generic6DOFJoint3D::set_param_x(Param p_param, real_t p_value) {
 		PhysicsServer3D::get_singleton()->generic_6dof_joint_set_param(get_joint(), Vector3::AXIS_X, PhysicsServer3D::G6DOFJointAxisParam(p_param), p_value);
 	}
 
-	update_gizmo();
+	update_gizmos();
 }
 
 real_t Generic6DOFJoint3D::get_param_x(Param p_param) const {
@@ -871,7 +871,7 @@ void Generic6DOFJoint3D::set_param_y(Param p_param, real_t p_value) {
 	if (is_configured()) {
 		PhysicsServer3D::get_singleton()->generic_6dof_joint_set_param(get_joint(), Vector3::AXIS_Y, PhysicsServer3D::G6DOFJointAxisParam(p_param), p_value);
 	}
-	update_gizmo();
+	update_gizmos();
 }
 
 real_t Generic6DOFJoint3D::get_param_y(Param p_param) const {
@@ -885,7 +885,7 @@ void Generic6DOFJoint3D::set_param_z(Param p_param, real_t p_value) {
 	if (is_configured()) {
 		PhysicsServer3D::get_singleton()->generic_6dof_joint_set_param(get_joint(), Vector3::AXIS_Z, PhysicsServer3D::G6DOFJointAxisParam(p_param), p_value);
 	}
-	update_gizmo();
+	update_gizmos();
 }
 
 real_t Generic6DOFJoint3D::get_param_z(Param p_param) const {
@@ -899,7 +899,7 @@ void Generic6DOFJoint3D::set_flag_x(Flag p_flag, bool p_enabled) {
 	if (is_configured()) {
 		PhysicsServer3D::get_singleton()->generic_6dof_joint_set_flag(get_joint(), Vector3::AXIS_X, PhysicsServer3D::G6DOFJointAxisFlag(p_flag), p_enabled);
 	}
-	update_gizmo();
+	update_gizmos();
 }
 
 bool Generic6DOFJoint3D::get_flag_x(Flag p_flag) const {
@@ -913,7 +913,7 @@ void Generic6DOFJoint3D::set_flag_y(Flag p_flag, bool p_enabled) {
 	if (is_configured()) {
 		PhysicsServer3D::get_singleton()->generic_6dof_joint_set_flag(get_joint(), Vector3::AXIS_Y, PhysicsServer3D::G6DOFJointAxisFlag(p_flag), p_enabled);
 	}
-	update_gizmo();
+	update_gizmos();
 }
 
 bool Generic6DOFJoint3D::get_flag_y(Flag p_flag) const {
@@ -927,7 +927,7 @@ void Generic6DOFJoint3D::set_flag_z(Flag p_flag, bool p_enabled) {
 	if (is_configured()) {
 		PhysicsServer3D::get_singleton()->generic_6dof_joint_set_flag(get_joint(), Vector3::AXIS_Z, PhysicsServer3D::G6DOFJointAxisFlag(p_flag), p_enabled);
 	}
-	update_gizmo();
+	update_gizmos();
 }
 
 bool Generic6DOFJoint3D::get_flag_z(Flag p_flag) const {

+ 3 - 3
scene/3d/ray_cast_3d.cpp

@@ -37,7 +37,7 @@
 
 void RayCast3D::set_target_position(const Vector3 &p_point) {
 	target_position = p_point;
-	update_gizmo();
+	update_gizmos();
 
 	if (Engine::get_singleton()->is_editor_hint()) {
 		if (is_inside_tree()) {
@@ -102,7 +102,7 @@ Vector3 RayCast3D::get_collision_normal() const {
 
 void RayCast3D::set_enabled(bool p_enabled) {
 	enabled = p_enabled;
-	update_gizmo();
+	update_gizmos();
 
 	if (is_inside_tree() && !Engine::get_singleton()->is_editor_hint()) {
 		set_physics_process_internal(p_enabled);
@@ -366,7 +366,7 @@ void RayCast3D::_update_debug_shape_vertices() {
 
 void RayCast3D::set_debug_shape_thickness(const float p_debug_shape_thickness) {
 	debug_shape_thickness = p_debug_shape_thickness;
-	update_gizmo();
+	update_gizmos();
 
 	if (Engine::get_singleton()->is_editor_hint()) {
 		if (is_inside_tree()) {

+ 2 - 2
scene/3d/reflection_probe.cpp

@@ -101,7 +101,7 @@ void ReflectionProbe::set_extents(const Vector3 &p_extents) {
 	RS::get_singleton()->reflection_probe_set_extents(probe, extents);
 	RS::get_singleton()->reflection_probe_set_origin_offset(probe, origin_offset);
 
-	update_gizmo();
+	update_gizmos();
 }
 
 Vector3 ReflectionProbe::get_extents() const {
@@ -119,7 +119,7 @@ void ReflectionProbe::set_origin_offset(const Vector3 &p_extents) {
 	RS::get_singleton()->reflection_probe_set_extents(probe, extents);
 	RS::get_singleton()->reflection_probe_set_origin_offset(probe, origin_offset);
 
-	update_gizmo();
+	update_gizmos();
 }
 
 Vector3 ReflectionProbe::get_origin_offset() const {

+ 1 - 1
scene/3d/skeleton_3d.cpp

@@ -404,7 +404,7 @@ void Skeleton3D::add_bone(const String &p_name) {
 	process_order_dirty = true;
 	version++;
 	_make_dirty();
-	update_gizmo();
+	update_gizmos();
 }
 
 int Skeleton3D::find_bone(const String &p_name) const {

+ 1 - 1
scene/3d/spring_arm_3d.cpp

@@ -84,7 +84,7 @@ real_t SpringArm3D::get_length() const {
 
 void SpringArm3D::set_length(real_t p_length) {
 	if (is_inside_tree() && (Engine::get_singleton()->is_editor_hint() || get_tree()->is_debugging_collisions_hint())) {
-		update_gizmo();
+		update_gizmos();
 	}
 
 	spring_length = p_length;

+ 1 - 1
scene/3d/sprite_3d.cpp

@@ -174,7 +174,7 @@ void SpriteBase3D::_queue_update() {
 	}
 
 	triangle_mesh.unref();
-	update_gizmo();
+	update_gizmos();
 
 	pending_update = true;
 	call_deferred(SceneStringNames::get_singleton()->_im_update);

+ 2 - 2
scene/3d/vehicle_body_3d.cpp

@@ -149,7 +149,7 @@ void VehicleWheel3D::_update(PhysicsDirectBodyState3D *s) {
 
 void VehicleWheel3D::set_radius(real_t p_radius) {
 	m_wheelRadius = p_radius;
-	update_gizmo();
+	update_gizmos();
 }
 
 real_t VehicleWheel3D::get_radius() const {
@@ -158,7 +158,7 @@ real_t VehicleWheel3D::get_radius() const {
 
 void VehicleWheel3D::set_suspension_rest_length(real_t p_length) {
 	m_suspensionRestLength = p_length;
-	update_gizmo();
+	update_gizmos();
 }
 
 real_t VehicleWheel3D::get_suspension_rest_length() const {

+ 1 - 1
scene/3d/visible_on_screen_notifier_3d.cpp

@@ -63,7 +63,7 @@ void VisibleOnScreenNotifier3D::set_aabb(const AABB &p_aabb) {
 
 	RS::get_singleton()->visibility_notifier_set_aabb(get_base(), aabb);
 
-	update_gizmo();
+	update_gizmos();
 }
 
 AABB VisibleOnScreenNotifier3D::get_aabb() const {

+ 2 - 2
scene/3d/voxel_gi.cpp

@@ -265,7 +265,7 @@ Ref<VoxelGIData> VoxelGI::get_probe_data() const {
 void VoxelGI::set_subdiv(Subdiv p_subdiv) {
 	ERR_FAIL_INDEX(p_subdiv, SUBDIV_MAX);
 	subdiv = p_subdiv;
-	update_gizmo();
+	update_gizmos();
 }
 
 VoxelGI::Subdiv VoxelGI::get_subdiv() const {
@@ -274,7 +274,7 @@ VoxelGI::Subdiv VoxelGI::get_subdiv() const {
 
 void VoxelGI::set_extents(const Vector3 &p_extents) {
 	extents = p_extents;
-	update_gizmo();
+	update_gizmos();
 }
 
 Vector3 VoxelGI::get_extents() const {

+ 1 - 0
scene/scene_string_names.cpp

@@ -136,6 +136,7 @@ SceneStringNames::SceneStringNames() {
 
 	_spatial_editor_group = StaticCString::create("_spatial_editor_group");
 	_request_gizmo = StaticCString::create("_request_gizmo");
+	_clear_subgizmo_selection = StaticCString::create("_clear_subgizmo_selection");
 
 	offset = StaticCString::create("offset");
 	unit_offset = StaticCString::create("unit_offset");

+ 1 - 0
scene/scene_string_names.h

@@ -156,6 +156,7 @@ public:
 
 	StringName _spatial_editor_group;
 	StringName _request_gizmo;
+	StringName _clear_subgizmo_selection;
 
 	StringName offset;
 	StringName unit_offset;

Some files were not shown because too many files changed in this diff