18
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-14 03:07:59 +01:00

feat(editor): track gizmo turns, lengthens and edits the control vectors, joined tracks keep the tangent

W turns the track (about its middle or the selected end) and a selection of tracks, the joined tracks follow.
E lengthens the track at its free end: a straight stays straight, an arc keeps its radius, cant and grade.
The gizmo on a control point moves, turns or lengthens the vector; the control point of the track joined
there turns with it, at a switch the vector only slides along the switch. Straight neighbours dragged along
keep their tangents and become straight again when nothing bent them.
This commit is contained in:
maj00r
2026-10-11 12:59:55 +02:00
parent 2beae15390
commit 89678ead3e
8 changed files with 782 additions and 99 deletions

View File

@@ -638,6 +638,36 @@ class editor_mode : public application_mode, private editor_track::observer
glm::dvec3 m_track_pivot{0.0}; glm::dvec3 m_track_pivot{0.0};
glm::mat4 m_track_gizmo{1.0f}; glm::mat4 m_track_gizmo{1.0f};
bool m_track_gizmo_using{false}; bool m_track_gizmo_using{false};
struct track_gizmo_drag
{
gizmo_operation operation{gizmo_operation::translate};
bool control{false};
double angle{0.0};
int path{-1};
bool atend{true};
std::size_t pieces{1};
std::vector<segment_data> paths;
editor_track::point_ref point;
glm::dvec3 origin{0.0};
glm::dvec3 axis{0.0};
double grabbed{0.0};
double length{0.0};
double stretch{0.0};
std::vector<std::pair<TTrack *, editor_track::point_ref>> partners;
glm::dvec3 fixed{0.0};
std::vector<std::pair<TTrack *, std::vector<int>>> held;
};
track_gizmo_drag m_track_gizmo_drag;
gizmo_operation track_gizmo_operation(TTrack const &Track, bool const Pointmode) const;
glm::mat4 track_gizmo_frame(TTrack const &Track, bool const Pointmode, gizmo_operation const Operation) const;
void track_gizmo_hold();
void track_gizmo_settle();
void track_gizmo_stretch(TTrack &Track, double const Length);
void track_gizmo_control(TTrack &Track, glm::dvec3 Control);
void track_gizmo_begin(TTrack &Track, bool const Pointmode, gizmo_operation const Operation);
void track_gizmo_turn(TTrack &Track, double const Angle);
void track_gizmo_length(TTrack &Track, double Length);
void track_gizmo_end(TTrack &Track);
bool m_track_dirty{false}; bool m_track_dirty{false};
std::chrono::steady_clock::time_point m_track_last_commit; std::chrono::steady_clock::time_point m_track_last_commit;
editor_track::state m_track_field_before; editor_track::state m_track_field_before;
@@ -1204,6 +1234,11 @@ class editor_mode : public application_mode, private editor_track::observer
glm::dvec3 m_track_set_pivot{0.0}; glm::dvec3 m_track_set_pivot{0.0};
std::vector<std::pair<TTrack *, std::vector<editor_track::joint>>> m_track_set_joints; std::vector<std::pair<TTrack *, std::vector<editor_track::joint>>> m_track_set_joints;
bool in_track_set(TTrack const *Track) const; bool in_track_set(TTrack const *Track) const;
bool track_set_begin(bool const Rotate);
void track_set_turn(double const Angle);
void track_set_move(glm::dvec3 const &Offset);
void track_set_end();
glm::dvec3 track_set_centre() const;
void track_set_toggle(TTrack &Track); void track_set_toggle(TTrack &Track);
void track_set_prune(); void track_set_prune();
void start_track_box(); void start_track_box();

View File

