Merge pull request #87901 from pohy/feat/use-subgizmos-for-path3d-position-editing
Use subgizmos for Path3D position editing and declutter the UI
This commit is contained in:
commit
031ca87d83
|
@ -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"));
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue