Browse Source

Merge pull request #80278 from KoBeWi/basically_ViewPanner_but_3D_and_without_panning

Add helper for 3D gizmos and unify box
Yuri Sizov 2 years ago
parent
commit
76062213d1

+ 16 - 63
editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.cpp

@@ -34,6 +34,7 @@
 #include "core/math/geometry_3d.h"
 #include "editor/editor_settings.h"
 #include "editor/editor_undo_redo_manager.h"
+#include "editor/plugins/gizmos/gizmo_3d_helper.h"
 #include "editor/plugins/node_3d_editor_plugin.h"
 #include "scene/3d/collision_shape_3d.h"
 #include "scene/resources/box_shape_3d.h"
@@ -47,6 +48,7 @@
 #include "scene/resources/world_boundary_shape_3d.h"
 
 CollisionShape3DGizmoPlugin::CollisionShape3DGizmoPlugin() {
+	helper.instantiate();
 	const Color gizmo_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/shape");
 	create_material("shape_material", gizmo_color);
 	const float gizmo_value = gizmo_color.get_v();
@@ -55,6 +57,9 @@ CollisionShape3DGizmoPlugin::CollisionShape3DGizmoPlugin() {
 	create_handle_material("handles");
 }
 
+CollisionShape3DGizmoPlugin::~CollisionShape3DGizmoPlugin() {
+}
+
 bool CollisionShape3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
 	return Object::cast_to<CollisionShape3D>(p_spatial) != nullptr;
 }
@@ -80,7 +85,7 @@ String CollisionShape3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_g
 	}
 
 	if (Object::cast_to<BoxShape3D>(*s)) {
-		return "Size";
+		return helper->box_get_handle_name(p_id);
 	}
 
 	if (Object::cast_to<CapsuleShape3D>(*s)) {
@@ -135,8 +140,7 @@ Variant CollisionShape3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p
 }
 
 void CollisionShape3DGizmoPlugin::begin_handle_action(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) {
-	initial_transform = p_gizmo->get_node_3d()->get_global_transform();
-	initial_value = get_handle_value(p_gizmo, p_id, p_secondary);
+	helper->initialize_handle_action(get_handle_value(p_gizmo, p_id, p_secondary), p_gizmo->get_node_3d()->get_global_transform());
 }
 
 void CollisionShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) {
@@ -147,13 +151,8 @@ void CollisionShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, i
 		return;
 	}
 
-	Transform3D gt = initial_transform;
-	Transform3D gi = gt.affine_inverse();
-
-	Vector3 ray_from = p_camera->project_ray_origin(p_point);
-	Vector3 ray_dir = p_camera->project_ray_normal(p_point);
-
-	Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 4096) };
+	Vector3 sg[2];
+	helper->get_segment(p_camera, p_point, sg);
 
 	if (Object::cast_to<SphereShape3D>(*s)) {
 		Ref<SphereShape3D> ss = s;
@@ -188,38 +187,12 @@ void CollisionShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, i
 	}
 
 	if (Object::cast_to<BoxShape3D>(*s)) {
-		Vector3 axis;
-		axis[p_id / 2] = 1.0;
 		Ref<BoxShape3D> bs = s;
-		Vector3 ra, rb;
-		int sign = p_id % 2 * -2 + 1;
-		Vector3 initial_size = initial_value;
-
-		Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096 * sign, sg[0], sg[1], ra, rb);
-		if (ra[p_id / 2] == 0) {
-			// Point before half of the shape. Needs to be calculated in opposite direction.
-			Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096 * -sign, sg[0], sg[1], ra, rb);
-		}
-
-		float d = ra[p_id / 2] * sign;
-
-		Vector3 he = bs->get_size();
-		he[p_id / 2] = d * 2;
-		if (Node3DEditor::get_singleton()->is_snap_enabled()) {
-			he[p_id / 2] = Math::snapped(he[p_id / 2], Node3DEditor::get_singleton()->get_translate_snap());
-		}
-
-		if (Input::get_singleton()->is_key_pressed(Key::ALT)) {
-			he[p_id / 2] = MAX(he[p_id / 2], 0.001);
-			bs->set_size(he);
-			cs->set_global_position(initial_transform.get_origin());
-		} else {
-			he[p_id / 2] = MAX(he[p_id / 2], -initial_size[p_id / 2] + 0.002);
-			bs->set_size((initial_size + (he - initial_size) * 0.5).abs());
-			Vector3 pos = initial_transform.affine_inverse().xform(initial_transform.get_origin());
-			pos += (bs->get_size() - initial_size) * 0.5 * sign;
-			cs->set_global_position(initial_transform.xform(pos));
-		}
+		Vector3 size = bs->get_size();
+		Vector3 position;
+		helper->box_set_handle(sg, p_id, size, position);
+		bs->set_size(size);
+		cs->set_global_position(position);
 	}
 
 	if (Object::cast_to<CapsuleShape3D>(*s)) {
@@ -291,20 +264,7 @@ void CollisionShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo
 	}
 
 	if (Object::cast_to<BoxShape3D>(*s)) {
-		Ref<BoxShape3D> ss = s;
-		if (p_cancel) {
-			cs->set_global_position(initial_transform.get_origin());
-			ss->set_size(p_restore);
-			return;
-		}
-
-		EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
-		ur->create_action(TTR("Change Box Shape Size"));
-		ur->add_do_method(ss.ptr(), "set_size", ss->get_size());
-		ur->add_do_method(cs, "set_global_position", cs->get_global_position());
-		ur->add_undo_method(ss.ptr(), "set_size", p_restore);
-		ur->add_undo_method(cs, "set_global_position", initial_transform.get_origin());
-		ur->commit_action();
+		helper->box_commit_handle(TTR("Change Box Shape Size"), p_cancel, cs, s.ptr());
 	}
 
 	if (Object::cast_to<CapsuleShape3D>(*s)) {
@@ -446,14 +406,7 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
 			lines.push_back(b);
 		}
 