@@ -1127,6 +1127,138 @@ void editor_mode::selftest_step()
} }
WriteLog(format("SELFTEST joints: %d issues around the points, %zu in all", around, m_joints.issues.size())); WriteLog(format("SELFTEST joints: %d issues around the points, %zu in all", around, m_joints.issues.size()));
} }
else if (command == "gizmo")
{
std::string action;
words >> action;
auto *track{selected_track()};
if (track == nullptr)
{
WriteLog("SELFTEST gizmo: nothing selected");
continue;
}
if (action == "set")
{
m_track_set.clear();
for (std::string name; words >> name;)
if (auto *each{simulation::Paths.find(name)}; each != nullptr)
m_track_set.push_back(each);
m_track_set_pivot = track_set_centre();
WriteLog(format("SELFTEST gizmo set: %zu tracks, centre %.3f %.3f", m_track_set.size(), m_track_set_pivot.x, m_track_set_pivot.z));
}
else if (action == "setturn")
{
double degrees{0.0};
words >> degrees;
if (track_set_begin(true))
{
track_set_turn(glm::radians(degrees));
track_set_end();
}
}
else if (action == "op")
{
std::string operation;
words >> operation;
choose_work_area(work_area::tracks);
m_gizmo_op = operation == "rotate" ? gizmo_operation::rotate : operation == "scale" ? gizmo_operation::scale : gizmo_operation::translate;
int path{-1};
std::string kind;
words >> path >> kind;
m_track_point = {};
if (path >= 0)
m_track_point = {path, kind == "c1" ? editor_track::point_kind::control1 : kind == "c2" ? editor_track::point_kind::control2 : kind == "end" ? editor_track::point_kind::end : editor_track::point_kind::start};
}
else if (action == "begin")
{
std::string operation, kind;
int path{-1};
words >> operation >> path >> kind;
m_track_point = {};
if (path >= 0)
m_track_point = {path, kind == "c1" ? editor_track::point_kind::control1 : kind == "c2" ? editor_track::point_kind::control2 : kind == "end" ? editor_track::point_kind::end : editor_track::point_kind::start};
auto const wanted{operation == "rotate" ? gizmo_operation::rotate : operation == "scale" ? gizmo_operation::scale : gizmo_operation::translate};
auto const previous{m_gizmo_op};
m_gizmo_op = wanted;
auto const chosen{track_gizmo_operation(*track, m_track_point.valid())};
m_gizmo_op = previous;
track_gizmo_begin(*track, m_track_point.valid(), chosen);
WriteLog(format("SELFTEST gizmo begin %s: %s, %zu tracks dragged, %zu joints, %zu partners%s", operation.c_str(), chosen == gizmo_operation::rotate ? "rotate" : chosen == gizmo_operation::scale ? "scale" : "translate", m_track_drag.size(), m_track_joints.size(),
m_track_gizmo_drag.partners.size(), m_track_gizmo_drag.fixed != glm::dvec3{} ? ", fixed direction" : ""));
}
else if (action == "turn")
{
double degrees{0.0};
words >> degrees;
track_gizmo_turn(*track, glm::radians(degrees));
}
else if (action == "length")
{
double length{0.0};
words >> length;
track_gizmo_length(*track, length);
WriteLog(format("SELFTEST gizmo length %.3f of %.3f, %zu pieces", m_track_gizmo_drag.stretch, m_track_gizmo_drag.length, m_track_gizmo_drag.pieces));
}
else if (action == "control")
{
glm::dvec3 control{0.0};
words >> control.x >> control.y >> control.z;
track_gizmo_control(*track, control);
}
else if (action == "offset")
{
glm::dvec3 offset{0.0};
words >> offset.x >> offset.y >> offset.z;
if (m_track_point.valid())
{
auto const position{editor_track::point_position(*track, m_track_point) + offset};
editor_track::move_point(*track, m_track_point, position);
for (auto const &connection : m_track_drag_points)
editor_track::move_point(*connection.first, connection.second, position);
}
else
{
editor_track::translate(*track, offset);
editor_track::follow(*track, m_track_joints);
}
m_track_dirty = true;
}
else if (action == "end")
track_gizmo_end(*track);
else if (action == "show")
{
std::vector<TTrack *> shown{track};
for (auto *neighbour : editor_track::neighbours(*track))
shown.push_back(neighbour);
WriteLog(format("SELFTEST gizmo %s prev %s next %s", track->name().c_str(), track->trPrev != nullptr ? track->trPrev->name().c_str() : "-", track->trNext != nullptr ? track->trNext->name().c_str() : "-"));
for (auto *each : shown)
for (int i = 0; i < static_cast<int>(each->m_paths.size()); ++i)
{
auto const &path{each->m_paths[i]};
auto const &a{path.points[segment_data::point::start]};
auto const &b{path.points[segment_data::point::end]};
auto const &c1{path.points[segment_data::point::control1]};
auto const &c2{path.points[segment_data::point::control2]};
WriteLog(format("SELFTEST gizmo path %s[%d] %.3f %.3f %.3f -> %.3f %.3f %.3f, c1 %.3f %.3f %.3f, c2 %.3f %.3f %.3f, L %.3f, R %.1f, rolls %.2f %.2f", each->name().c_str(), i, a.x, a.y, a.z, b.x, b.y, b.z, c1.x, c1.y, c1.z, c2.x, c2.y, c2.z,
geometry::bezier{path}.plan_length(), path.radius, path.rolls[0], path.rolls[1]));
}
for (int i = 0; i < static_cast<int>(track->m_paths.size()); ++i)
for (auto const kind : {editor_track::point_kind::start, editor_track::point_kind::end})
{
geometry::bezier const own{track->m_paths[i]};
bool const start{kind == editor_track::point_kind::start};
auto const out{start ? -own.first(0.0) : own.first(1.0)};
for (auto const &partner : editor_track::partners(*track, {i, kind}))
{
geometry::bezier const theirs{partner.first->m_paths[partner.second.path]};
auto const in{partner.second.kind == editor_track::point_kind::start ? theirs.first(0.0) : -theirs.first(1.0)};
auto const angle{glm::degrees(std::acos(std::clamp(glm::dot(glm::normalize(out), glm::normalize(in)), -1.0, 1.0)))};
auto const gap{glm::distance(editor_track::point_position(*track, {i, kind}), editor_track::point_position(*partner.first, partner.second))};
WriteLog(format("SELFTEST gizmo joint %s[%d].%s - %s[%d]: kink %.4f deg, gap %.4f m", track->name().c_str(), i, start ? "start" : "end", partner.first->name().c_str(), partner.second.path, angle, gap));
}
}
}
}
else if (command == "deletetrack") else if (command == "deletetrack")
{ {
delete_selected_track(); delete_selected_track();

View File

@@ -262,6 +262,137 @@ void compound_from(editor_track::curve const &Curve, alignment::vertex &Vertex)
Vertex.arcs.push_back({std::round(Curve.arcs[i].radius), std::round(Curve.arcs[i].transition), Curve.arcs[i].turn}); Vertex.arcs.push_back({std::round(Curve.arcs[i].radius), std::round(Curve.arcs[i].transition), Curve.arcs[i].turn});
} }
std::vector<segment_data> stretched(segment_data const &Path, bool const Atend, double const Length)
{
auto const path{Atend ? Path : turned_around(Path)};
bezier const curve{path};
auto const &start{path.points[segment_data::point::start]};
auto const &end{path.points[segment_data::point::end]};
auto const first{plan_of(curve.first(0.0))};
auto const last{plan_of(curve.first(1.0))};
if (glm::length(first) < 1e-9 || glm::length(last) < 1e-9)
return {};
glm::dvec2 const direction{glm::normalize(first)};
auto const grade{grade_of(curve.first(0.0))};
auto const original{curve.plan_length()};
auto const turn{signed_angle(direction, glm::normalize(last))};
auto const lateral{cross(direction, plan_of(end - start))};
std::vector<segment_data> pieces;
float radius{0.0f};
if (std::abs(turn) < 1e-4 && std::abs(lateral) < 0.01)
pieces = straight_piece(start, direction, grade, Length);
else
{
auto const arcradius{original / std::abs(turn)};
int const side{turn > 0.0 ? 1 : -1};
auto const arc{arc_pieces(start, direction, grade, arcradius, std::abs(turn), side)};
if (std::abs(turn) > 1e-4 && plan_distance(arc.back().points[segment_data::point::end], end) < 0.05 && plan_distance(arc.front().points[segment_data::point::start], start) < 1e-6)
{
pieces = arc_pieces(start, direction, grade, arcradius, std::min(Length / arcradius, glm::radians(330.0)), side);
radius = path.radius;
}
else
{
auto const scale{Length / std::max(original, 1e-6)};
segment_data piece{path};
piece.points[segment_data::point::end] = start + (end - start) * scale;
piece.points[segment_data::point::control1] *= scale;
piece.points[segment_data::point::control2] *= scale;
piece.radius *= static_cast<float>(scale);
return {Atend ? piece : turned_around(piece)};
}
}
double chainage{0.0};
for (auto &piece : pieces)
{
auto const piecelength{bezier{piece}.plan_length()};
auto const from{static_cast<float>(std::clamp(chainage / original, 0.0, 1.0))};
auto const to{static_cast<float>(std::clamp((chainage + piecelength) / original, 0.0, 1.0))};
piece.rolls = {glm::mix(path.rolls[0], path.rolls[1], from), glm::mix(path.rolls[0], path.rolls[1], to)};
piece.radius = radius;
chainage += piecelength;
}
if (false == Atend)
{
std::reverse(pieces.begin(), pieces.end());
for (auto &piece : pieces)
piece = turned_around(piece);
}
return pieces;
}
bool stretch_end(TTrack const &Track, editor_track::point_ref const &Point, int &Path, bool &Atend)
{
if (Track.eType != tt_Normal || false == editor_track::is_supported(Track) || Track.m_paths.empty())
return false;
auto const last{static_cast<int>(Track.m_paths.size()) - 1};
if (Point.valid())
{
if (false == editor_track::is_end(Point.kind) || Point.path > last)
return false;
Path = Point.path;
Atend = Point.kind == editor_track::point_kind::end;
auto const position{editor_track::point_position(Track, Point)};
for (int i = 0; i <= last; ++i)
if (i != Path && (plan_distance(Track.m_paths[i].points[segment_data::point::start], position) < geometry::same_point || plan_distance(Track.m_paths[i].points[segment_data::point::end], position) < geometry::same_point))
return false;
return true;
}
bool const startfree{false == editor_track::is_connected(Track, {0, editor_track::point_kind::start})};
bool const endfree{false == editor_track::is_connected(Track, {last, editor_track::point_kind::end})};
Atend = endfree || false == startfree;
Path = Atend ? last : 0;
return true;
}
std::pair<glm::dvec3, glm::dvec3> stretch_axis(segment_data const &Path, bool const Atend)
{
bezier const curve{Path};
auto const tangent{Atend ? curve.first(1.0) : -curve.first(0.0)};
return {Path.points[Atend ? segment_data::point::end : segment_data::point::start], glm::length(tangent) > 1e-9 ? glm::normalize(tangent) : glm::dvec3{}};
}
bool mouse_along(glm::dvec3 const &Origin, glm::dvec3 const &Axis, double &Along)
{
screen_projection const projection;
glm::dvec3 origin, direction;
projection.ray(ImGui::GetIO().MousePos, origin, direction);
auto const b{glm::dot(Axis, direction)};
auto const denominator{1.0 - b * b};
if (denominator < 1e-6)
return false;
auto const w{Origin - origin};
Along = (b * glm::dot(direction, w) - glm::dot(Axis, w)) / denominator;
return true;
}
glm::mat4 gizmo_along(glm::dvec3 const &Position, glm::dvec3 const &Axis)
{
auto gizmo{glm::translate(glm::mat4(1.0f), glm::vec3(Position))};
if (glm::length(Axis) < 1e-9)
return gizmo;
auto const z{glm::normalize(Axis)};
auto x{glm::cross(glm::dvec3{0.0, 1.0, 0.0}, z)};
x = glm::length(x) > 1e-6 ? glm::normalize(x) : glm::dvec3{1.0, 0.0, 0.0};
auto const y{glm::cross(z, x)};
gizmo[0] = glm::vec4(glm::vec3(x), 0.0f);
gizmo[1] = glm::vec4(glm::vec3(y), 0.0f);
gizmo[2] = glm::vec4(glm::vec3(z), 0.0f);
return gizmo;
}
editor_track::point_ref stretch_remap(editor_track::point_ref Point, int const Path, std::size_t const Pieces)
{
if (false == Point.valid() || Point.path < Path)
return Point;
auto const extra{static_cast<int>(Pieces) - 1};
if (Point.path > Path)
Point.path += extra;
else if (Point.kind == editor_track::point_kind::end || Point.kind == editor_track::point_kind::control2)
Point.path += extra;
return Point;
}
} }
TTrack *editor_mode::selected_track() const TTrack *editor_mode::selected_track() const
@@ -395,6 +526,214 @@ void editor_mode::draw_track_overlay() const
} }
} }
editor_mode::gizmo_operation editor_mode::track_gizmo_operation(TTrack const &Track, bool const Pointmode) const
{
if (m_gizmo_op == gizmo_operation::translate || (Pointmode && false == editor_track::is_end(m_track_point.kind)))
return m_gizmo_op;
if (m_gizmo_op == gizmo_operation::rotate)
return gizmo_operation::rotate;
int path{0};
bool atend{true};
return stretch_end(Track, Pointmode ? m_track_point : editor_track::point_ref{}, path, atend) ? gizmo_operation::scale : gizmo_operation::translate;
}
glm::mat4 editor_mode::track_gizmo_frame(TTrack const &Track, bool const Pointmode, gizmo_operation const Operation) const
{
auto anchor{Pointmode ? editor_track::point_position(Track, m_track_point) : editor_track::pivot(Track)};
glm::dvec3 axis{0.0};
if (Operation == gizmo_operation::scale)
{
int path{0};
bool atend{true};
if (Pointmode && false == editor_track::is_end(m_track_point.kind))
axis = anchor - editor_track::point_position(Track, editor_track::end_of(m_track_point));
else if (stretch_end(Track, Pointmode ? m_track_point : editor_track::point_ref{}, path, atend))
std::tie(anchor, axis) = stretch_axis(Track.m_paths[path], atend);
}
return gizmo_along(anchor - glm::dvec3{GfxRenderer->Camera_Position()}, axis);
}
void editor_mode::track_gizmo_hold()
{
auto &drag{m_track_gizmo_drag};
drag.held.clear();
for (auto *track : m_track_drag)
{
if (drag.operation == gizmo_operation::scale && false == drag.control && track == selected_track())
continue;
auto held{editor_track::hold_controls(*track)};
if (false == held.empty())
drag.held.emplace_back(track, std::move(held));
}
}
void editor_mode::track_gizmo_settle()
{
for (auto &entry : m_track_gizmo_drag.held)
editor_track::settle_controls(*entry.first, entry.second);
m_track_gizmo_drag.held.clear();
}
void editor_mode::track_gizmo_stretch(TTrack &Track, double const Length)
{
auto &drag{m_track_gizmo_drag};
auto const pieces{stretched(drag.paths[drag.path], drag.atend, Length)};
if (pieces.empty())
return;
auto paths{drag.paths};
paths.erase(paths.begin() + drag.path);
paths.insert(paths.begin() + drag.path, pieces.begin(), pieces.end());
Track.m_paths = std::move(paths);
drag.pieces = pieces.size();
drag.stretch = Length;
if (drag.point.valid())
m_track_point = stretch_remap(drag.point, drag.path, drag.pieces);
auto joints{m_track_joints};
for (auto &joint : joints)
joint.own = stretch_remap(joint.own, drag.path, drag.pieces);
editor_track::follow(Track, joints);
m_track_dirty = true;
}
void editor_mode::track_gizmo_control(TTrack &Track, glm::dvec3 Control)
{
auto const &drag{m_track_gizmo_drag};
if (drag.fixed != glm::dvec3{})
Control = drag.fixed * std::max(0.5, glm::dot(Control, drag.fixed));
if (glm::length(Control) < 0.5)
Control = glm::length(Control) > 1e-9 ? glm::normalize(Control) * 0.5 : drag.axis * 0.5;
editor_track::move_point(Track, drag.point, editor_track::point_position(Track, editor_track::end_of(drag.point)) + Control);
editor_track::align_controls(Track, drag.point, drag.partners);
m_track_dirty = true;
}
void editor_mode::track_gizmo_begin(TTrack &Track, bool const Pointmode, gizmo_operation const Operation)
{
bool const controlmode{Pointmode && false == editor_track::is_end(m_track_point.kind)};
auto &drag{m_track_gizmo_drag};
m_track_gizmo_using = true;
m_track_pivot = editor_track::pivot(Track);
m_track_drag = {&Track};
m_track_drag_points.clear();
m_track_joints.clear();
m_track_snap = {};
drag = {};
drag.operation = Operation;
drag.control = controlmode;
drag.point = Pointmode ? m_track_point : editor_track::point_ref{};
bool const whole{false == Pointmode || (false == controlmode && Operation != gizmo_operation::translate)};
if (whole && m_track_drag_connected)
{
m_track_joints = editor_track::joints(Track);
for (auto const &joint : m_track_joints)
if (std::find(m_track_drag.begin(), m_track_drag.end(), joint.other) == m_track_drag.end())
m_track_drag.emplace_back(joint.other);
}
if (Pointmode && false == controlmode && Operation == gizmo_operation::translate && m_track_drag_connected)
{
for (auto const &connection : editor_track::connected_points(Track, editor_track::point_position(Track, m_track_point)))
{
std::string reason;
if (false == editor_track::can_edit_geometry(*connection.first, reason))
continue;
m_track_drag_points.emplace_back(connection);
if (std::find(m_track_drag.begin(), m_track_drag.end(), connection.first) == m_track_drag.end())
m_track_drag.emplace_back(connection.first);
}
}
if (controlmode)
{
for (auto const &partner : editor_track::partners(Track, editor_track::end_of(m_track_point)))
{
std::string reason;
auto const &path{partner.first->m_paths[partner.second.path]};
if (partner.first->eType != tt_Normal || false == editor_track::can_edit_geometry(*partner.first, reason))
{
bool const start{partner.second.kind == editor_track::point_kind::start};
auto const theirs{path.points[start ? segment_data::point::control1 : segment_data::point::control2]};
auto const chord{path.points[start ? segment_data::point::end : segment_data::point::start] - path.points[start ? segment_data::point::start : segment_data::point::end]};
auto const along{theirs != glm::dvec3{} ? theirs : chord};
if (glm::length(along) > 1e-9)
drag.fixed = -glm::normalize(along);
continue;
}
if (false == m_track_drag_connected && partner.first != &Track)
continue;
drag.partners.push_back(partner);
if (std::find(m_track_drag.begin(), m_track_drag.end(), partner.first) == m_track_drag.end())
m_track_drag.emplace_back(partner.first);
}
}
std::vector<std::pair<TTrack *, editor_track::state>> states;
for (auto *dragged : m_track_drag)
states.emplace_back(dragged, editor_track::capture(*dragged));
push_track_snapshot(std::move(states));
track_gizmo_hold();
if (controlmode)
{
drag.origin = editor_track::point_position(Track, m_track_point);
auto const control{drag.origin - editor_track::point_position(Track, editor_track::end_of(m_track_point))};
drag.length = glm::length(control);
drag.axis = drag.length > 1e-9 ? control / drag.length : glm::dvec3{0.0, 0.0, 1.0};
}
else if (Operation == gizmo_operation::scale)
{
stretch_end(Track, drag.point, drag.path, drag.atend);
drag.paths = Track.m_paths;
std::tie(drag.origin, drag.axis) = stretch_axis(Track.m_paths[drag.path], drag.atend);
drag.length = bezier{Track.m_paths[drag.path]}.plan_length();
}
drag.stretch = drag.length;
}
void editor_mode::track_gizmo_turn(TTrack &Track, double const Angle)
{
auto &drag{m_track_gizmo_drag};
auto const step{std::remainder(Angle - drag.angle, 2.0 * glm::pi<double>())};
if (std::abs(step) < 1e-9 || (drag.control && drag.fixed != glm::dvec3{}))
return;
drag.angle += step;
if (drag.control)
{
auto const control{editor_track::point_position(Track, drag.point) - editor_track::point_position(Track, editor_track::end_of(drag.point))};
auto const plan{turned(plan_of(control), -step)};
track_gizmo_control(Track, {plan.x, control.y, plan.y});
return;
}
editor_track::rotate_y(Track, drag.point.valid() ? editor_track::point_position(Track, drag.point) : m_track_pivot, step);
editor_track::follow(Track, m_track_joints);
m_track_dirty = true;
}
void editor_mode::track_gizmo_length(TTrack &Track, double Length)
{
auto &drag{m_track_gizmo_drag};
Length = std::max(drag.control ? 0.5 : 1.0, Length);
if (std::abs(Length - drag.stretch) < 1e-6)
return;
if (drag.control)
{
drag.stretch = Length;
track_gizmo_control(Track, drag.axis * Length);
}
else if (drag.path >= 0)
track_gizmo_stretch(Track, Length);
}
void editor_mode::track_gizmo_end(TTrack &Track)
{
if (m_track_snap.track != nullptr && m_track_gizmo_drag.operation == gizmo_operation::translate && m_track_point.valid())
editor_track::snap_point(Track, m_track_point, m_track_snap, m_track_align_tangent);
track_gizmo_settle();
m_track_dirty = true;
m_track_snap = {};
commit_track_drag(true);
m_track_gizmo_using = false;
m_track_drag.clear();
m_track_drag_points.clear();
m_track_joints.clear();
}
void editor_mode::render_track_gizmo() void editor_mode::render_track_gizmo()
{ {
auto *track = selected_track(); auto *track = selected_track();
@@ -405,54 +744,49 @@ void editor_mode::render_track_gizmo()
return; return;
} }
bool const pointmode = ui()->mode() == nodebank_panel::TRACK && m_track_point.valid() && m_track_point.path < static_cast<int>(track->m_paths.size()); bool const pointmode = ui()->mode() == nodebank_panel::TRACK && m_track_point.valid() && m_track_point.path < static_cast<int>(track->m_paths.size());
bool const controlmode = pointmode && false == editor_track::is_end(m_track_point.kind);
gizmo_frame const frame; gizmo_frame const frame;
auto &drag{m_track_gizmo_drag};
auto const operation{m_track_gizmo_using ? drag.operation : track_gizmo_operation(*track, pointmode)};
if (false == m_track_gizmo_using) if (false == m_track_gizmo_using)
{ m_track_gizmo = track_gizmo_frame(*track, pointmode, operation);
auto const anchor = pointmode ? editor_track::point_position(*track, m_track_point) : editor_track::pivot(*track);
m_track_gizmo = glm::translate(glm::mat4(1.0f), glm::vec3(anchor - frame.camera));
}
glm::mat4 delta(1.0f); glm::mat4 delta(1.0f);
frame.manipulate(ImGuizmo::TRANSLATE, m_track_gizmo, m_gizmo_snap, &delta); auto const gizmo{operation == gizmo_operation::rotate ? ImGuizmo::ROTATE_Y : operation == gizmo_operation::scale ? ImGuizmo::SCALE_Z : ImGuizmo::TRANSLATE};
ImGuizmo::AllowAxisFlip(operation != gizmo_operation::scale);
frame.manipulate(gizmo, m_track_gizmo, operation == gizmo_operation::rotate ? 5.0f : operation == gizmo_operation::scale ? 0.0f : m_gizmo_snap, &delta, operation == gizmo_operation::scale ? ImGuizmo::LOCAL : ImGuizmo::WORLD);
ImGuizmo::AllowAxisFlip(true);
if (ImGuizmo::IsUsing()) if (ImGuizmo::IsUsing())
{ {
if (false == m_track_gizmo_using) if (false == m_track_gizmo_using)
{ {
m_track_gizmo_using = true; track_gizmo_begin(*track, pointmode, operation);
m_track_pivot = editor_track::pivot(*track); if (operation == gizmo_operation::scale && false == mouse_along(drag.origin, drag.axis, drag.grabbed))
m_track_drag = {track}; drag.grabbed = 0.0;
m_track_drag_points.clear();
m_track_joints.clear();
m_track_snap = {};
if (false == pointmode && m_track_drag_connected)
{
m_track_joints = editor_track::joints(*track);
for (auto const &joint : m_track_joints)
if (std::find(m_track_drag.begin(), m_track_drag.end(), joint.other) == m_track_drag.end())
m_track_drag.emplace_back(joint.other);
}
if (pointmode && editor_track::is_end(m_track_point.kind) && m_track_drag_connected)
{
for (auto const &connection : editor_track::connected_points(*track, editor_track::point_position(*track, m_track_point)))
{
std::string neighbourreason;
if (false == editor_track::can_edit_geometry(*connection.first, neighbourreason))
continue;
m_track_drag_points.emplace_back(connection);
if (std::find(m_track_drag.begin(), m_track_drag.end(), connection.first) == m_track_drag.end())
m_track_drag.emplace_back(connection.first);
}
}
std::vector<std::pair<TTrack *, editor_track::state>> states;
for (auto *dragged : m_track_drag)
states.emplace_back(dragged, editor_track::capture(*dragged));
push_track_snapshot(std::move(states));
} }
if (pointmode) if (operation == gizmo_operation::rotate)
track_gizmo_turn(*track, std::atan2(static_cast<double>(m_track_gizmo[2][0]), static_cast<double>(m_track_gizmo[0][0])));
else if (operation == gizmo_operation::scale)
{
double along{0.0};
if (mouse_along(drag.origin, drag.axis, along))
{
auto length{drag.length + along - drag.grabbed};
if (Global.ctrlState && m_gizmo_snap > 0.0f)
length = std::round(length / m_gizmo_snap) * m_gizmo_snap;
track_gizmo_length(*track, length);
}
}
else if (controlmode)
{
glm::dvec3 const position = frame.camera + glm::dvec3(m_track_gizmo[3]);
track_gizmo_control(*track, position - editor_track::point_position(*track, editor_track::end_of(m_track_point)));
}
else if (pointmode)
{ {
glm::dvec3 const position = frame.camera + glm::dvec3(m_track_gizmo[3]); glm::dvec3 const position = frame.camera + glm::dvec3(m_track_gizmo[3]);
editor_track::move_point(*track, m_track_point, position); editor_track::move_point(*track, m_track_point, position);
@@ -480,19 +814,7 @@ void editor_mode::render_track_gizmo()
commit_track_drag(false); commit_track_drag(false);
} }
else if (m_track_gizmo_using) else if (m_track_gizmo_using)
{ track_gizmo_end(*track);
if (m_track_snap.track != nullptr && pointmode)
{
editor_track::snap_point(*track, m_track_point, m_track_snap, m_track_align_tangent);
m_track_dirty = true;
}
m_track_snap = {};
commit_track_drag(true);
m_track_gizmo_using = false;
m_track_drag.clear();
m_track_drag_points.clear();
m_track_joints.clear();
}
} }
void editor_mode::commit_track_drag(bool const Force) void editor_mode::commit_track_drag(bool const Force)
@@ -3605,12 +3927,24 @@ std::vector<editor_mode::key_hint> editor_mode::track_key_hints(bool const All)
hints = {{"Drag", "moves the point, level"}, {"Over a free end", "the end joins it"}, {"Release", "done"}, {"Esc", "done"}}; hints = {{"Drag", "moves the point, level"}, {"Over a free end", "the end joins it"}, {"Release", "done"}, {"Esc", "done"}};
else if (m_track_point.valid()) else if (m_track_point.valid())
{ {
hints = {{"Drag a point", "moves it, an end dropped on a free end joins it"}, {"Gizmo", "moves the point along the axes"}, {"Esc", "releases the point"}}; char const *action{"moves the point along the axes"};
if (false == editor_track::is_end(m_track_point.kind))
action = m_gizmo_op == gizmo_operation::rotate ? "turns the control vector, the joined track keeps the tangent" : m_gizmo_op == gizmo_operation::scale ? "length of the control vector" : "moves the control point, the joined track keeps the tangent";
else if (m_gizmo_op == gizmo_operation::rotate)
action = "turns the track about the point, the joined tracks follow";
else if (m_gizmo_op == gizmo_operation::scale && selected != nullptr && track_gizmo_operation(*selected, true) == gizmo_operation::scale)
action = "lengthens the track at this end, the curve keeps its radius";
hints = {{"Drag a point", "moves it, an end dropped on a free end joins it"}, {"Gizmo", action}, {"Q W E", "gizmo: move, turn, lengthen"}, {"Esc", "releases the point"}};
gizmo = true; gizmo = true;
} }
else else
{ {
hints = {{"Drag a point", "moves it, an end dropped on a free end joins it"}, {"Gizmo", "moves the whole track"}, {"Shift+LMB", "adds a track to the selection, drag: a box"}, {"L", "new track, also from a free end"}, {"Esc", "deselects"}, {"Del", "deletes the track"}}; char const *action{"moves the whole track"};
if (m_gizmo_op == gizmo_operation::rotate)
action = "turns the track, the joined tracks follow";
else if (m_gizmo_op == gizmo_operation::scale && selected != nullptr && track_gizmo_operation(*selected, false) == gizmo_operation::scale)
action = "lengthens the track at its free end, the curve keeps its radius";
hints = {{"Drag a point", "moves it, an end dropped on a free end joins it"}, {"Gizmo", action}, {"Q W E", "gizmo: move, turn, lengthen"}, {"Shift+LMB", "adds a track to the selection, drag: a box"}, {"L", "new track, also from a free end"}, {"Esc", "deselects"}, {"Del", "deletes the track"}};
gizmo = true; gizmo = true;
} }
if (auto const *meaning{typed_meaning()}; meaning != nullptr) if (auto const *meaning{typed_meaning()}; meaning != nullptr)
@@ -3736,9 +4070,31 @@ std::string editor_mode::track_readout() const
return format("L %.3f m (%+.3f) azimuth %.4f deg", plan_distance(state.preview_start, state.preview_end), plan_distance(state.preview_start, state.preview_end) - grabbed.length, return format("L %.3f m (%+.3f) azimuth %.4f deg", plan_distance(state.preview_start, state.preview_end), plan_distance(state.preview_start, state.preview_end) - grabbed.length,
azimuth(state.preview_start, state.preview_end)); azimuth(state.preview_start, state.preview_end));
} }
if (m_track_set_using && m_track_gizmo_drag.operation == gizmo_operation::rotate)
return format(STR_C("turned %+.2f deg"), glm::degrees(m_track_gizmo_drag.angle));
auto const *track{selected_track()}; auto const *track{selected_track()};
if (m_track_gizmo_using && track != nullptr) if (m_track_gizmo_using && track != nullptr)
{ {
auto const &drag{m_track_gizmo_drag};
if (drag.control)
{
auto const control{editor_track::point_position(*track, m_track_point) - editor_track::point_position(*track, editor_track::end_of(m_track_point))};
auto text{format(STR_C("control %.2f m azimuth %.2f deg"), glm::length(control), azimuth(glm::dvec3{0.0}, control))};
if (drag.operation == gizmo_operation::rotate)
text += format(STR_C(" turned %+.2f deg"), glm::degrees(drag.angle));
if (drag.fixed != glm::dvec3{})
text += "\n" + STR("along the switch");
return text;
}
if (drag.operation == gizmo_operation::scale && drag.path >= 0)
{
auto text{format("L %.3f m (%+.3f)", drag.stretch, drag.stretch - drag.length)};
if (drag.path < static_cast<int>(drag.paths.size()) && std::abs(drag.paths[drag.path].radius) > 0.5f)
text += format(" R %.1f m", std::abs(drag.paths[drag.path].radius));
return text;
}
if (drag.operation == gizmo_operation::rotate)
return format(STR_C("turned %+.2f deg"), glm::degrees(drag.angle));
if (m_track_point.valid() && m_track_point.path < static_cast<int>(track->m_paths.size())) if (m_track_point.valid() && m_track_point.path < static_cast<int>(track->m_paths.size()))
{ {
auto const &path{track->m_paths[m_track_point.path]}; auto const &path{track->m_paths[m_track_point.path]};

View File

@@ -1281,6 +1281,79 @@ void editor_mode::render_track_set_ui()
ImGui::Separator(); ImGui::Separator();
} }
bool editor_mode::track_set_begin(bool const Rotate)
{
std::string reason;
if (false == tracks_editable(m_track_set, reason))
{
ui()->set_status(reason, true);
return false;
}
m_track_set_using = true;
m_track_gizmo_drag = {};
m_track_gizmo_drag.operation = Rotate ? gizmo_operation::rotate : gizmo_operation::translate;
m_track_drag = m_track_set;
m_track_set_joints.clear();
for (auto *track : m_track_set)
{
auto joints{editor_track::joints(*track)};
joints.erase(std::remove_if(joints.begin(), joints.end(), [&](editor_track::joint const &Joint) { return in_track_set(Joint.other); }), joints.end());
for (auto const &joint : joints)
if (std::find(m_track_drag.begin(), m_track_drag.end(), joint.other) == m_track_drag.end())
m_track_drag.push_back(joint.other);
m_track_set_joints.emplace_back(track, std::move(joints));
}
std::vector<std::pair<TTrack *, editor_track::state>> states;
for (auto *track : m_track_drag)
states.emplace_back(track, editor_track::capture(*track));
push_track_snapshot(std::move(states));
track_gizmo_hold();
return true;
}
void editor_mode::track_set_turn(double const Angle)
{
auto &drag{m_track_gizmo_drag};
auto const step{std::remainder(Angle - drag.angle, 2.0 * glm::pi<double>())};
if (std::abs(step) < 1e-9)
return;
drag.angle += step;
for (auto &entry : m_track_set_joints)
editor_track::rotate_y(*entry.first, m_track_set_pivot, step);
for (auto &entry : m_track_set_joints)
editor_track::follow(*entry.first, entry.second);
m_track_dirty = true;
}
void editor_mode::track_set_move(glm::dvec3 const &Offset)
{
for (auto &entry : m_track_set_joints)
{
editor_track::translate(*entry.first, Offset);
editor_track::follow(*entry.first, entry.second);
}
m_track_dirty = true;
}
void editor_mode::track_set_end()
{
track_gizmo_settle();
m_track_dirty = true;
commit_track_drag(true);
m_track_set_using = false;
m_track_drag.clear();
m_track_set_joints.clear();
straight_refresh();
}
glm::dvec3 editor_mode::track_set_centre() const
{
glm::dvec3 centre{0.0};
for (auto const *track : m_track_set)
centre += editor_track::pivot(*track);
return m_track_set.empty() ? centre : centre / static_cast<double>(m_track_set.size());
}
void editor_mode::render_track_set_gizmo() void editor_mode::render_track_set_gizmo()
{ {
track_set_prune(); track_set_prune();
@@ -1292,61 +1365,24 @@ void editor_mode::render_track_set_gizmo()
gizmo_frame const frame; gizmo_frame const frame;
if (false == m_track_set_using) if (false == m_track_set_using)
{ {
glm::dvec3 centre{0.0}; m_track_set_pivot = track_set_centre();
for (auto const *track : m_track_set)
centre += editor_track::pivot(*track);
m_track_set_pivot = centre / static_cast<double>(m_track_set.size());
m_track_set_gizmo = glm::translate(glm::mat4(1.0f), glm::vec3(m_track_set_pivot - frame.camera)); m_track_set_gizmo = glm::translate(glm::mat4(1.0f), glm::vec3(m_track_set_pivot - frame.camera));
} }
auto const rotate{m_track_set_using ? m_track_gizmo_drag.operation == gizmo_operation::rotate : m_gizmo_op == gizmo_operation::rotate};
glm::mat4 delta(1.0f); glm::mat4 delta(1.0f);
frame.manipulate(ImGuizmo::TRANSLATE, m_track_set_gizmo, m_gizmo_snap, &delta); frame.manipulate(rotate ? ImGuizmo::ROTATE_Y : ImGuizmo::TRANSLATE, m_track_set_gizmo, rotate ? 5.0f : m_gizmo_snap, &delta);
if (ImGuizmo::IsUsing()) if (ImGuizmo::IsUsing())
{ {
if (false == m_track_set_using) if (false == m_track_set_using && false == track_set_begin(rotate))
{ return;
std::string reason; if (rotate)
if (false == tracks_editable(m_track_set, reason)) track_set_turn(std::atan2(static_cast<double>(m_track_set_gizmo[2][0]), static_cast<double>(m_track_set_gizmo[0][0])));
{ else if (glm::dvec3 const offset{delta[3]}; glm::length(offset) > 1e-9)
ui()->set_status(reason, true); track_set_move(offset);
return;
}
m_track_set_using = true;
m_track_drag = m_track_set;
m_track_set_joints.clear();
for (auto *track : m_track_set)
{
auto joints{editor_track::joints(*track)};
joints.erase(std::remove_if(joints.begin(), joints.end(), [&](editor_track::joint const &Joint) { return in_track_set(Joint.other); }), joints.end());
for (auto const &joint : joints)
if (std::find(m_track_drag.begin(), m_track_drag.end(), joint.other) == m_track_drag.end())
m_track_drag.push_back(joint.other);
m_track_set_joints.emplace_back(track, std::move(joints));
}
std::vector<std::pair<TTrack *, editor_track::state>> states;
for (auto *track : m_track_drag)
states.emplace_back(track, editor_track::capture(*track));
push_track_snapshot(std::move(states));
}
glm::dvec3 const offset{delta[3]};
if (glm::length(offset) > 1e-9)
{
for (auto &entry : m_track_set_joints)
{
editor_track::translate(*entry.first, offset);
editor_track::follow(*entry.first, entry.second);
}
m_track_dirty = true;
}
commit_track_drag(false); commit_track_drag(false);
} }
else if (m_track_set_using) else if (m_track_set_using)
{ track_set_end();
commit_track_drag(true);
m_track_set_using = false;
m_track_drag.clear();
m_track_set_joints.clear();
straight_refresh();
}
} }
void editor_mode::render_lineside_ui() void editor_mode::render_lineside_ui()

View File

@@ -130,11 +130,11 @@ struct gizmo_frame
projection = editor_mode::projection_matrix(io.DisplaySize.y > 0.0f ? io.DisplaySize.x / io.DisplaySize.y : 1.0f); projection = editor_mode::projection_matrix(io.DisplaySize.y > 0.0f ? io.DisplaySize.x / io.DisplaySize.y : 1.0f);
} }
// moves the gizmo, placed at the point given in world space, with snapping to the step when ctrl is held // moves the gizmo, placed at the point given in world space, with snapping to the step when ctrl is held
bool manipulate(ImGuizmo::OPERATION const Operation, glm::mat4 &Gizmo, float const Snap, glm::mat4 *Delta = nullptr) const bool manipulate(ImGuizmo::OPERATION const Operation, glm::mat4 &Gizmo, float const Snap, glm::mat4 *Delta = nullptr, ImGuizmo::MODE const Mode = ImGuizmo::WORLD) const
{ {
glm::vec3 snapvalue(Snap); glm::vec3 snapvalue(Snap);
float const *snap = Global.ctrlState && Snap > 0.0f ? &snapvalue.x : nullptr; float const *snap = Global.ctrlState && Snap > 0.0f ? &snapvalue.x : nullptr;
drawn = ImGui::GetFrameCount(); drawn = ImGui::GetFrameCount();
return ImGuizmo::Manipulate(&view[0][0], &projection[0][0], Operation, ImGuizmo::WORLD, &Gizmo[0][0], Delta != nullptr ? &(*Delta)[0][0] : nullptr, snap); return ImGuizmo::Manipulate(&view[0][0], &projection[0][0], Operation, Mode, &Gizmo[0][0], Delta != nullptr ? &(*Delta)[0][0] : nullptr, snap);
} }
}; };

