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:
@@ -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();
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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]};
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user