-		Vector<Vector3> handles;
-
-		for (int i = 0; i < 3; i++) {
-			Vector3 ax;
-			ax[i] = bs->get_size()[i] / 2;
-			handles.push_back(ax);
-			handles.push_back(-ax);
-		}
+		const Vector<Vector3> handles = helper->box_get_handles(bs->get_size());
 
 		p_gizmo->add_lines(lines, material);
 		p_gizmo->add_collision_segments(lines);

+ 4 - 2
editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.h

@@ -33,11 +33,12 @@
 
 #include "editor/plugins/node_3d_editor_gizmos.h"
 
+class Gizmo3DHelper;
+
 class CollisionShape3DGizmoPlugin : public EditorNode3DGizmoPlugin {
 	GDCLASS(CollisionShape3DGizmoPlugin, EditorNode3DGizmoPlugin);
 
-	Transform3D initial_transform;
-	Variant initial_value;
+	Ref<Gizmo3DHelper> helper;
 
 public:
 	bool has_gizmo(Node3D *p_spatial) override;
@@ -52,6 +53,7 @@ public:
 	void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override;
 
 	CollisionShape3DGizmoPlugin();
+	~CollisionShape3DGizmoPlugin();
 };
 
 #endif // COLLISION_SHAPE_3D_GIZMO_PLUGIN_H

+ 17 - 55
editor/plugins/gizmos/decal_gizmo_plugin.cpp

@@ -32,10 +32,12 @@
 
 #include "editor/editor_settings.h"
 #include "editor/editor_undo_redo_manager.h"
+#include "editor/plugins/gizmos/gizmo_3d_helper.h"
 #include "editor/plugins/node_3d_editor_plugin.h"
 #include "scene/3d/decal.h"
 
 DecalGizmoPlugin::DecalGizmoPlugin() {
+	helper.instantiate();
 	Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/decal", Color(0.6, 0.5, 1.0));
 
 	create_material("decal_material", gizmo_color);
@@ -43,6 +45,9 @@ DecalGizmoPlugin::DecalGizmoPlugin() {
 	create_handle_material("handles");
 }
 
+DecalGizmoPlugin::~DecalGizmoPlugin() {
+}
+
 bool DecalGizmoPlugin::has_gizmo(Node3D *p_spatial) {
 	return Object::cast_to<Decal>(p_spatial) != nullptr;
 }
@@ -56,16 +61,7 @@ int DecalGizmoPlugin::get_priority() const {
 }
 
 String DecalGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const {
-	switch (p_id) {
-		case 0:
-			return "Size X";
-		case 1:
-			return "Size Y";
-		case 2:
-			return "Size Z";
-	}
-
-	return "";
+	return helper->box_get_handle_name(p_id);
 }
 
 Variant DecalGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const {
@@ -73,52 +69,25 @@ Variant DecalGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int
 	return decal->get_size();
 }
 
+void DecalGizmoPlugin::begin_handle_action(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) {
+	helper->initialize_handle_action(get_handle_value(p_gizmo, p_id, p_secondary), p_gizmo->get_node_3d()->get_global_transform());
+}
+
 void DecalGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) {
 	Decal *decal = Object::cast_to<Decal>(p_gizmo->get_node_3d());
-	Transform3D gt = decal->get_global_transform();
-
-	Transform3D gi = gt.affine_inverse();
-
 	Vector3 size = decal->get_size();
 
-	Vector3 ray_from = p_camera->project_ray_origin(p_point);
-	Vector3 ray_dir = p_camera->project_ray_normal(p_point);
-
-	Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 16384) };
-
-	Vector3 axis;
-	axis[p_id] = 1.0;
-
-	Vector3 ra, rb;
-	Geometry3D::get_closest_points_between_segments(Vector3(), axis * 16384, sg[0], sg[1], ra, rb);
-	float d = ra[p_id] * 2;
-	if (Node3DEditor::get_singleton()->is_snap_enabled()) {
-		d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
-	}
-
-	if (d < 0.001) {
-		d = 0.001;
-	}
+	Vector3 sg[2];
+	helper->get_segment(p_camera, p_point, sg);
 
-	size[p_id] = d;
+	Vector3 position;
+	helper->box_set_handle(sg, p_id, size, position);
 	decal->set_size(size);
+	decal->set_global_position(position);
 }
 
 void DecalGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) {
-	Decal *decal = Object::cast_to<Decal>(p_gizmo->get_node_3d());
-
-	Vector3 restore = p_restore;
-
-	if (p_cancel) {
-		decal->set_size(restore);
-		return;
-	}
-
-	EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
-	ur->create_action(TTR("Change Decal Size"));
-	ur->add_do_method(decal, "set_size", decal->get_size());
-	ur->add_undo_method(decal, "set_size", restore);
-	ur->commit_action();
+	helper->box_commit_handle(TTR("Change Decal Size"), p_cancel, p_gizmo->get_node_3d());
 }
 
 void DecalGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
@@ -153,14 +122,7 @@ void DecalGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
 	lines.push_back(Vector3(0, half_size_y, 0));
 	lines.push_back(Vector3(0, half_size_y * 1.2, 0));
 
-	Vector<Vector3> handles;
-
-	for (int i = 0; i < 3; i++) {
-		Vector3 ax;
-		ax[i] = aabb.position[i] + aabb.size[i];
-		handles.push_back(ax);
-	}
-
+	Vector<Vector3> handles = helper->box_get_handles(decal->get_size());
 	Ref<Material> material = get_material("decal_material", p_gizmo);
 
 	p_gizmo->add_lines(lines, material);

+ 6 - 0
editor/plugins/gizmos/decal_gizmo_plugin.h

@@ -33,9 +33,13 @@
 
 #include "editor/plugins/node_3d_editor_gizmos.h"
 
+class Gizmo3DHelper;
+
 class DecalGizmoPlugin : public EditorNode3DGizmoPlugin {
 	GDCLASS(DecalGizmoPlugin, EditorNode3DGizmoPlugin);
 
+	Ref<Gizmo3DHelper> helper;
+
 public:
 	bool has_gizmo(Node3D *p_spatial) override;
 	String get_gizmo_name() const override;
@@ -44,10 +48,12 @@ public:
 
 	String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override;
 	Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override;
+	void begin_handle_action(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) override;
 	void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override;
 	void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override;
 
 	DecalGizmoPlugin();
+	~DecalGizmoPlugin();
 };
 
 #endif // DECAL_GIZMO_PLUGIN_H

+ 131 - 0
editor/plugins/gizmos/gizmo_3d_helper.cpp

@@ -0,0 +1,131 @@
+/**************************************************************************/
+/*  gizmo_3d_helper.cpp                                                   */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#include "gizmo_3d_helper.h"
+
+#include "editor/editor_undo_redo_manager.h"
+#include "editor/plugins/node_3d_editor_plugin.h"
+#include "scene/3d/camera_3d.h"
+
+void Gizmo3DHelper::initialize_handle_action(const Variant &p_initial_value, const Transform3D &p_initial_transform) {
+	initial_value = p_initial_value;
+	initial_transform = p_initial_transform;
+}
+
+void Gizmo3DHelper::get_segment(Camera3D *p_camera, const Point2 &p_point, Vector3 *r_segment) {
+	Transform3D gt = initial_transform;
+	Transform3D gi = gt.affine_inverse();
+
+	Vector3 ray_from = p_camera->project_ray_origin(p_point);
+	Vector3 ray_dir = p_camera->project_ray_normal(p_point);
+
+	r_segment[0] = gi.xform(ray_from);
+	r_segment[1] = gi.xform(ray_from + ray_dir * 4096);
+}
+
+Vector<Vector3> Gizmo3DHelper::box_get_handles(const Vector3 &p_box_size) {
+	Vector<Vector3> handles;
+	for (int i = 0; i < 3; i++) {
+		Vector3 ax;
+		ax[i] = p_box_size[i] / 2;
+		handles.push_back(ax);
+		handles.push_back(-ax);
+	}
+	return handles;
+}
+
+String Gizmo3DHelper::box_get_handle_name(int p_id) const {
+	switch (p_id) {
+		case 0:
+		case 1:
+			return "Size X";
+		case 2:
+		case 3:
+			return "Size Y";
+		case 4:
+		case 5:
+			return "Size Z";
+	}
+	return "";
+}
+
+void Gizmo3DHelper::box_set_handle(const Vector3 p_segment[2], int p_id, Vector3 &r_box_size, Vector3 &r_box_position) {
+	Vector3 axis;
+	axis[p_id / 2] = 1.0;
+	Vector3 ra, rb;
+	int sign = p_id % 2 * -2 + 1;
+	Vector3 initial_size = initial_value;
+
+	Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096 * sign, p_segment[0], p_segment[1], ra, rb);
+	if (ra[p_id / 2] == 0) {
+		// Point before half of the shape. Needs to be calculated in opposite direction.
+		Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096 * -sign, p_segment[0], p_segment[1], ra, rb);
+	}
+
+	float d = ra[p_id / 2] * sign;
+
+	Vector3 he = r_box_size;
+	he[p_id / 2] = d * 2;
+	if (Node3DEditor::get_singleton()->is_snap_enabled()) {
+		he[p_id / 2] = Math::snapped(he[p_id / 2], Node3DEditor::get_singleton()->get_translate_snap());
+	}
+
+	if (Input::get_singleton()->is_key_pressed(Key::ALT)) {
+		he[p_id / 2] = MAX(he[p_id / 2], 0.001);
+		r_box_size = he;
+		r_box_position = initial_transform.get_origin();
+	} else {
+		he[p_id / 2] = MAX(he[p_id / 2], -initial_size[p_id / 2] + 0.002);
+		r_box_size = (initial_size + (he - initial_size) * 0.5).abs();
+		Vector3 pos = initial_transform.affine_inverse().xform(initial_transform.get_origin());
+		pos += (r_box_size - initial_size) * 0.5 * sign;
+		r_box_position = initial_transform.xform(pos);
+	}
+}
+
+void Gizmo3DHelper::box_commit_handle(const String &p_action_name, bool p_cancel, Object *p_position_object, Object *p_size_object, const StringName &p_position_property, const StringName &p_size_property) {
+	if (!p_size_object) {
+		p_size_object = p_position_object;
+	}
+
+	if (p_cancel) {
+		p_size_object->set(p_size_property, initial_value);
+		p_position_object->set(p_position_property, initial_transform.get_origin());
+		return;
+	}
+
+	EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
+	ur->create_action(p_action_name);
+	ur->add_do_property(p_size_object, p_size_property, p_size_object->get(p_size_property));
+	ur->add_do_property(p_position_object, p_position_property, p_position_object->get(p_position_property));
+	ur->add_undo_property(p_size_object, p_size_property, initial_value);
+	ur->add_undo_property(p_position_object, p_position_property, initial_transform.get_origin());
+	ur->commit_action();
+}

