Ver código fonte

Use subgizmos instead of handles for Path3D curve points

pohy 1 ano atrás
pai
commit
6efff2cd06

+ 13 - 0
editor/plugins/node_3d_editor_plugin.cpp

@@ -646,6 +646,18 @@ void Node3DEditorViewport::cancel_transform() {
 			continue;
 		}
 
+		if (se && se->gizmo.is_valid()) {
+			Vector<int> ids;
+			Vector<Transform3D> restore;
+
+			for (const KeyValue<int, Transform3D> &GE : se->subgizmos) {
+				ids.push_back(GE.key);
+				restore.push_back(GE.value);
+			}
+
+			se->gizmo->commit_subgizmos(ids, restore, true);
+		}
+
 		sp->set_global_transform(se->original);
 	}
 
@@ -8071,6 +8083,7 @@ void Node3DEditor::_bind_methods() {
 	ClassDB::bind_method("_refresh_menu_icons", &Node3DEditor::_refresh_menu_icons);
 
 	ClassDB::bind_method("update_all_gizmos", &Node3DEditor::update_all_gizmos);
+	ClassDB::bind_method("update_transform_gizmo", &Node3DEditor::update_transform_gizmo);
 
 	ADD_SIGNAL(MethodInfo("transform_key_request"));
 	ADD_SIGNAL(MethodInfo("item_lock_status_changed"));

+ 1 - 1
editor/plugins/node_3d_editor_plugin.h

@@ -509,7 +509,7 @@ public:
 	RID sbox_instance_xray;
 	RID sbox_instance_xray_offset;
 	Ref<EditorNode3DGizmo> gizmo;
-	HashMap<int, Transform3D> subgizmos; // map ID -> initial transform
+	HashMap<int, Transform3D> subgizmos; // Key: Subgizmo ID, Value: Initial subgizmo transform.
 
 	Node3DEditorSelectedItem() {
 		sp = nullptr;

+ 166 - 43
editor/plugins/path_3d_editor_plugin.cpp

@@ -200,22 +200,6 @@ void Path3DGizmo::commit_handle(int p_id, bool p_secondary, const Variant &p_res
 
 	EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
 
-	// Primary handles: position.
-	if (!p_secondary) {
-		// Special cas for primary handle, the handle id equals control point id.
-		const int idx = p_id;
-		if (p_cancel) {
-			c->set_point_position(idx, p_restore);
-			return;
-		}
-		ur->create_action(TTR("Set Curve Point Position"));
-		ur->add_do_method(c.ptr(), "set_point_position", idx, c->get_point_position(idx));
-		ur->add_undo_method(c.ptr(), "set_point_position", idx, p_restore);
-		ur->commit_action();
-
-		return;
-	}
-
 	// Secondary handles: in, out, tilt.
 	const HandleInfo info = _secondary_handles_info[p_id];
 	const int idx = info.point_idx;
@@ -235,6 +219,7 @@ void Path3DGizmo::commit_handle(int p_id, bool p_secondary, const Variant &p_res
 				ur->add_do_method(c.ptr(), "set_point_in", idx, Path3DEditorPlugin::singleton->mirror_length_enabled() ? -c->get_point_out(idx) : (-c->get_point_out(idx).normalized() * orig_in_length));
 				ur->add_undo_method(c.ptr(), "set_point_in", idx, Path3DEditorPlugin::singleton->mirror_length_enabled() ? -static_cast<Vector3>(p_restore) : (-static_cast<Vector3>(p_restore).normalized() * orig_in_length));
 			}
+
 			ur->commit_action();
 			break;
 		}
@@ -252,6 +237,7 @@ void Path3DGizmo::commit_handle(int p_id, bool p_secondary, const Variant &p_res
 				ur->add_do_method(c.ptr(), "set_point_out", idx, Path3DEditorPlugin::singleton->mirror_length_enabled() ? -c->get_point_in(idx) : (-c->get_point_in(idx).normalized() * orig_out_length));
 				ur->add_undo_method(c.ptr(), "set_point_out", idx, Path3DEditorPlugin::singleton->mirror_length_enabled() ? -static_cast<Vector3>(p_restore) : (-static_cast<Vector3>(p_restore).normalized() * orig_out_length));
 			}
+
 			ur->commit_action();
 			break;
 		}
@@ -263,6 +249,7 @@ void Path3DGizmo::commit_handle(int p_id, bool p_secondary, const Variant &p_res
 			ur->create_action(TTR("Set Curve Point Tilt"));
 			ur->add_do_method(c.ptr(), "set_point_tilt", idx, c->get_point_tilt(idx));
 			ur->add_undo_method(c.ptr(), "set_point_tilt", idx, p_restore);
+
 			ur->commit_action();
 			break;
 		}
@@ -275,7 +262,7 @@ void Path3DGizmo::redraw() {
 	Ref<StandardMaterial3D> path_material = gizmo_plugin->get_material("path_material", this);
 	Ref<StandardMaterial3D> path_thin_material = gizmo_plugin->get_material("path_thin_material", this);
 	Ref<StandardMaterial3D> path_tilt_material = gizmo_plugin->get_material("path_tilt_material", this);
-	Ref<StandardMaterial3D> handles_material = gizmo_plugin->get_material("handles");
+	Ref<StandardMaterial3D> path_tilt_muted_material = gizmo_plugin->get_material("path_tilt_muted_material", this);
 	Ref<StandardMaterial3D> sec_handles_material = gizmo_plugin->get_material("sec_handles");
 
 	Ref<Curve3D> c = path->get_curve();
@@ -353,48 +340,57 @@ void Path3DGizmo::redraw() {
 	if (Path3DEditorPlugin::singleton->get_edited_path() == path) {
 		PackedVector3Array handle_lines;
 		PackedVector3Array tilt_handle_lines;
-		PackedVector3Array primary_handle_points;
 		PackedVector3Array secondary_handle_points;
 		PackedInt32Array collected_secondary_handle_ids; // Avoid shadowing member on Node3DEditorGizmo.
 
 		_secondary_handles_info.resize(c->get_point_count() * 3);
 
 		for (int idx = 0; idx < c->get_point_count(); idx++) {
-			// Collect primary-handles.
 			const Vector3 pos = c->get_point_position(idx);
-			primary_handle_points.append(pos);
+			bool is_current_point_selected = is_subgizmo_selected(idx);
+			bool is_previous_point_selected = is_subgizmo_selected(idx - 1);
+			bool is_following_point_selected = is_subgizmo_selected(idx + 1);
 
 			HandleInfo info;
 			info.point_idx = idx;
 
 			// Collect in-handles except for the first point.
-			if (idx > 0) {
-				info.type = HandleType::HANDLE_TYPE_IN;
-				const int handle_idx = idx * 3 + 0;
-				collected_secondary_handle_ids.append(handle_idx);
-				_secondary_handles_info.write[handle_idx] = info;
-
+			if (idx > 0 && (is_current_point_selected || is_previous_point_selected)) {
 				const Vector3 in = c->get_point_in(idx);
-				secondary_handle_points.append(pos + in);
-				handle_lines.append(pos);
-				handle_lines.append(pos + in);
+
+				// Display in-handles only when they are "initialized".
+				if (in.length_squared() > 0) {
+					info.type = HandleType::HANDLE_TYPE_IN;
+					const int handle_idx = idx * 3 + 0;
+					collected_secondary_handle_ids.append(handle_idx);
+					_secondary_handles_info.write[handle_idx] = info;
+
+					secondary_handle_points.append(pos + in);
+					handle_lines.append(pos);
+					handle_lines.append(pos + in);
+				}
 			}
 
 			// Collect out-handles except for the last point.
-			if (idx < c->get_point_count() - 1) {
-				info.type = HandleType::HANDLE_TYPE_OUT;
-				const int handle_idx = idx * 3 + 1;
-				collected_secondary_handle_ids.append(handle_idx);
-				_secondary_handles_info.write[handle_idx] = info;
-
+			if (idx < c->get_point_count() - 1 && (is_current_point_selected || is_following_point_selected)) {
 				const Vector3 out = c->get_point_out(idx);
-				secondary_handle_points.append(pos + out);
-				handle_lines.append(pos);
-				handle_lines.append(pos + out);
+
+				// Display out-handles only when they are "initialized".
+				if (out.length_squared() > 0) {
+					info.type = HandleType::HANDLE_TYPE_OUT;
+					const int handle_idx = idx * 3 + 1;
+					collected_secondary_handle_ids.append(handle_idx);
+					_secondary_handles_info.write[handle_idx] = info;
+
+					secondary_handle_points.append(pos + out);
+					handle_lines.append(pos);
+					handle_lines.append(pos + out);
+				}
 			}
 
 			// Collect tilt-handles.
-			{
+			if (is_current_point_selected || is_previous_point_selected || is_following_point_selected) {
+				// Tilt handle.
 				{
 					info.type = HandleType::HANDLE_TYPE_TILT;
 					const int handle_idx = idx * 3 + 2;
@@ -423,7 +419,7 @@ void Path3DGizmo::redraw() {
 						const Vector3 edge = sin(a) * side + cos(a) * up;
 						disk.append(pos + edge * disk_size);
 					}
-					add_vertices(disk, path_tilt_material, Mesh::PRIMITIVE_LINE_STRIP);
+					add_vertices(disk, is_current_point_selected ? path_tilt_material : path_tilt_muted_material, Mesh::PRIMITIVE_LINE_STRIP);
 				}
 			}
 		}
@@ -436,21 +432,27 @@ void Path3DGizmo::redraw() {
 			add_lines(tilt_handle_lines, path_tilt_material);
 		}
 
-		if (primary_handle_points.size()) {
-			add_handles(primary_handle_points, handles_material);
-		}
 		if (secondary_handle_points.size()) {
 			add_handles(secondary_handle_points, sec_handles_material, collected_secondary_handle_ids, false, true);
 		}
+		// Draw the gizmo plugin manually, because handles are registered. In which case, the caller code skips drawing the gizmo plugin.
+		gizmo_plugin->redraw(this);
 	}
 }
 
+void Path3DGizmo::_update_transform_gizmo() {
+	Node3DEditor::get_singleton()->update_transform_gizmo();
+}
+
 Path3DGizmo::Path3DGizmo(Path3D *p_path, float p_disk_size) {
 	path = p_path;
 	disk_size = p_disk_size;
 	set_node_3d(p_path);
 	orig_in_length = 0;
 	orig_out_length = 0;
+
+	// Connecting to a signal once, rather than plaguing the implementation with calls to `Node3DEditor::update_transform_gizmo`.
+	path->connect("curve_changed", callable_mp(this, &Path3DGizmo::_update_transform_gizmo));
 }
 
 EditorPlugin::AfterGUIInput Path3DEditorPlugin::forward_3d_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) {
@@ -819,10 +821,130 @@ Ref<EditorNode3DGizmo> Path3DGizmoPlugin::create_gizmo(Node3D *p_spatial) {
 	return ref;
 }
 
+bool Path3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+	return Object::cast_to<Path3D>(p_spatial) != nullptr;
+}
+
 String Path3DGizmoPlugin::get_gizmo_name() const {
 	return "Path3D";
 }
 
+void Path3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+	Path3D *path = Object::cast_to<Path3D>(p_gizmo->get_node_3d());
+	ERR_FAIL_NULL(path);
+
+	Ref<Curve3D> curve = path->get_curve();
+
+	Ref<StandardMaterial3D> handle_material = get_material("handles", p_gizmo);
+	PackedVector3Array handles;
+
+	for (int idx = 0; idx < curve->get_point_count(); ++idx) {
+		// Collect handles.
+		const Vector3 pos = curve->get_point_position(idx);
+
+		handles.append(pos);
+	}
+
+	if (handles.size()) {
+		p_gizmo->add_vertices(handles, handle_material, Mesh::PRIMITIVE_POINTS);
+	}
+}
+
+int Path3DGizmoPlugin::subgizmos_intersect_ray(const EditorNode3DGizmo *p_gizmo, Camera3D *p_camera, const Vector2 &p_point) const {
+	Path3D *path = Object::cast_to<Path3D>(p_gizmo->get_node_3d());
+	ERR_FAIL_NULL_V(path, -1);
+	Ref<Curve3D> curve = path->get_curve();
+	ERR_FAIL_COND_V(curve.is_null(), -1);
+
+	for (int idx = 0; idx < curve->get_point_count(); ++idx) {
+		Vector3 pos = path->get_global_transform().xform(curve->get_point_position(idx));
+		if (p_camera->unproject_position(pos).distance_to(p_point) < 20) {
+			return idx;
+		}
+	}
+	return -1;
+}
+
+Vector<int> Path3DGizmoPlugin::subgizmos_intersect_frustum(const EditorNode3DGizmo *p_gizmo, const Camera3D *p_camera, const Vector<Plane> &p_frustum) const {
+	Vector<int> contained_points;
+
+	Path3D *path = Object::cast_to<Path3D>(p_gizmo->get_node_3d());
+	ERR_FAIL_NULL_V(path, contained_points);
+	Ref<Curve3D> curve = path->get_curve();
+	ERR_FAIL_COND_V(curve.is_null(), contained_points);
+
+	for (int idx = 0; idx < curve->get_point_count(); ++idx) {
+		Vector3 pos = path->get_global_transform().xform(curve->get_point_position(idx));
+		bool is_contained_in_frustum = true;
+		for (int i = 0; i < p_frustum.size(); ++i) {
+			if (p_frustum[i].distance_to(pos) > 0) {
+				is_contained_in_frustum = false;
+				break;
+			}
+		}
+
+		if (is_contained_in_frustum) {
+			contained_points.push_back(idx);
+		}
+	}
+
+	return contained_points;
+}
+
+Transform3D Path3DGizmoPlugin::get_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+	Path3D *path = Object::cast_to<Path3D>(p_gizmo->get_node_3d());
+	ERR_FAIL_NULL_V(path, Transform3D());
+	Ref<Curve3D> curve = path->get_curve();
+	ERR_FAIL_COND_V(curve.is_null(), Transform3D());
+	ERR_FAIL_INDEX_V(p_id, curve->get_point_count(), Transform3D());
+
+	Basis basis = transformation_locked_basis.has(p_id) ? transformation_locked_basis[p_id] : curve->get_point_baked_posture(p_id, true);
+	Vector3 pos = curve->get_point_position(p_id);
+
+	Transform3D t = Transform3D(basis, pos);
+	return t;
+}
+
+void Path3DGizmoPlugin::set_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id, Transform3D p_transform) {
+	Path3D *path = Object::cast_to<Path3D>(p_gizmo->get_node_3d());
+	ERR_FAIL_NULL(path);
+	Ref<Curve3D> curve = path->get_curve();
+	ERR_FAIL_COND(curve.is_null());
+	ERR_FAIL_INDEX(p_id, curve->get_point_count());
+
+	if (!transformation_locked_basis.has(p_id)) {
+		transformation_locked_basis[p_id] = Basis(curve->get_point_baked_posture(p_id, true));
+	}
+	curve->set_point_position(p_id, p_transform.origin);
+}
+
+void Path3DGizmoPlugin::commit_subgizmos(const EditorNode3DGizmo *p_gizmo, const Vector<int> &p_ids, const Vector<Transform3D> &p_restore, bool p_cancel) {
+	Path3D *path = Object::cast_to<Path3D>(p_gizmo->get_node_3d());
+	ERR_FAIL_NULL(path);
+	Ref<Curve3D> curve = path->get_curve();
+	ERR_FAIL_COND(curve.is_null());
+
+	transformation_locked_basis.clear();
+
+	if (p_cancel) {
+		for (int i = 0; i < p_ids.size(); ++i) {
+			curve->set_point_position(p_ids[i], p_restore[i].origin);
+		}
+		return;
+	}
+
+	EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
+
+	undo_redo->create_action(TTR("Set Curve Point Position"));
+
+	for (int i = 0; i < p_ids.size(); ++i) {
+		const int idx = p_ids[i];
+		undo_redo->add_do_method(curve.ptr(), "set_point_position", idx, curve->get_point_position(idx));
+		undo_redo->add_undo_method(curve.ptr(), "set_point_position", idx, p_restore[i].origin);
+	}
+	undo_redo->commit_action();
+}
+
 int Path3DGizmoPlugin::get_priority() const {
 	return -1;
 }
@@ -835,6 +957,7 @@ Path3DGizmoPlugin::Path3DGizmoPlugin(float p_disk_size) {
 	create_material("path_material", path_color);
 	create_material("path_thin_material", Color(0.6, 0.6, 0.6));
 	create_material("path_tilt_material", path_tilt_color);
+	create_material("path_tilt_muted_material", path_tilt_color * 0.7);
 	create_handle_material("handles", false, EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("EditorPathSmoothHandle"), EditorStringName(EditorIcons)));
 	create_handle_material("sec_handles", false, EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("EditorCurveHandle"), EditorStringName(EditorIcons)));
 }