View File

@@ -665,6 +665,89 @@ void editor_track::follow(TTrack const &Track, std::vector<joint> const &Joints)
} }
} }
std::vector<int> editor_track::hold_controls(TTrack &Track)
{
std::vector<int> result;
for (int i = 0; i < static_cast<int>(Track.m_paths.size()); ++i)
{
auto &path{Track.m_paths[i]};
auto &control1{path.points[segment_data::point::control1]};
auto &control2{path.points[segment_data::point::control2]};
if (control1 != glm::dvec3{} || control2 != glm::dvec3{})
continue;
auto const chord{path.points[segment_data::point::end] - path.points[segment_data::point::start]};
if (glm::length(chord) < 1e-6)
continue;
control1 = chord / 3.0;
control2 = -chord / 3.0;
result.push_back(i);
}
return result;
}
void editor_track::settle_controls(TTrack &Track, std::vector<int> const &Paths)
{
for (auto const index : Paths)
{
if (index < 0 || index >= static_cast<int>(Track.m_paths.size()))
continue;
auto &path{Track.m_paths[index]};
auto const chord{path.points[segment_data::point::end] - path.points[segment_data::point::start]};
auto const length{glm::length(chord)};
if (length < 1e-6)
continue;
auto const axis{chord / length};
auto const &control1{path.points[segment_data::point::control1]};
auto const &control2{path.points[segment_data::point::control2]};
auto const off = [&](glm::dvec3 const &Control) { return glm::length(Control - axis * glm::dot(Control, axis)); };
if (glm::dot(control1, axis) <= 0.0 || glm::dot(control2, axis) >= 0.0 || off(control1) > 1e-3 || off(control2) > 1e-3)
continue;
path.points[segment_data::point::control1] = glm::dvec3{};
path.points[segment_data::point::control2] = glm::dvec3{};
}
}
editor_track::point_ref editor_track::end_of(point_ref const &Control)
{
switch (Control.kind)
{
case point_kind::control1: return {Control.path, point_kind::start};
case point_kind::control2: return {Control.path, point_kind::end};
default: return Control;
}
}
std::vector<std::pair<TTrack *, editor_track::point_ref>> editor_track::partners(TTrack &Track, point_ref const &End)
{
auto const position{point_position(Track, End)};
auto result{connected_points(Track, position)};
for (int i = 0; i < static_cast<int>(Track.m_paths.size()); ++i)
{
if (i == End.path)
continue;
for (auto const kind : {point_kind::start, point_kind::end})
if (glm::distance(Track.m_paths[i].points[point_index(kind)], position) < kSamePoint)
result.emplace_back(&Track, point_ref{i, kind});
}
return result;
}
void editor_track::align_controls(TTrack const &Track, point_ref const &Control, std::vector<std::pair<TTrack *, point_ref>> const &Partners)
{
auto const own{point_position(Track, Control) - point_position(Track, end_of(Control))};
if (glm::length(own) < 1e-6)
return;
auto const direction{glm::normalize(own)};
for (auto const &partner : Partners)
{
auto &path{partner.first->m_paths[partner.second.path]};
bool const start{partner.second.kind == point_kind::start};
auto &control{path.points[start ? segment_data::point::control1 : segment_data::point::control2]};
auto const length{control != glm::dvec3{} ? glm::length(control) : glm::distance(path.points[segment_data::point::start], path.points[segment_data::point::end]) / 3.0};
control = -direction * length;
}
}
bool editor_track::is_connected(TTrack const &Track, point_ref const &Point) bool editor_track::is_connected(TTrack const &Track, point_ref const &Point)
{ {
if (false == editor_track::is_end(Point.kind)) if (false == editor_track::is_end(Point.kind))

View File

@@ -116,6 +116,11 @@ class editor_track
static std::vector<joint> joints(TTrack const &Track); static std::vector<joint> joints(TTrack const &Track);
// moves the joined ends to where the ends of the track are now, turning them as the track turned there // moves the joined ends to where the ends of the track are now, turning them as the track turned there
static void follow(TTrack const &Track, std::vector<joint> const &Joints); static void follow(TTrack const &Track, std::vector<joint> const &Joints);
static std::vector<int> hold_controls(TTrack &Track);
static void settle_controls(TTrack &Track, std::vector<int> const &Paths);
static std::vector<std::pair<TTrack *, point_ref>> partners(TTrack &Track, point_ref const &End);
static void align_controls(TTrack const &Track, point_ref const &Control, std::vector<std::pair<TTrack *, point_ref>> const &Partners);
static point_ref end_of(point_ref const &Control);
struct snap_target struct snap_target
{ {

View File

@@ -5268,3 +5268,39 @@ msgstr "kładzie prostą dotąd i idzie dalej od jej końca"
msgid "type the radius" msgid "type the radius"
msgstr "wpisz promień" msgstr "wpisz promień"
msgid "moves the control point, the joined track keeps the tangent"
msgstr "przesuwa punkt kontrolny, dołączony tor zachowuje styczność"
msgid "turns the control vector, the joined track keeps the tangent"
msgstr "obraca wektor kontrolny, dołączony tor zachowuje styczność"
msgid "length of the control vector"
msgstr "długość wektora kontrolnego"
msgid "turns the track about the point, the joined tracks follow"
msgstr "obraca tor wokół punktu, dołączone tory idą za nim"
msgid "lengthens the track at this end, the curve keeps its radius"
msgstr "wydłuża tor na tym końcu, łuk zachowuje promień"
msgid "turns the track, the joined tracks follow"
msgstr "obraca tor, dołączone tory idą za nim"
msgid "lengthens the track at its free end, the curve keeps its radius"
msgstr "wydłuża tor na wolnym końcu, łuk zachowuje promień"
msgid "gizmo: move, turn, lengthen"
msgstr "gizmo: przesuń, obróć, wydłuż"
msgid "control %.2f m azimuth %.2f deg"
msgstr "wektor %.2f m azymut %.2f°"
msgid " turned %+.2f deg"
msgstr " obrót %+.2f°"
msgid "turned %+.2f deg"
msgstr "obrót %+.2f°"
msgid "along the switch"
msgstr "wzdłuż rozjazdu"