+ 55 - 0
editor/plugins/gizmos/gizmo_3d_helper.h

@@ -0,0 +1,55 @@
+/**************************************************************************/
+/*  gizmo_3d_helper.h                                                     */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#ifndef GIZMO_3D_HELPER_H
+#define GIZMO_3D_HELPER_H
+
+#include "core/object/ref_counted.h"
+
+class Camera3D;
+
+class Gizmo3DHelper : public RefCounted {
+	GDCLASS(Gizmo3DHelper, RefCounted);
+
+	int current_handle_id;
+	Variant initial_value;
+	Transform3D initial_transform;
+
+public:
+	void initialize_handle_action(const Variant &p_initial_value, const Transform3D &p_initial_transform);
+	void get_segment(Camera3D *p_camera, const Point2 &p_point, Vector3 *r_segment);
+
+	Vector<Vector3> box_get_handles(const Vector3 &p_box_size);
+	String box_get_handle_name(int p_id) const;
+	void box_set_handle(const Vector3 p_segment[2], int p_id, Vector3 &r_box_size, Vector3 &r_box_position);
+	void box_commit_handle(const String &p_action_name, bool p_cancel, Object *p_position_object, Object *p_size_object = nullptr, const StringName &p_position_property = "global_position", const StringName &p_size_property = "size");
+};
+
+#endif // GIZMO_3D_HELPER_H

+ 20 - 41
editor/plugins/gizmos/gpu_particles_collision_3d_gizmo_plugin.cpp

@@ -32,10 +32,13 @@
 
 #include "editor/editor_settings.h"
 #include "editor/editor_undo_redo_manager.h"
+#include "editor/plugins/gizmos/gizmo_3d_helper.h"
 #include "editor/plugins/node_3d_editor_plugin.h"
 #include "scene/3d/gpu_particles_collision_3d.h"
 
 GPUParticlesCollision3DGizmoPlugin::GPUParticlesCollision3DGizmoPlugin() {
+	helper.instantiate();
+
 	Color gizmo_color_attractor = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/particle_attractor", Color(1, 0.7, 0.5));
 	create_material("shape_material_attractor", gizmo_color_attractor);
 	gizmo_color_attractor.a = 0.15;
@@ -49,6 +52,9 @@ GPUParticlesCollision3DGizmoPlugin::GPUParticlesCollision3DGizmoPlugin() {
 	create_handle_material("handles");
 }
 
+GPUParticlesCollision3DGizmoPlugin::~GPUParticlesCollision3DGizmoPlugin() {
+}
+
 bool GPUParticlesCollision3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
 	return (Object::cast_to<GPUParticlesCollision3D>(p_spatial) != nullptr) || (Object::cast_to<GPUParticlesAttractor3D>(p_spatial) != nullptr);
 }
@@ -69,7 +75,7 @@ String GPUParticlesCollision3DGizmoPlugin::get_handle_name(const EditorNode3DGiz
 	}
 
 	if (Object::cast_to<GPUParticlesCollisionBox3D>(cs) || Object::cast_to<GPUParticlesAttractorBox3D>(cs) || Object::cast_to<GPUParticlesAttractorVectorField3D>(cs) || Object::cast_to<GPUParticlesCollisionSDF3D>(cs) || Object::cast_to<GPUParticlesCollisionHeightField3D>(cs)) {
-		return "Size";
+		return helper->box_get_handle_name(p_id);
 	}
 
 	return "";
@@ -89,16 +95,15 @@ Variant GPUParticlesCollision3DGizmoPlugin::get_handle_value(const EditorNode3DG
 	return Variant();
 }
 