+ 16 - 0
editor/plugins/path_3d_editor_plugin.h

@@ -63,6 +63,8 @@ class Path3DGizmo : public EditorNode3DGizmo {
 	// Cache information of secondary handles.
 	Vector<HandleInfo> _secondary_handles_info;
 
+	void _update_transform_gizmo();
+
 public:
 	virtual String get_handle_name(int p_id, bool p_secondary) const override;
 	virtual Variant get_handle_value(int p_id, bool p_secondary) const override;
@@ -78,11 +80,25 @@ class Path3DGizmoPlugin : public EditorNode3DGizmoPlugin {
 
 	float disk_size = 0.8;
 
+	// Locking basis is meant to ensure a predictable behavior during translation of the curve points in "local space transform mode".
+	// Without the locking, the gizmo/point, in "local space transform mode", wouldn't follow a straight path and would curve and twitch in an unpredictable way.
+	HashMap<int, Basis> transformation_locked_basis;
+
 protected:
 	Ref<EditorNode3DGizmo> create_gizmo(Node3D *p_spatial) override;
 
 public:
+	virtual bool has_gizmo(Node3D *p_spatial) override;
 	String get_gizmo_name() const override;
+
+	virtual void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+	virtual int subgizmos_intersect_ray(const EditorNode3DGizmo *p_gizmo, Camera3D *p_camera, const Vector2 &p_point) const override;
+	virtual Vector<int> subgizmos_intersect_frustum(const EditorNode3DGizmo *p_gizmo, const Camera3D *p_camera, const Vector<Plane> &p_frustum) const override;
+	virtual Transform3D get_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+	virtual void set_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id, Transform3D p_transform) override;
+	virtual void commit_subgizmos(const EditorNode3DGizmo *p_gizmo, const Vector<int> &p_ids, const Vector<Transform3D> &p_restore, bool p_cancel = false) override;
+
 	int get_priority() const override;
 	Path3DGizmoPlugin(float p_disk_size);
 };