+void GPUParticlesCollision3DGizmoPlugin::begin_handle_action(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) {
+	helper->initialize_handle_action(get_handle_value(p_gizmo, p_id, p_secondary), p_gizmo->get_node_3d()->get_global_transform());
+}
+
 void GPUParticlesCollision3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) {
 	Node3D *sn = p_gizmo->get_node_3d();
 
-	Transform3D gt = sn->get_global_transform();
-	Transform3D gi = gt.affine_inverse();
-
-	Vector3 ray_from = p_camera->project_ray_origin(p_point);
-	Vector3 ray_dir = p_camera->project_ray_normal(p_point);
-
-	Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 4096) };
+	Vector3 sg[2];
+	helper->get_segment(p_camera, p_point, sg);
 
 	if (Object::cast_to<GPUParticlesCollisionSphere3D>(sn) || Object::cast_to<GPUParticlesAttractorSphere3D>(sn)) {
 		Vector3 ra, rb;
@@ -116,22 +121,11 @@ void GPUParticlesCollision3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_g
 	}
 
 	if (Object::cast_to<GPUParticlesCollisionBox3D>(sn) || Object::cast_to<GPUParticlesAttractorBox3D>(sn) || Object::cast_to<GPUParticlesAttractorVectorField3D>(sn) || Object::cast_to<GPUParticlesCollisionSDF3D>(sn) || Object::cast_to<GPUParticlesCollisionHeightField3D>(sn)) {
-		Vector3 axis;
-		axis[p_id] = 1.0;
-		Vector3 ra, rb;
-		Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb);
-		float d = ra[p_id] * 2;
-		if (Node3DEditor::get_singleton()->is_snap_enabled()) {
-			d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
-		}
-
-		if (d < 0.001) {
-			d = 0.001;
-		}
-
-		Vector3 he = sn->call("get_size");
-		he[p_id] = d;
-		sn->call("set_size", he);
+		Vector3 size = sn->call("get_size");
+		Vector3 position;
+		helper->box_set_handle(sg, p_id, size, position);
+		sn->call("set_size", size);
+		sn->set_global_position(position);
 	}
 }
 
@@ -152,16 +146,7 @@ void GPUParticlesCollision3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *
 	}
 
 	if (Object::cast_to<GPUParticlesCollisionBox3D>(sn) || Object::cast_to<GPUParticlesAttractorBox3D>(sn) || Object::cast_to<GPUParticlesAttractorVectorField3D>(sn) || Object::cast_to<GPUParticlesCollisionSDF3D>(sn) || Object::cast_to<GPUParticlesCollisionHeightField3D>(sn)) {
-		if (p_cancel) {
-			sn->call("set_size", p_restore);
-			return;
-		}
-
-		EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
-		ur->create_action(TTR("Change Box Shape Size"));
-		ur->add_do_method(sn, "set_size", sn->call("get_size"));
-		ur->add_undo_method(sn, "set_size", p_restore);
-		ur->commit_action();
+		helper->box_commit_handle("Change Box Shape Size", p_cancel, sn);
 	}
 }
 
@@ -237,13 +222,7 @@ void GPUParticlesCollision3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
 			lines.push_back(b);
 		}
 
-		Vector<Vector3> handles;
-
-		for (int i = 0; i < 3; i++) {
-			Vector3 ax;
-			ax[i] = cs->call("get_size").operator Vector3()[i] / 2;
-			handles.push_back(ax);
-		}
+		Vector<Vector3> handles = helper->box_get_handles(aabb.size);
 
 		p_gizmo->add_lines(lines, material);
 		p_gizmo->add_collision_segments(lines);

+ 6 - 0
editor/plugins/gizmos/gpu_particles_collision_3d_gizmo_plugin.h

@@ -33,9 +33,13 @@
 
 #include "editor/plugins/node_3d_editor_gizmos.h"
 
+class Gizmo3DHelper;
+
 class GPUParticlesCollision3DGizmoPlugin : public EditorNode3DGizmoPlugin {
 	GDCLASS(GPUParticlesCollision3DGizmoPlugin, EditorNode3DGizmoPlugin);
 
+	Ref<Gizmo3DHelper> helper;
+
 public:
 	bool has_gizmo(Node3D *p_spatial) override;
 	String get_gizmo_name() const override;
@@ -44,10 +48,12 @@ public:
 
 	String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override;
 	Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override;
+	void begin_handle_action(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) override;
 	void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override;
 	void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override;
 
 	GPUParticlesCollision3DGizmoPlugin();
+	~GPUParticlesCollision3DGizmoPlugin();
 };
 
 #endif // GPU_PARTICLES_COLLISION_3D_GIZMO_PLUGIN_H

+ 31 - 50
editor/plugins/gizmos/reflection_probe_gizmo_plugin.cpp

@@ -32,10 +32,12 @@
 
 #include "editor/editor_settings.h"
 #include "editor/editor_undo_redo_manager.h"
+#include "editor/plugins/gizmos/gizmo_3d_helper.h"
 #include "editor/plugins/node_3d_editor_plugin.h"
 #include "scene/3d/reflection_probe.h"
 
 ReflectionProbeGizmoPlugin::ReflectionProbeGizmoPlugin() {
+	helper.instantiate();
 	Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/reflection_probe", Color(0.6, 1, 0.5));
 
 	create_material("reflection_probe_material", gizmo_color);
@@ -50,6 +52,9 @@ ReflectionProbeGizmoPlugin::ReflectionProbeGizmoPlugin() {
 	create_handle_material("handles");
 }
 
+ReflectionProbeGizmoPlugin::~ReflectionProbeGizmoPlugin() {
+}
+
 bool ReflectionProbeGizmoPlugin::has_gizmo(Node3D *p_spatial) {
 	return Object::cast_to<ReflectionProbe>(p_spatial) != nullptr;
 }
@@ -63,21 +68,17 @@ int ReflectionProbeGizmoPlugin::get_priority() const {
 }
 
 String ReflectionProbeGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const {
+	if (p_id < 6) {
+		return helper->box_get_handle_name(p_id);
+	}
 	switch (p_id) {
-		case 0:
-			return "Size X";
-		case 1:
-			return "Size Y";
-		case 2:
-			return "Size Z";
-		case 3:
+		case 6:
 			return "Origin X";
-		case 4:
+		case 7:
 			return "Origin Y";
-		case 5:
+		case 8:
 			return "Origin Z";
 	}
-
 	return "";
 }
 
@@ -86,47 +87,30 @@ Variant ReflectionProbeGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_
 	return AABB(probe->get_origin_offset(), probe->get_size());
 }
 
+void ReflectionProbeGizmoPlugin::begin_handle_action(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) {
+	// The initial value is only used for resizing the box, so we only need AABB size.
+	AABB aabb = get_handle_value(p_gizmo, p_id, p_secondary);
+	helper->initialize_handle_action(aabb.size, p_gizmo->get_node_3d()->get_global_transform());
+}
+
 void ReflectionProbeGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) {
 	ReflectionProbe *probe = Object::cast_to<ReflectionProbe>(p_gizmo->get_node_3d());
-	Transform3D gt = probe->get_global_transform();
 
-	Transform3D gi = gt.affine_inverse();
+	Vector3 sg[2];
+	helper->get_segment(p_camera, p_point, sg);
 
-	if (p_id < 3) {
+	if (p_id < 6) {
 		Vector3 size = probe->get_size();
-
-		Vector3 ray_from = p_camera->project_ray_origin(p_point);
-		Vector3 ray_dir = p_camera->project_ray_normal(p_point);
-
-		Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 16384) };
-
-		Vector3 axis;
-		axis[p_id] = 1.0;
-
-		Vector3 ra, rb;
-		Geometry3D::get_closest_points_between_segments(Vector3(), axis * 16384, sg[0], sg[1], ra, rb);
-		float d = ra[p_id] * 2;
-		if (Node3DEditor::get_singleton()->is_snap_enabled()) {
-			d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
-		}
-
-		if (d < 0.001) {
-			d = 0.001;
-		}
-
-		size[p_id] = d;
+		Vector3 position;
+		helper->box_set_handle(sg, p_id, size, position);
 		probe->set_size(size);
+		probe->set_global_position(position);
 	} else {
-		p_id -= 3;
+		p_id -= 6;
 
 		Vector3 origin = probe->get_origin_offset();
 		origin[p_id] = 0;
 
-		Vector3 ray_from = p_camera->project_ray_origin(p_point);
-		Vector3 ray_dir = p_camera->project_ray_normal(p_point);
-
-		Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 16384) };
-
 		Vector3 axis;
 		axis[p_id] = 1.0;
 
@@ -146,6 +130,11 @@ void ReflectionProbeGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, in
 void ReflectionProbeGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) {
 	ReflectionProbe *probe = Object::cast_to<ReflectionProbe>(p_gizmo->get_node_3d());
 
+	if (p_id < 6) {
+		helper->box_commit_handle(TTR("Change Probe Size"), p_cancel, probe);
+		return;
+	}
+
 	AABB restore = p_restore;
 
 	if (p_cancel) {
@@ -155,10 +144,8 @@ void ReflectionProbeGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo,
 	}
 
 	EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
-	ur->create_action(TTR("Change Probe Size"));
-	ur->add_do_method(probe, "set_size", probe->get_size());
+	ur->create_action(TTR("Change Probe Origin Offset"));
 	ur->add_do_method(probe, "set_origin_offset", probe->get_origin_offset());
-	ur->add_undo_method(probe, "set_size", restore.size);
 	ur->add_undo_method(probe, "set_origin_offset", restore.position);
 	ur->commit_action();
 }
@@ -189,13 +176,7 @@ void ReflectionProbeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
 		internal_lines.push_back(ep);
 	}
 
-	Vector<Vector3> handles;
-
-	for (int i = 0; i < 3; i++) {
-		Vector3 ax;
-		ax[i] = aabb.position[i] + aabb.size[i];
-		handles.push_back(ax);
-	}
+	Vector<Vector3> handles = helper->box_get_handles(probe->get_size());
 
 	for (int i = 0; i < 3; i++) {
 		Vector3 orig_handle = probe->get_origin_offset();

+ 6 - 0
editor/plugins/gizmos/reflection_probe_gizmo_plugin.h

@@ -33,9 +33,13 @@
 
 #include "editor/plugins/node_3d_editor_gizmos.h"
 
+class Gizmo3DHelper;
+
 class ReflectionProbeGizmoPlugin : public EditorNode3DGizmoPlugin {
 	GDCLASS(ReflectionProbeGizmoPlugin, EditorNode3DGizmoPlugin);
 
+	Ref<Gizmo3DHelper> helper;
+
 public:
 	bool has_gizmo(Node3D *p_spatial) override;
 	String get_gizmo_name() const override;
@@ -44,10 +48,12 @@ public:
 
 	String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override;
 	Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override;
+	void begin_handle_action(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) override;
 	void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override;
 	void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override;
 
 	ReflectionProbeGizmoPlugin();
+	~ReflectionProbeGizmoPlugin();
 };
 
 #endif // REFLECTION_PROBE_GIZMO_PLUGIN_H

+ 18 - 54
editor/plugins/gizmos/voxel_gi_gizmo_plugin.cpp

@@ -32,10 +32,13 @@
 
 #include "editor/editor_settings.h"
 #include "editor/editor_undo_redo_manager.h"
+#include "editor/plugins/gizmos/gizmo_3d_helper.h"
 #include "editor/plugins/node_3d_editor_plugin.h"
 #include "scene/3d/voxel_gi.h"
 
 VoxelGIGizmoPlugin::VoxelGIGizmoPlugin() {
+	helper.instantiate();
+
 	Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/voxel_gi", Color(0.5, 1, 0.6));
 
 	create_material("voxel_gi_material", gizmo_color);
@@ -51,6 +54,9 @@ VoxelGIGizmoPlugin::VoxelGIGizmoPlugin() {
 	create_handle_material("handles");
 }
 
+VoxelGIGizmoPlugin::~VoxelGIGizmoPlugin() {
+}
+
 bool VoxelGIGizmoPlugin::has_gizmo(Node3D *p_spatial) {
 	return Object::cast_to<VoxelGI>(p_spatial) != nullptr;
 }
@@ -64,16 +70,7 @@ int VoxelGIGizmoPlugin::get_priority() const {
 }
 
 String VoxelGIGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const {
-	switch (p_id) {
-		case 0:
-			return "Size X";
-		case 1:
-			return "Size Y";
-		case 2:
-			return "Size Z";
-	}
-
-	return "";
+	return helper->box_get_handle_name(p_id);
 }
 
 Variant VoxelGIGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const {
@@ -81,52 +78,25 @@ Variant VoxelGIGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, i
 	return probe->get_size();
 }
 
+void VoxelGIGizmoPlugin::begin_handle_action(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) {
+	helper->initialize_handle_action(get_handle_value(p_gizmo, p_id, p_secondary), p_gizmo->get_node_3d()->get_global_transform());
+}
+
 void VoxelGIGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) {
 	VoxelGI *probe = Object::cast_to<VoxelGI>(p_gizmo->get_node_3d());
 
-	Transform3D gt = probe->get_global_transform();
-	Transform3D gi = gt.affine_inverse();
+	Vector3 sg[2];
+	helper->get_segment(p_camera, p_point, sg);
 
 	Vector3 size = probe->get_size();
-
-	Vector3 ray_from = p_camera->project_ray_origin(p_point);
-	Vector3 ray_dir = p_camera->project_ray_normal(p_point);
-
-	Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 16384) };
-
-	Vector3 axis;
-	axis[p_id] = 1.0;
-
-	Vector3 ra, rb;
-	Geometry3D::get_closest_points_between_segments(Vector3(), axis * 16384, sg[0], sg[1], ra, rb);
-	float d = ra[p_id] * 2;
-	if (Node3DEditor::get_singleton()->is_snap_enabled()) {
-		d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
-	}
-
-	if (d < 0.001) {
-		d = 0.001;
-	}
-
-	size[p_id] = d;
+	Vector3 position;
+	helper->box_set_handle(sg, p_id, size, position);
 	probe->set_size(size);
+	probe->set_global_position(position);
 }
 
 void VoxelGIGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) {
-	VoxelGI *probe = Object::cast_to<VoxelGI>(p_gizmo->get_node_3d());
-
-	Vector3 restore = p_restore;
-
-	if (p_cancel) {
-		probe->set_size(restore);
-		return;
-	}
-
-	EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
-	ur->create_action(TTR("Change Probe Size"));
-	ur->add_do_method(probe, "set_size", probe->get_size());
-	ur->add_undo_method(probe, "set_size", restore);
-	ur->commit_action();
+	helper->box_commit_handle(TTR("Change Probe Size"), p_cancel, p_gizmo->get_node_3d());
 }
 
 void VoxelGIGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
@@ -191,13 +161,7 @@ void VoxelGIGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
 
 	p_gizmo->add_lines(lines, material_internal);
 
-	Vector<Vector3> handles;
-
-	for (int i = 0; i < 3; i++) {
-		Vector3 ax;
-		ax[i] = aabb.position[i] + aabb.size[i];
-		handles.push_back(ax);
-	}
+	Vector<Vector3> handles = helper->box_get_handles(probe->get_size());
 
 	if (p_gizmo->is_selected()) {
 		Ref<Material> solid_material = get_material("voxel_gi_solid_material", p_gizmo);

+ 6 - 0
editor/plugins/gizmos/voxel_gi_gizmo_plugin.h

@@ -33,9 +33,13 @@
 
 #include "editor/plugins/node_3d_editor_gizmos.h"
 
+class Gizmo3DHelper;
+
 class VoxelGIGizmoPlugin : public EditorNode3DGizmoPlugin {
 	GDCLASS(VoxelGIGizmoPlugin, EditorNode3DGizmoPlugin);
 
+	Ref<Gizmo3DHelper> helper;
+
 public:
 	bool has_gizmo(Node3D *p_spatial) override;
 	String get_gizmo_name() const override;
@@ -44,10 +48,12 @@ public:
 
 	String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override;
 	Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override;
+	void begin_handle_action(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) override;
 	void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override;
 	void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override;
 
 	VoxelGIGizmoPlugin();
+	~VoxelGIGizmoPlugin();
 };
 
 #endif // VOXEL_GI_GIZMO_PLUGIN_H

+ 20 - 52
modules/csg/editor/csg_gizmos.cpp

@@ -35,12 +35,15 @@
 #include "editor/editor_node.h"
 #include "editor/editor_settings.h"
 #include "editor/editor_undo_redo_manager.h"
+#include "editor/plugins/gizmos/gizmo_3d_helper.h"
 #include "editor/plugins/node_3d_editor_plugin.h"
 #include "scene/3d/camera_3d.h"
 
 ///////////
 
 CSGShape3DGizmoPlugin::CSGShape3DGizmoPlugin() {
+	helper.instantiate();
+
 	Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/csg", Color(0.0, 0.4, 1, 0.15));
 	create_material("shape_union_material", gizmo_color);
 	create_material("shape_union_solid_material", gizmo_color);
@@ -56,6 +59,9 @@ CSGShape3DGizmoPlugin::CSGShape3DGizmoPlugin() {
 	create_handle_material("handles");
 }
 
+CSGShape3DGizmoPlugin::~CSGShape3DGizmoPlugin() {
+}
+
 String CSGShape3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const {
 	CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_node_3d());
 
@@ -64,7 +70,7 @@ String CSGShape3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo,
 	}
 
 	if (Object::cast_to<CSGBox3D>(cs)) {
-		return "Size";
+		return helper->box_get_handle_name(p_id);
 	}
 
 	if (Object::cast_to<CSGCylinder3D>(cs)) {
@@ -104,17 +110,15 @@ Variant CSGShape3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo
 	return Variant();
 }
 
+void CSGShape3DGizmoPlugin::begin_handle_action(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) {
+	helper->initialize_handle_action(get_handle_value(p_gizmo, p_id, p_secondary), p_gizmo->get_node_3d()->get_global_transform());
+}
+
 void CSGShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) {
 	CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_node_3d());
 
-	Transform3D gt = cs->get_global_transform();
-	//gt.orthonormalize();
-	Transform3D gi = gt.affine_inverse();
-
-	Vector3 ray_from = p_camera->project_ray_origin(p_point);
-	Vector3 ray_dir = p_camera->project_ray_normal(p_point);
-
-	Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 16384) };
+	Vector3 sg[2];
+	helper->get_segment(p_camera, p_point, sg);
 
 	if (Object::cast_to<CSGSphere3D>(cs)) {
 		CSGSphere3D *s = Object::cast_to<CSGSphere3D>(cs);
@@ -135,29 +139,11 @@ void CSGShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_i
 
 	if (Object::cast_to<CSGBox3D>(cs)) {
 		CSGBox3D *s = Object::cast_to<CSGBox3D>(cs);
-
-		Vector3 axis;
-		axis[p_id] = 1.0;
-		Vector3 ra, rb;
-		Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb);
-		float d = ra[p_id];
-
-		if (Math::is_nan(d)) {
-			// The handle is perpendicular to the camera.
-			return;
-		}
-
-		if (Node3DEditor::get_singleton()->is_snap_enabled()) {
-			d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
-		}
-
-		if (d < 0.001) {
-			d = 0.001;
-		}
-
-		Vector3 h = s->get_size();
-		h[p_id] = d * 2;
-		s->set_size(h);
+		Vector3 size = s->get_size();
+		Vector3 position;
+		helper->box_set_handle(sg, p_id, size, position);
+		s->set_size(size);
+		s->set_global_position(position);
 	}
 
 	if (Object::cast_to<CSGCylinder3D>(cs)) {
@@ -225,17 +211,7 @@ void CSGShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int
 	}
 
 	if (Object::cast_to<CSGBox3D>(cs)) {
-		CSGBox3D *s = Object::cast_to<CSGBox3D>(cs);
-		if (p_cancel) {
-			s->set_size(p_restore);
-			return;
-		}
-
-		EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
-		ur->create_action(TTR("Change Box Shape Size"));
-		ur->add_do_method(s, "set_size", s->get_size());
-		ur->add_undo_method(s, "set_size", p_restore);
-		ur->commit_action();
+		helper->box_commit_handle(TTR("Change Box Shape Size"), p_cancel, cs);
 	}
 
 	if (Object::cast_to<CSGCylinder3D>(cs)) {
@@ -394,15 +370,7 @@ void CSGShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
 
 	if (Object::cast_to<CSGBox3D>(cs)) {
 		CSGBox3D *s = Object::cast_to<CSGBox3D>(cs);
-
-		Vector<Vector3> handles;
-
-		for (int i = 0; i < 3; i++) {
-			Vector3 h;
-			h[i] = s->get_size()[i] / 2;
-			handles.push_back(h);
-		}
-
+		Vector<Vector3> handles = helper->box_get_handles(s->get_size());
 		p_gizmo->add_handles(handles, handles_material);
 	}
 

+ 6 - 0
modules/csg/editor/csg_gizmos.h

@@ -38,9 +38,13 @@
 #include "editor/editor_plugin.h"
 #include "editor/plugins/node_3d_editor_gizmos.h"
 
+class Gizmo3DHelper;
+
 class CSGShape3DGizmoPlugin : public EditorNode3DGizmoPlugin {
 	GDCLASS(CSGShape3DGizmoPlugin, EditorNode3DGizmoPlugin);
 
+	Ref<Gizmo3DHelper> helper;
+
 public:
 	virtual bool has_gizmo(Node3D *p_spatial) override;
 	virtual String get_gizmo_name() const override;
@@ -50,10 +54,12 @@ public:
 
 	virtual String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override;
 	virtual Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override;
+	void begin_handle_action(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) override;
 	virtual void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override;
 	virtual void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) override;
 
 	CSGShape3DGizmoPlugin();
+	~CSGShape3DGizmoPlugin();
 };
 
 class EditorPluginCSG : public EditorPlugin {