From 7fcb53c7d028af804dae58b5dfe5958900020209 Mon Sep 17 00:00:00 2001 From: Bruno Herbelin Date: Sun, 27 Aug 2023 12:15:31 +0200 Subject: [PATCH] New GRID in views to perrorm MousePointer snap to GRID For this new MousePointer to snap to grid, a Grid specific for each view is necessary. Grid for moving is orthographic (with an aspect ratio), and grid for mixing or rotation is in polar coordinates. Rendering is done with new Primitives. The entire calculation of grab coordinates is changed to be able to snap to grid coordinates in all circumstances. --- rsc/images/icons.dds | Bin 1638528 -> 1638528 bytes rsc/mesh/icon_clock_hand.ply | 25 +- src/CMakeLists.txt | 1 + src/DisplaysView.cpp | 8 +- src/DisplaysView.h | 1 - src/DrawVisitor.cpp | 11 +- src/GeometryView.cpp | 574 +++++++++++++++++++++++------------ src/GeometryView.h | 5 + src/Grid.cpp | 154 ++++++++++ src/Grid.h | 89 ++++++ src/ImGuiVisitor.cpp | 2 +- src/LayerView.cpp | 87 +++++- src/LayerView.h | 8 + src/MixingView.cpp | 116 +++++-- src/MixingView.h | 10 +- src/MousePointer.cpp | 168 +++++----- src/MousePointer.h | 72 ++++- src/PickingVisitor.h | 1 + src/Primitives.cpp | 165 +++++++++- src/Primitives.h | 24 +- src/Settings.cpp | 2 + src/Settings.h | 2 + src/TextureView.cpp | 493 ++++++++++++++++++------------ src/TextureView.h | 5 +- src/TransitionView.cpp | 1 - src/UserInterfaceManager.cpp | 60 ++-- src/View.cpp | 6 +- src/View.h | 5 +- src/defines.h | 1 + 29 files changed, 1517 insertions(+), 579 deletions(-) create mode 100644 src/Grid.cpp create mode 100644 src/Grid.h diff --git a/rsc/images/icons.dds b/rsc/images/icons.dds index e0486690f0f36b9e4ed4af0fa5e21f263fddfab5..680e07055134649db4d6d8022dee5a7358b9e7fa 100644 GIT binary patch delta 1947 zcmcIlUuYa<5TDyk(&YB)?zgLH5kjsB1s@a+`Xbb1lZvzm)}XluMJR$51u;!j@TIwp zF1-^JvsJ`WP&p#4}B(AbQU5z#Oc>IzI@aC<5q#QMy>q>ALPY_xBSNg1~_9yEgJ0dA8U# z;s?fyRsZ~(+CQeTG8JDx;mV;iQ`m@OCUGfjJ8nRWWAc?aQ`|TF@7~o` z%u12HIXBSysQxd7W*w3BdL3^j`pVStXVs!&u#R|rf$bmHTb=k+Ux#ybYf)i*6rnu| zdttm@+GJhVu@%REobYKvqy7xaz6Al|M!vcYreQu#h}1S&&{jOWlf!Q5-)q~RGvS~7 zMEk?67!2h-P`~N_Lc1E|HB{oGu}FHrcO8d-azL;pe%PjlxPZ?8pB z>F3=d>3i#tt{?1lXuPr|;4-z^Io6-hyjnr*#8U#US8l)_@Ma(NPn^~Ut-JmXc>bje z+OO0vu@K5GwrLk4yqM_Rn3?_EZQ4=NF2t2ie5zAp#A{#X*gvLy6do26(w@*x@l!m& z2#8(bd+ZR?PQ>@$ru`N-qQe0MguQWrHqXekf9a~WXjay7rk3=LUtqjh%jl}18gCtF z6HQFFu$r+WX7OLcOF4MEO|%_F6IiM}&>q}Yi*}R6j{{EC4x|Mz(J*nWC!Xiv7QI?o z2f%?^cZvsy_Y%=u4)9~51MzcOcYFBf{iUwLWwSC~j=Kmn6}-xcUgnJ7JZFh(%`;6Z z;6`kpyls#56faMrKg)Y!+M{KjhY{E#@pKg35nuZ6h4N!D?ZGn7!|#3OzT}9yBY}KS zmd6R*j(E=V{@I;{>t>~im&4*vJT25OVm%Cpw&<$SW$YXNJi(gc} z$_)uP$9Ot~qp{7>RWbC4oW@oQiS-PA4DkJMc1ywlj=+&&^Y6YSVTS`?KJ2CXWI#mQ zt4#XeK3Lc^2je~eZ>&%Ir^X6;a*Uikn6x_il&ryVE;;>_?2sj9J|!SOBz>=|jHGVc z^vXJ)1(OA9dr6J!mlp5nLsoqHwl^h1ss7Pb>%*LX;^l&E`tz$+q*Rw?3nbe~?jg}h Z3=%bn%#MF?myGa`-}hp delta 1515 zcmZWpU2GIp6h1RMyDiQ_XJ=a4*y>JOswLI1jV2Zzv;iM{Aap@_(eNiiBq6eoXiU4C zj6q|Q?qt;)HPY6Q5J?OLir2TLKmtB!DHt&kGohxK2<&RoC@t(B&zo3@uqVfH9EN;PWSVeL6O*@?+cd&cv%JX*JH~s{dfMEHSO+Vpj{)@UPX5mm!1^7jaKz+3ty5 zAflR=n44T-vBtf|3~NfqZt`3E^j{Ko2J+$&Ji)>u9>V8Im$2gI?BOxT&&_fJTH9x1Pau{pi*v;*v+=vUxE>iUq$Eb zFy6ubdOzauv%~tV=^k0%ovOf(&>yPjWKfX|slsL`cjGj>yiNI*PmJj|O*c2to_YYM z!4A0*e-X!pmtWb*LT}E}UL#j(=L0@IsV6Nr-?hoGzmXJU0Zu!&eUD&T{`QBewA1vK zMNBgSJHU(L77F+kpSi4GGhL^1gCXhE_T-+*@4kzmUl+R~wn{r5Jg7uy5)xa?9bRdz zxfd!LJz)$)gwUV|Mxm@x#!vao4Sm{l^X=<0a4S`*bt)aiDxAE?E@*?;avnEpvvXJD z(d2p9EjbVSP?bB`N1FNtpZSx{rOTib$w=}X-ba5ZO9`mTB97uvp}TcGOMDWuw7>Bx zvcAt69#@_&)X}uBXz`!S4YgKb45r{Wn3oa%m;Mm$!kwffq6IhG|Kuqg!z}tmgia^$ z6r3$EARfy14xeg?do-HFx_OwVJX11Gz^?hwO&G!vq%Uka4>Rtlnw3TW&V&=O<9oGt z`I)EURWp@`-QpfdH?dZ6K`epCH2N1?Vukg-*O+wkHk-H|(H@%%Q_DC_RkTT}R_JZb z@KgQq28&H}G-+#$`*2Mf#VRFexu{?r4zk0?8hSdiFqf_fvoQG^@wqMGa&!b9EkyT! zc>LTeacf9iFd@=h!HZ}w!d8ic`u`Bn57!Oen?6{=VDI}mjQwxh^ZLX)ksf>g^wSIzhL%m{qDbi5O+%(w+p_ERr>l_HH@F*iFB-}vMXvisible_ = false; // if ( display_action_ == 2 ) { @@ -1116,8 +1116,8 @@ View::Cursor DisplaysView::over (glm::vec2 pos) // } - return Cursor(); -} +// return Cursor(); +//} bool DisplaysView::doubleclic (glm::vec2 P) { diff --git a/src/DisplaysView.h b/src/DisplaysView.h index 010f8be..1e0d2c9 100644 --- a/src/DisplaysView.h +++ b/src/DisplaysView.h @@ -73,7 +73,6 @@ public: void initiate () override; void terminate (bool force = false) override; Cursor grab (Source *s, glm::vec2 from, glm::vec2 to, std::pair pick) override; - Cursor over (glm::vec2) override; void arrow (glm::vec2) override; bool doubleclic (glm::vec2) override; diff --git a/src/DrawVisitor.cpp b/src/DrawVisitor.cpp index 9d03a39..6009e4b 100644 --- a/src/DrawVisitor.cpp +++ b/src/DrawVisitor.cpp @@ -24,7 +24,6 @@ #include "Decorations.h" #include "defines.h" #include "Scene.h" -#include "Primitives.h" #include "DrawVisitor.h" @@ -64,6 +63,7 @@ void DrawVisitor::visit(Node &n) // found this node in the list of targets: draw it if (it != targets_.end()) { + // remove this node from list of targets targets_.erase(it); for (int i = 0; i < num_duplicat_; ++i) { @@ -76,6 +76,7 @@ void DrawVisitor::visit(Node &n) // restore visibility n.visible_ = v; + // no need to traverse deeper if alls nodes were drawn if (targets_.empty()) return; // update transform @@ -85,7 +86,7 @@ void DrawVisitor::visit(Node &n) void DrawVisitor::visit(Group &n) { - // no need to traverse deeper if this node was drawn already + // no need to traverse deeper if alls nodes were drawn if (targets_.empty()) return; // traverse children @@ -105,7 +106,7 @@ void DrawVisitor::visit(Scene &n) void DrawVisitor::visit(Switch &n) { - // no need to traverse deeper if this node was drawn already + // no need to traverse deeper if alls nodes were drawn if (targets_.empty()) return; // traverse acive child @@ -144,7 +145,9 @@ void ColorVisitor::visit(Scene &n) void ColorVisitor::visit(Switch &n) { - n.activeChild()->accept(*this); + for (uint c = 0; c < n.numChildren(); ++c) { + n.child(c)->accept(*this); + } } void ColorVisitor::visit(Primitive &p) diff --git a/src/GeometryView.cpp b/src/GeometryView.cpp index e32a750..13969fa 100644 --- a/src/GeometryView.cpp +++ b/src/GeometryView.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include "ImGuiToolkit.h" @@ -138,6 +139,12 @@ GeometryView::GeometryView() : View(GEOMETRY) overlay_selection_rotate_ = nullptr; overlay_selection_stored_status_ = nullptr; overlay_selection_active_ = false; + + // replace grid with appropriate one + translation_grid_ = new TranslationGrid(scene.root()); + rotation_grid_ = new RotationGrid(scene.root()); + if (grid) delete grid; + grid = translation_grid_; } void GeometryView::update(float dt) @@ -164,6 +171,17 @@ void GeometryView::update(float dt) float s = CLAMP(scene.root()->scale_.x, GEOMETRY_MIN_SCALE, GEOMETRY_MAX_SCALE); scene.root()->scale_.x = s; scene.root()->scale_.y = s; + + // change grid color + const ImVec4 c = ImGuiToolkit::HighlightColor(); + translation_grid_->setColor( glm::vec4(c.x, c.y, c.z, 0.3) ); + rotation_grid_->setColor( glm::vec4(c.x, c.y, c.z, 0.3) ); + + // set grid aspect ratio + if (Settings::application.proportional_grid) + translation_grid_->setAspectRatio( Mixer::manager().session()->frame()->aspectRatio() ); + else + translation_grid_->setAspectRatio( 1.f ); } // the current view is the geometry view @@ -171,6 +189,7 @@ void GeometryView::update(float dt) { ImVec4 c = ImGuiToolkit::HighlightColor(); updateSelectionOverlay(glm::vec4(c.x, c.y, c.z, c.w)); + // overlay_selection_icon_->visible_ = false; } } @@ -251,6 +270,12 @@ void GeometryView::draw() DrawVisitor draw_foreground(scene.fg(), projection); scene.accept(draw_foreground); + // 6. Display grid + if (grid->active() && current_action_ongoing_) { + DrawVisitor draw_grid(grid->root(), projection, true); + scene.accept(draw_grid); + } + // display interface // Locate window at upper right corner glm::vec2 P = glm::vec2(-output_surface_->scale_.x - 0.02f, output_surface_->scale_.y + 0.01); @@ -270,28 +295,10 @@ void GeometryView::draw() ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.4f, 0.4f, 0.4f, 0.56f)); ImGui::PushStyleColor(ImGuiCol_PopupBg, ImVec4(0.14f, 0.14f, 0.14f, 0.9f)); -// bool on = Settings::application.current_workspace == Source::BACKGROUND; -// if ( ImGuiToolkit::ButtonIconToggle(10,16,10,16, &on, "Background") ) { -// Settings::application.current_workspace = Source::BACKGROUND; -// ++View::need_deep_update_; -// } -// ImGui::SameLine(0, IMGUI_SAME_LINE); -// on = Settings::application.current_workspace == Source::STAGE; -// if ( ImGuiToolkit::ButtonIconToggle(11,16,11,16, &on, "Workspace") ) { -// Settings::application.current_workspace = Source::STAGE; -// ++View::need_deep_update_; -// } -// ImGui::SameLine(0, IMGUI_SAME_LINE); -// on = Settings::application.current_workspace == Source::FOREGROUND; -// if ( ImGuiToolkit::ButtonIconToggle(12,16,12,16, &on, "Foreground") ) { -// Settings::application.current_workspace = Source::FOREGROUND; -// ++View::need_deep_update_; -// } - static std::vector< std::tuple > _workspaces = { - {10, 16, "Background"}, - {11, 16, "Workspace"}, - {12, 16, "Foreground"} + {10, 16, "Only sources in Background layer"}, + {11, 16, "Only sources in Workspace layer"}, + {12, 16, "Only sources in Foreground layer"} }; ImGui::SetNextItemWidth( ImGui::GetTextLineHeight() * 2.6); if ( ImGuiToolkit::ComboIcon ("##WORKSPACE", &Settings::application.current_workspace, _workspaces, true) ){ @@ -427,6 +434,56 @@ void GeometryView::draw() } } +void GeometryView::adaptGridToSource(Source *s, Node *picked) +{ + // Reset by default + rotation_grid_->root()->translation_ = glm::vec3(0.f); + rotation_grid_->root()->scale_ = glm::vec3(1.f); + translation_grid_->root()->translation_ = glm::vec3(0.f); + translation_grid_->root()->rotation_.z = 0.f; + + if (s != nullptr && picked != nullptr) { + if (picked == s->handles_[mode_][Handles::ROTATE]) { + // shift grid at center of source + rotation_grid_->root()->translation_ = s->group(mode_)->translation_; + rotation_grid_->root()->scale_.x = glm::length( + glm::vec2(s->frame()->aspectRatio() * s->group(mode_)->scale_.x, + s->group(mode_)->scale_.y) ); + rotation_grid_->root()->scale_.y = rotation_grid_->root()->scale_.x; + // Swap grid to rotation grid + rotation_grid_->setActive( grid->active() ); + translation_grid_->setActive( false ); + grid = rotation_grid_; + return; + } + else if ( picked == s->handles_[mode_][Handles::RESIZE] || + picked == s->handles_[mode_][Handles::RESIZE_V] || + picked == s->handles_[mode_][Handles::RESIZE_H] ){ + translation_grid_->root()->translation_ = glm::vec3(0.f); + translation_grid_->root()->rotation_.z = s->group(mode_)->rotation_.z; + // Swap grid to translation grid + translation_grid_->setActive( grid->active() ); + rotation_grid_->setActive( false ); + grid = translation_grid_; + } + else if ( picked == s->handles_[mode_][Handles::SCALE] || + picked == s->handles_[mode_][Handles::CROP] ){ + translation_grid_->root()->translation_ = s->group(mode_)->translation_; + translation_grid_->root()->rotation_.z = s->group(mode_)->rotation_.z; + // Swap grid to translation grid + translation_grid_->setActive( grid->active() ); + rotation_grid_->setActive( false ); + grid = translation_grid_; + } + } + else { + // Default: + // Grid in scene global coordinate + translation_grid_->setActive( grid->active() ); + rotation_grid_->setActive( false ); + grid = translation_grid_; + } +} std::pair GeometryView::pick(glm::vec2 P) { @@ -437,7 +494,7 @@ std::pair GeometryView::pick(glm::vec2 P) glm::vec3 scene_point_ = Rendering::manager().unProject(P); // picking visitor traverses the scene - PickingVisitor pv(scene_point_); + PickingVisitor pv(scene_point_, false); scene.accept(pv); // picking visitor found nodes? @@ -457,6 +514,8 @@ std::pair GeometryView::pick(glm::vec2 P) if ( is_in_source( current ) ){ // a node in the current source was clicked ! pick = *itp; + // adapt grid to prepare grab action + adaptGridToSource(current, pick.first); break; } } @@ -464,6 +523,7 @@ std::pair GeometryView::pick(glm::vec2 P) // OR the selection contains multiple sources and actions on single source are disabled if (itp == pv.rend() || Mixer::selection().size() > 1) { current = nullptr; + pick = { nullptr, glm::vec2(0.f) }; } // picking on the menu handle: show context menu else if ( pick.first == current->handles_[mode_][Handles::MENU] ) { @@ -581,7 +641,6 @@ View::Cursor GeometryView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::p // grab coordinates in scene-View reference frame glm::vec3 scene_from = Rendering::manager().unProject(from, scene.root()->transform_); glm::vec3 scene_to = Rendering::manager().unProject(to, scene.root()->transform_); - glm::vec3 scene_translation = scene_to - scene_from; // No source is given if (!s) { @@ -613,6 +672,24 @@ View::Cursor GeometryView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::p overlay_scaling_->color = overlay_selection_icon_->color; overlay_scaling_cross_->color = overlay_selection_icon_->color; + // + // Manipulate the scaling handle in the SCENE coordinates to apply grid snap + // + if ( grid->active() ) { + glm::vec3 handle = glm::vec3(1.f, -1.f, 0.f); + // Compute handle coordinates into SCENE reference frame + handle = overlay_selection_stored_status_->transform_ * glm::vec4( handle, 1.f ); + // move the handle we hold by the mouse translation (in scene reference frame) + handle = glm::translate(glm::identity(), scene_to - scene_from) * glm::vec4( handle, 1.f ); + // snap handle coordinates to grid (if active) + handle = grid->snap(handle); + // Compute handle coordinates back in SOURCE reference frame + handle = glm::inverse(overlay_selection_stored_status_->transform_) * glm::vec4( handle, 1.f ); + // The scaling factor is computed by dividing new handle coordinates with the ones before transform + glm::vec3 handle_scaling = glm::vec3(handle) / glm::vec3(1.f, -1.f, 1.f); + S = glm::scale(glm::identity(), handle_scaling); + } + // apply to selection overlay glm::vec4 vec = S * glm::vec4( overlay_selection_stored_status_->scale_, 0.f ); overlay_selection_->scale_ = glm::vec3(vec); @@ -639,34 +716,48 @@ View::Cursor GeometryView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::p overlay_rotation_fix_->copyTransform(overlay_rotation_); overlay_rotation_fix_->color = overlay_selection_icon_->color; - // cancel out scaling with SHIFT modifier key - if (UserInterface::manager().shiftModifier()) { - overlay_rotation_fix_->visible_ = true; - float scale_factor = glm::length( glm::vec2( overlay_selection_->scale_ ) ) / glm::length( glm::vec2( overlay_selection_stored_status_->scale_ ) ); - S = glm::scale(glm::identity(), glm::vec3(scale_factor, scale_factor, 1.f)); - } + // Swap grid to rotation, shifted at center of source + rotation_grid_->setActive( grid->active() ); + translation_grid_->setActive( false ); + grid = rotation_grid_; + grid->root()->translation_ = overlay_selection_stored_status_->translation_; + + // prepare variables + const float diagonal = glm::length( glm::vec2(overlay_selection_stored_status_->scale_)); + glm::vec2 handle_polar = glm::vec2( diagonal, 0.f); // compute rotation angle float angle = glm::orientedAngle( glm::normalize(glm::vec2(selection_from)), glm::normalize(glm::vec2(selection_to))); + handle_polar.y = overlay_selection_stored_status_->rotation_.z + angle; + + // compute scaling of diagonal to reach new coordinates + handle_polar.x *= factor; + + // snap polar coordiantes (diagonal lenght, angle) + if ( grid->active() ) { + handle_polar = glm::round( handle_polar / grid->step() ) * grid->step(); + // prevent null size + handle_polar.x = glm::max( grid->step().x, handle_polar.x ); + } + + // cancel scaling with SHIFT modifier key + if (UserInterface::manager().shiftModifier()) { + overlay_rotation_fix_->visible_ = true; + handle_polar.x = 1.f; + } + else + handle_polar.x /= diagonal ; + + S = glm::scale(glm::identity(), glm::vec3(handle_polar.x, handle_polar.x, 1.f)); // apply to selection overlay glm::vec4 vec = S * glm::vec4( overlay_selection_stored_status_->scale_, 0.f ); overlay_selection_->scale_ = glm::vec3(vec); - overlay_selection_->rotation_.z = overlay_selection_stored_status_->rotation_.z + angle; - - // POST-CORRECTION ; discretized rotation with ALT - if (UserInterface::manager().altModifier()) { - int degrees = int( glm::degrees(overlay_selection_->rotation_.z) ); - degrees = (degrees / 10) * 10; - overlay_selection_->rotation_.z = glm::radians( float(degrees) ); - angle = overlay_selection_->rotation_.z - overlay_selection_stored_status_->rotation_.z; - overlay_rotation_clock_->visible_ = true; - overlay_rotation_clock_->copyTransform(overlay_rotation_); - overlay_rotation_clock_tic_->color = overlay_selection_icon_->color; - } + overlay_selection_->rotation_.z = handle_polar.y; // apply to selection sources // NB: complete transform matrix (right to left) : move to center, rotate, scale and move back + angle = handle_polar.y - overlay_selection_stored_status_->rotation_.z; glm::mat4 R = glm::rotate(glm::identity(), angle, glm::vec3(0.f, 0.f, 1.f) ); glm::mat4 M = T * S * R * glm::inverse(T); applySelectionTransform(M); @@ -686,35 +777,28 @@ View::Cursor GeometryView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::p // make sure matrix transform of stored status is updated s->stored_status_->update(0); + // grab coordinates in source-root reference frame - glm::vec4 source_from = glm::inverse(s->stored_status_->transform_) * glm::vec4( scene_from, 1.f ); - glm::vec4 source_to = glm::inverse(s->stored_status_->transform_) * glm::vec4( scene_to, 1.f ); - glm::vec3 source_scaling = glm::vec3(source_to) / glm::vec3(source_from); + const glm::mat4 source_scale = glm::scale(glm::identity(), + glm::vec3(1.f / s->frame()->aspectRatio(), 1.f, 1.f)); + const glm::mat4 scene_to_source_transform = source_scale * glm::inverse(s->stored_status_->transform_); + const glm::mat4 source_to_scene_transform = glm::inverse(scene_to_source_transform); // which manipulation to perform? std::ostringstream info; if (pick.first) { + // which corner was picked ? glm::vec2 corner = glm::round(pick.second); - // transform from source center to corner - glm::mat4 T = GlmToolkit::transform(glm::vec3(corner.x, corner.y, 0.f), glm::vec3(0.f, 0.f, 0.f), - glm::vec3(1.f / s->frame()->aspectRatio(), 1.f, 1.f)); + // keep transform from source center to opposite corner + const glm::mat4 source_to_corner_transform = glm::translate(glm::identity(), glm::vec3(corner, 0.f)); // transformation from scene to corner: - glm::mat4 scene_to_corner_transform = T * glm::inverse(s->stored_status_->transform_); - glm::mat4 corner_to_scene_transform = glm::inverse(scene_to_corner_transform); + const glm::mat4 scene_to_corner_transform = source_to_corner_transform * scene_to_source_transform; + const glm::mat4 corner_to_scene_transform = glm::inverse(scene_to_corner_transform); - // compute cursor movement in corner reference frame - glm::vec4 corner_from = scene_to_corner_transform * glm::vec4( scene_from, 1.f ); - glm::vec4 corner_to = scene_to_corner_transform * glm::vec4( scene_to, 1.f ); - // operation of scaling in corner reference frame - glm::vec3 corner_scaling = glm::vec3(corner_to) / glm::vec3(corner_from); - - // convert source position in corner reference frame - glm::vec4 center = scene_to_corner_transform * glm::vec4( s->stored_status_->translation_, 1.f); - - // picking on the resizing handles in the corners + // picking on the resizing handles in the corners RESIZE CORNER if ( pick.first == s->handles_[mode_][Handles::RESIZE] ) { // hide all other grips @@ -726,47 +810,48 @@ View::Cursor GeometryView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::p s->handles_[mode_][Handles::MENU]->visible_ = false; // inform on which corner should be overlayed (opposite) s->handles_[mode_][Handles::RESIZE]->overlayActiveCorner(-corner); - // RESIZE CORNER + + // + // Manipulate the handle in the SCENE coordinates to apply grid snap + // + glm::vec4 handle = corner_to_scene_transform * glm::vec4(corner * 2.f, 0.f, 1.f ); + // move the corner we hold by the mouse translation (in scene reference frame) + handle = glm::translate(glm::identity(), scene_to - scene_from) * handle; + // snap handle coordinates to grid (if active) + if ( grid->active() ) + handle = grid->snap(handle); + // Compute coordinates coordinates back in CORNER reference frame + handle = scene_to_corner_transform * handle; + // The scaling factor is computed by dividing new handle coordinates with the ones before transform + glm::vec2 corner_scaling = glm::vec2(handle) / glm::vec2(corner * 2.f); + // proportional SCALING with SHIFT if (UserInterface::manager().shiftModifier()) { - // calculate proportional scaling factor - float factor = glm::length( glm::vec2( corner_to ) ) / glm::length( glm::vec2( corner_from ) ); - // scale node - sourceNode->scale_ = s->stored_status_->scale_ * glm::vec3(factor, factor, 1.f); - // discretized scaling with ALT - if (UserInterface::manager().altModifier()) { - sourceNode->scale_.x = ROUND(sourceNode->scale_.x, 10.f); - factor = sourceNode->scale_.x / s->stored_status_->scale_.x; - sourceNode->scale_.y = s->stored_status_->scale_.y * factor; - } - // update corner scaling to apply to center coordinates - corner_scaling = sourceNode->scale_ / s->stored_status_->scale_; + corner_scaling = glm::vec2(glm::compMax(corner_scaling)); } - // non-proportional CORNER RESIZE (normal case) - else { - // scale node - sourceNode->scale_ = s->stored_status_->scale_ * corner_scaling; - // discretized scaling with ALT - if (UserInterface::manager().altModifier()) { - sourceNode->scale_.x = ROUND(sourceNode->scale_.x, 10.f); - sourceNode->scale_.y = ROUND(sourceNode->scale_.y, 10.f); - corner_scaling = sourceNode->scale_ / s->stored_status_->scale_; - } - } - // transform source center (in corner reference frame) - center = glm::scale(glm::identity(), corner_scaling) * center; + + // Apply scaling to the source + sourceNode->scale_ = s->stored_status_->scale_ * glm::vec3(corner_scaling, 1.f); + + // + // Adjust translation + // + // The center of the source in CORNER reference frame + glm::vec4 corner_center = glm::vec4( corner, 0.f, 1.f); + // scale center of source in CORNER reference frame + corner_center = glm::scale(glm::identity(), glm::vec3(corner_scaling, 1.f)) * corner_center; // convert center back into scene reference frame - center = corner_to_scene_transform * center; - // apply to node - sourceNode->translation_ = glm::vec3(center); + corner_center = corner_to_scene_transform * corner_center; + // Apply scaling to the source + sourceNode->translation_ = glm::vec3(corner_center); + // show cursor depending on diagonal (corner picked) - T = glm::rotate(glm::identity(), s->stored_status_->rotation_.z, glm::vec3(0.f, 0.f, 1.f)); + glm::mat4 T = glm::rotate(glm::identity(), s->stored_status_->rotation_.z, glm::vec3(0.f, 0.f, 1.f)); T = glm::scale(T, s->stored_status_->scale_); corner = T * glm::vec4( corner, 0.f, 0.f ); ret.type = corner.x * corner.y > 0.f ? Cursor_ResizeNESW : Cursor_ResizeNWSE; info << "Size " << std::fixed << std::setprecision(3) << sourceNode->scale_.x; info << " x " << sourceNode->scale_.y; - } // picking on the BORDER RESIZING handles left or right else if ( pick.first == s->handles_[mode_][Handles::RESIZE_H] ) { @@ -780,29 +865,42 @@ View::Cursor GeometryView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::p s->handles_[mode_][Handles::MENU]->visible_ = false; // inform on which corner should be overlayed (opposite) s->handles_[mode_][Handles::RESIZE_H]->overlayActiveCorner(-corner); - // SHIFT: HORIZONTAL SCALE to restore source aspect ratio + + // + // Manipulate the handle in the SCENE coordinates to apply grid snap + // + glm::vec4 handle = corner_to_scene_transform * glm::vec4(corner * 2.f, 0.f, 1.f ); + // move the corner we hold by the mouse translation (in scene reference frame) + handle = glm::translate(glm::identity(), scene_to - scene_from) * handle; + // snap handle coordinates to grid (if active) + if ( grid->active() ) + handle = grid->snap(handle); + // Compute coordinates coordinates back in CORNER reference frame + handle = scene_to_corner_transform * handle; + // The scaling factor is computed by dividing new handle coordinates with the ones before transform + glm::vec2 corner_scaling = glm::vec2(handle.x, 1.f) / glm::vec2(corner.x * 2.f, 1.f); + + // Apply scaling to the source + sourceNode->scale_ = s->stored_status_->scale_ * glm::vec3(corner_scaling, 1.f); + + // SHIFT: restore previous aspect ratio if (UserInterface::manager().shiftModifier()) { - sourceNode->scale_.x = ABS(sourceNode->scale_.y) * SIGN(sourceNode->scale_.x); - corner_scaling = sourceNode->scale_ / s->stored_status_->scale_; + float ar = s->stored_status_->scale_.y / s->stored_status_->scale_.x; + sourceNode->scale_.y = ar * sourceNode->scale_.x; } - // HORIZONTAL RESIZE (normal case) - else { - // x scale only - corner_scaling = glm::vec3(corner_scaling.x, 1.f, 1.f); - // scale node - sourceNode->scale_ = s->stored_status_->scale_ * corner_scaling; - // POST-CORRECTION ; discretized scaling with ALT - if (UserInterface::manager().altModifier()) { - sourceNode->scale_.x = ROUND(sourceNode->scale_.x, 10.f); - corner_scaling = sourceNode->scale_ / s->stored_status_->scale_; - } - } - // transform source center (in corner reference frame) - center = glm::scale(glm::identity(), corner_scaling) * center; + + // + // Adjust translation + // + // The center of the source in CORNER reference frame + glm::vec4 corner_center = glm::vec4( corner, 0.f, 1.f); + // scale center of source in CORNER reference frame + corner_center = glm::scale(glm::identity(), glm::vec3(corner_scaling, 1.f)) * corner_center; // convert center back into scene reference frame - center = corner_to_scene_transform * center; - // apply to node - sourceNode->translation_ = glm::vec3(center); + corner_center = corner_to_scene_transform * corner_center; + // Apply scaling to the source + sourceNode->translation_ = glm::vec3(corner_center); + // show cursor depending on angle float c = tan(sourceNode->rotation_.z); ret.type = ABS(c) > 1.f ? Cursor_ResizeNS : Cursor_ResizeEW; @@ -821,29 +919,42 @@ View::Cursor GeometryView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::p s->handles_[mode_][Handles::MENU]->visible_ = false; // inform on which corner should be overlayed (opposite) s->handles_[mode_][Handles::RESIZE_V]->overlayActiveCorner(-corner); - // SHIFT: VERTICAL SCALE to restore source aspect ratio + + // + // Manipulate the handle in the SCENE coordinates to apply grid snap + // + glm::vec4 handle = corner_to_scene_transform * glm::vec4(corner * 2.f, 0.f, 1.f ); + // move the corner we hold by the mouse translation (in scene reference frame) + handle = glm::translate(glm::identity(), scene_to - scene_from) * handle; + // snap handle coordinates to grid (if active) + if ( grid->active() ) + handle = grid->snap(handle); + // Compute coordinates coordinates back in CORNER reference frame + handle = scene_to_corner_transform * handle; + // The scaling factor is computed by dividing new handle coordinates with the ones before transform + glm::vec2 corner_scaling = glm::vec2(1.f, handle.y) / glm::vec2(1.f, corner.y * 2.f); + + // Apply scaling to the source + sourceNode->scale_ = s->stored_status_->scale_ * glm::vec3(corner_scaling, 1.f); + + // SHIFT: restore previous aspect ratio if (UserInterface::manager().shiftModifier()) { - sourceNode->scale_.y = ABS(sourceNode->scale_.x) * SIGN(sourceNode->scale_.y); - corner_scaling = sourceNode->scale_ / s->stored_status_->scale_; + float ar = s->stored_status_->scale_.x / s->stored_status_->scale_.y; + sourceNode->scale_.x = ar * sourceNode->scale_.y; } - // VERTICAL RESIZE (normal case) - else { - // y scale only - corner_scaling = glm::vec3(1.f, corner_scaling.y, 1.f); - // scale node - sourceNode->scale_ = s->stored_status_->scale_ * corner_scaling; - // POST-CORRECTION ; discretized scaling with ALT - if (UserInterface::manager().altModifier()) { - sourceNode->scale_.y = ROUND(sourceNode->scale_.y, 10.f); - corner_scaling = sourceNode->scale_ / s->stored_status_->scale_; - } - } - // transform source center (in corner reference frame) - center = glm::scale(glm::identity(), corner_scaling) * center; + + // + // Adjust translation + // + // The center of the source in CORNER reference frame + glm::vec4 corner_center = glm::vec4( corner, 0.f, 1.f); + // scale center of source in CORNER reference frame + corner_center = glm::scale(glm::identity(), glm::vec3(corner_scaling, 1.f)) * corner_center; // convert center back into scene reference frame - center = corner_to_scene_transform * center; - // apply to node - sourceNode->translation_ = glm::vec3(center); + corner_center = corner_to_scene_transform * corner_center; + // Apply scaling to the source + sourceNode->translation_ = glm::vec3(corner_center); + // show cursor depending on angle float c = tan(sourceNode->rotation_.z); ret.type = ABS(c) > 1.f ? Cursor_ResizeEW : Cursor_ResizeNS; @@ -868,22 +979,32 @@ View::Cursor GeometryView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::p overlay_scaling_->translation_.y = s->stored_status_->translation_.y; overlay_scaling_->rotation_.z = s->stored_status_->rotation_.z; overlay_scaling_->update(0); - // PROPORTIONAL ONLY + + // + // Manipulate the scaling handle in the SCENE coordinates to apply grid snap + // + glm::vec3 handle = glm::vec3( glm::round(pick.second), 0.f); + // Compute handle coordinates into SCENE reference frame + handle = source_to_scene_transform * glm::vec4( handle, 1.f ); + // move the handle we hold by the mouse translation (in scene reference frame) + handle = glm::translate(glm::identity(), scene_to - scene_from) * glm::vec4( handle, 1.f ); + // snap handle coordinates to grid (if active) + if ( grid->active() ) + handle = grid->snap(handle); + // Compute handle coordinates back in SOURCE reference frame + handle = scene_to_source_transform * glm::vec4( handle, 1.f ); + // The scaling factor is computed by dividing new handle coordinates with the ones before transform + glm::vec2 handle_scaling = glm::vec2(handle) / glm::round(pick.second); + + // proportional SCALING with SHIFT if (UserInterface::manager().shiftModifier()) { - float factor = glm::length( glm::vec2( source_to ) ) / glm::length( glm::vec2( source_from ) ); - source_scaling = glm::vec3(factor, factor, 1.f); + handle_scaling = glm::vec2(glm::compMax(handle_scaling)); overlay_scaling_cross_->visible_ = true; overlay_scaling_cross_->copyTransform(overlay_scaling_); } - // apply center scaling - sourceNode->scale_ = s->stored_status_->scale_ * source_scaling; - // POST-CORRECTION ; discretized scaling with ALT - if (UserInterface::manager().altModifier()) { - sourceNode->scale_.x = ROUND(sourceNode->scale_.x, 10.f); - sourceNode->scale_.y = ROUND(sourceNode->scale_.y, 10.f); - overlay_scaling_grid_->visible_ = true; - overlay_scaling_grid_->copyTransform(overlay_scaling_); - } + // Apply scaling to the source + sourceNode->scale_ = s->stored_status_->scale_ * glm::vec3(handle_scaling, 1.f); + // show cursor depending on diagonal corner = glm::sign(sourceNode->scale_); ret.type = (corner.x * corner.y) > 0.f ? Cursor_ResizeNWSE : Cursor_ResizeNESW; @@ -908,22 +1029,35 @@ View::Cursor GeometryView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::p overlay_crop_->rotation_.z = s->stored_status_->rotation_.z; overlay_crop_->update(0); overlay_crop_->visible_ = true; - // PROPORTIONAL ONLY + + // + // Manipulate the scaling handle in the SCENE coordinates to apply grid snap + // + glm::vec3 handle = glm::vec3( glm::round(pick.second), 0.f); + // Compute handle coordinates into SCENE reference frame + handle = source_to_scene_transform * glm::vec4( handle, 1.f ); + // move the handle we hold by the mouse translation (in scene reference frame) + handle = glm::translate(glm::identity(), scene_to - scene_from) * glm::vec4( handle, 1.f ); + // snap handle coordinates to grid (if active) + if ( grid->active() ) + handle = grid->snap(handle); + // Compute handle coordinates back in SOURCE reference frame + handle = scene_to_source_transform * glm::vec4( handle, 1.f ); + // The scaling factor is computed by dividing new handle coordinates with the ones before transform + glm::vec2 handle_scaling = glm::vec2(handle)/ glm::round(pick.second); + + // proportional SCALING with SHIFT if (UserInterface::manager().shiftModifier()) { - float factor = glm::length( glm::vec2( source_to ) ) / glm::length( glm::vec2( source_from ) ); - source_scaling = glm::vec3(factor, factor, 1.f); - } - // calculate crop of framebuffer - sourceNode->crop_ = s->stored_status_->crop_ * source_scaling; - // POST-CORRECTION ; discretized crop with ALT - if (UserInterface::manager().altModifier()) { - sourceNode->crop_.x = ROUND(sourceNode->crop_.x, 10.f); - sourceNode->crop_.y = ROUND(sourceNode->crop_.y, 10.f); + handle_scaling = glm::vec2(glm::compMax(handle_scaling)); } + + // Apply scaling to the CROP node of source + sourceNode->crop_ = s->stored_status_->crop_ * glm::vec3(handle_scaling, 1.f); // CLAMP crop values sourceNode->crop_.x = CLAMP(sourceNode->crop_.x, 0.1f, 1.f); sourceNode->crop_.y = CLAMP(sourceNode->crop_.y, 0.1f, 1.f); - // apply center scaling + + // Apply scaling to the source s->frame()->setProjectionArea( glm::vec2(sourceNode->crop_) ); sourceNode->scale_ = s->stored_status_->scale_ * (sourceNode->crop_ / s->stored_status_->crop_); // show cursor depending on diagonal @@ -947,70 +1081,109 @@ View::Cursor GeometryView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::p overlay_rotation_->translation_.x = s->stored_status_->translation_.x; overlay_rotation_->translation_.y = s->stored_status_->translation_.y; overlay_rotation_->update(0); - overlay_rotation_fix_->visible_ = true; + overlay_rotation_fix_->visible_ = false; overlay_rotation_fix_->copyTransform(overlay_rotation_); overlay_rotation_clock_->visible_ = false; - // rotation center to center of source (disregarding scale) - glm::mat4 M = glm::translate(glm::identity(), s->stored_status_->translation_); - source_from = glm::inverse(M) * glm::vec4( scene_from, 1.f ); - source_to = glm::inverse(M) * glm::vec4( scene_to, 1.f ); - // compute rotation angle - float angle = glm::orientedAngle( glm::normalize(glm::vec2(source_from)), glm::normalize(glm::vec2(source_to))); - // apply rotation on Z axis - sourceNode->rotation_ = s->stored_status_->rotation_ + glm::vec3(0.f, 0.f, angle); - - // POST-CORRECTION ; discretized rotation with ALT - int degrees = int( glm::degrees(sourceNode->rotation_.z) ); - if (UserInterface::manager().altModifier()) { - degrees = (degrees / 10) * 10; - sourceNode->rotation_.z = glm::radians( float(degrees) ); - overlay_rotation_clock_->visible_ = true; - overlay_rotation_clock_->copyTransform(overlay_rotation_); - info << "Angle " << degrees << UNICODE_DEGREE; - } - else - info << "Angle " << std::fixed << std::setprecision(1) << glm::degrees(sourceNode->rotation_.z) << UNICODE_DEGREE; - overlay_rotation_clock_hand_->visible_ = true; overlay_rotation_clock_hand_->translation_.x = s->stored_status_->translation_.x; overlay_rotation_clock_hand_->translation_.y = s->stored_status_->translation_.y; + + // prepare variables + const float diagonal = glm::length( glm::vec2(s->frame()->aspectRatio() * s->stored_status_->scale_.x, s->stored_status_->scale_.y)); + glm::vec2 handle_polar = glm::vec2(diagonal, 0.f); + + // rotation center to center of source (disregarding scale) + glm::mat4 M = glm::translate(glm::identity(), s->stored_status_->translation_); + glm::vec3 source_from = glm::inverse(M) * glm::vec4( scene_from, 1.f ); + glm::vec3 source_to = glm::inverse(M) * glm::vec4( scene_to, 1.f ); + + // compute rotation angle on Z axis + float angle = glm::orientedAngle( glm::normalize(glm::vec2(source_from)), glm::normalize(glm::vec2(source_to))); + handle_polar.y = s->stored_status_->rotation_.z + angle; + info << "Angle " << std::fixed << std::setprecision(1) << glm::degrees(sourceNode->rotation_.z) << UNICODE_DEGREE; + + // compute scaling of diagonal to reach new coordinates + handle_polar.x *= glm::length( glm::vec2(source_to) ) / glm::length( glm::vec2(source_from) ); + + // snap polar coordiantes (diagonal lenght, angle) + if ( grid->active() ) { + handle_polar = glm::round( handle_polar / grid->step() ) * grid->step(); + // prevent null size + handle_polar.x = glm::max( grid->step().x, handle_polar.x ); + } + + // Cancel scaling diagonal with SHIFT + if (UserInterface::manager().shiftModifier()) { + handle_polar.x = diagonal; + overlay_rotation_fix_->visible_ = true; + } else { + info << std::endl << " Size " << std::fixed << std::setprecision(3) << sourceNode->scale_.x; + info << " x " << sourceNode->scale_.y ; + } + + // apply after snap + sourceNode->rotation_ = glm::vec3(0.f, 0.f, handle_polar.y); + handle_polar.x /= diagonal ; + sourceNode->scale_ = s->stored_status_->scale_ * glm::vec3(handle_polar.x, handle_polar.x, 1.f); + + // update overlay overlay_rotation_clock_hand_->rotation_.z = sourceNode->rotation_.z; overlay_rotation_clock_hand_->update(0); // show cursor for rotation ret.type = Cursor_Hand; - // + SHIFT = no scaling / NORMAL = with scaling - if (!UserInterface::manager().shiftModifier()) { - // compute scaling to match cursor - float factor = glm::length( glm::vec2( source_to ) ) / glm::length( glm::vec2( source_from ) ); - source_scaling = glm::vec3(factor, factor, 1.f); - // apply center scaling - sourceNode->scale_ = s->stored_status_->scale_ * source_scaling; - info << std::endl << " Size " << std::fixed << std::setprecision(3) << sourceNode->scale_.x; - info << " x " << sourceNode->scale_.y ; - overlay_rotation_fix_->visible_ = false; - } } // picking anywhere but on a handle: user wants to move the source else { - ret.type = Cursor_ResizeAll; - sourceNode->translation_ = s->stored_status_->translation_ + scene_translation; - // discretized translation with ALT - if (UserInterface::manager().altModifier()) { - sourceNode->translation_.x = ROUND(sourceNode->translation_.x, 10.f); - sourceNode->translation_.y = ROUND(sourceNode->translation_.y, 10.f); - // Show grid overlay for POSITION - overlay_position_cross_->visible_ = true; - overlay_position_cross_->translation_.x = sourceNode->translation_.x; - overlay_position_cross_->translation_.y = sourceNode->translation_.y; - overlay_position_cross_->update(0); + + // Default is to grab the center (0,0) of the source + glm::vec3 handle(0.f); + glm::vec3 offset(0.f); + + // Snap corner with SHIFT + if (UserInterface::manager().shiftModifier()) { + // get corner closest representative of the quadrant of the picking point + handle = glm::vec3( glm::sign(pick.second), 0.f); + // remember the offset for adjustment of translation to this corner + offset = source_to_scene_transform * glm::vec4(handle, 0.f); } + + // Compute target coordinates of manipulated handle into SCENE reference frame + glm::vec3 source_target = source_to_scene_transform * glm::vec4(handle, 1.f); + + // apply translation of target in SCENE + source_target = glm::translate(glm::identity(), scene_to - scene_from) * glm::vec4(source_target, 1.f); + + // snap coordinates to grid (if active) + if ( grid->active() ) + // snap coordinate in scene + source_target = grid->snap(source_target); + + // Apply translation to the source + sourceNode->translation_ = source_target - offset; + + // + // grab all others in selection + // + // compute effective translation of current source s + source_target = sourceNode->translation_ - s->stored_status_->translation_; + // loop over selection + for (auto it = Mixer::selection().begin(); it != Mixer::selection().end(); ++it) { + if ( *it != s && !(*it)->locked() ) { + // translate and request update + (*it)->group(mode_)->translation_ = (*it)->stored_status_->translation_ + source_target; + (*it)->touch(); + } + } + // Show center overlay for POSITION overlay_position_->visible_ = true; overlay_position_->translation_.x = sourceNode->translation_.x; overlay_position_->translation_.y = sourceNode->translation_.y; overlay_position_->update(0); + // Show move cursor + ret.type = Cursor_ResizeAll; info << "Position " << std::fixed << std::setprecision(3) << sourceNode->translation_.x; info << ", " << sourceNode->translation_.y ; } @@ -1065,9 +1238,13 @@ void GeometryView::terminate(bool force) (*sit)->handles_[mode_][Handles::ROTATE]->visible_ = true; (*sit)->handles_[mode_][Handles::CROP]->visible_ = true; (*sit)->handles_[mode_][Handles::MENU]->visible_ = true; + } overlay_selection_active_ = false; + + // reset grid + adaptGridToSource(); } void GeometryView::arrow (glm::vec2 movement) @@ -1110,7 +1287,8 @@ void GeometryView::arrow (glm::vec2 movement) else break; } - else { + else + { // normal case: dest += delta dest_translation += gl_delta * ARROWS_MOVEMENT_FACTOR * dt_; accumulator = 0.f; diff --git a/src/GeometryView.h b/src/GeometryView.h index 0ebe789..2ece3ba 100644 --- a/src/GeometryView.h +++ b/src/GeometryView.h @@ -3,6 +3,7 @@ #include "View.h" + class GeometryView : public View { public: @@ -43,6 +44,10 @@ private: Handles *overlay_selection_rotate_; void applySelectionTransform(glm::mat4 M); + + void adaptGridToSource(Source *s = nullptr, Node *picked = nullptr); + TranslationGrid *translation_grid_; + RotationGrid *rotation_grid_; }; diff --git a/src/Grid.cpp b/src/Grid.cpp new file mode 100644 index 0000000..a6066c9 --- /dev/null +++ b/src/Grid.cpp @@ -0,0 +1,154 @@ +/* + * This file is part of vimix - video live mixer + * + * **Copyright** (C) 2019-2023 Bruno Herbelin + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +**/ + +#include + +#include "Primitives.h" +#include "DrawVisitor.h" + +#include "Grid.h" + + +float Grid::ortho_units_[] = { 1.f / 20.f, 1.f / 10.f, 1.f / 5.f, 1.f / 2.f, 1.f }; +float Grid::polar_units_[] = { M_PI / 18.f, M_PI / 12.f, M_PI / 6.f, M_PI / 4.f, M_PI / 2.f }; + +Grid::Grid(Group *parent, Shapes s) : active_(false), shape_(s), unit_(UNIT_DEFAULT), + parent_(parent), root_(new Group) +{ + +} + +glm::vec2 Grid::step() const { + + if ( shape_ != GRID_POLAR ) + return glm::vec2(root_->scale_.x, 1.f) * glm::vec2(ortho_units_[unit_]) ; + else + return glm::vec2(ortho_units_[unit_] * root_->scale_.x, polar_units_[unit_]); +} + +glm::vec2 Grid::snap(glm::vec2 in) { + + if ( shape_ != GRID_POLAR ) { + // convert to grid coordinate frame + glm::vec3 coord = glm::vec3(in, 0.f); + const glm::mat4 G = GlmToolkit::transform(root_->translation_, + root_->rotation_, + glm::vec3(1.f)); + coord = glm::inverse(G) * glm::vec4(coord, 1.f); + coord = glm::vec3( glm::round( glm::vec2(coord) / step() ) * step(), 0.f); + coord = G * glm::vec4(coord, 1.f); + return glm::vec2(coord); + } + else { + // convert orthographic to polar coordinates + glm::vec2 ortho = in; + glm::vec2 polar; + polar.x = glm::length( glm::vec2(ortho) ); + polar.y = -glm::orientedAngle( glm::normalize(in), glm::normalize(glm::vec2(1.f, 0.f))); + + // snap polar to polar grid + polar = glm::round( polar / step() ) * step();; + + // convert back to ortho coordinates + ortho.x = polar.x * cos(polar.y); + ortho.y = polar.x * sin(polar.y); + return ortho; + } +} + +void Grid::setColor (const glm::vec4 &c) +{ + ColorVisitor color(c); + root_->accept(color); +} + +TranslationGrid::TranslationGrid(Group *parent) : Grid(parent) +{ + root_ = new Group; + parent_->attach(root_); + + HLine *xaxis= new HLine(12.f); + xaxis->scale_.x = 0.1f; + root_->attach(xaxis); + + VLine *yaxis= new VLine(12.f); + yaxis->scale_.y = 0.1f; + root_->attach(yaxis); + + ortho_grids_ = new Switch; + ortho_grids_->attach(new LineGrid(112, Grid::ortho_units_[UNIT_PRECISE], 2.f)); + ortho_grids_->attach(new LineGrid(56, Grid::ortho_units_[UNIT_SMALL], 2.f)); + ortho_grids_->attach(new LineGrid(28, Grid::ortho_units_[UNIT_DEFAULT], 2.f)); + ortho_grids_->attach(new LineGrid(14, Grid::ortho_units_[UNIT_LARGE], 2.f)); + ortho_grids_->attach(new LineGrid( 7, Grid::ortho_units_[UNIT_ONE], 2.f)); + root_->attach(ortho_grids_); + + // not visible at init + setColor( glm::vec4(0.f) ); +} + +Group *TranslationGrid::root () +{ + //adjust the gridnodethe unit scale + ortho_grids_->setActive(unit_); + + // return the node to draw + return root_; +} + +RotationGrid::RotationGrid(Group *parent) : Grid(parent, Grid::GRID_POLAR) +{ + root_ = new Group; + parent_->attach(root_); + + polar_grids_ = new Switch; + polar_grids_->attach(new LineCircleGrid(Grid::polar_units_[UNIT_PRECISE], 50, + Grid::ortho_units_[UNIT_PRECISE], 0.5f)); + polar_grids_->attach(new LineCircleGrid(Grid::polar_units_[UNIT_SMALL], 30, + Grid::ortho_units_[UNIT_SMALL], 0.5f)); + polar_grids_->attach(new LineCircleGrid(Grid::polar_units_[UNIT_DEFAULT], 15, + Grid::ortho_units_[UNIT_DEFAULT], 0.5f)); + polar_grids_->attach(new LineCircleGrid(Grid::polar_units_[UNIT_LARGE], 6, + Grid::ortho_units_[UNIT_LARGE], 0.5f)); + polar_grids_->attach(new LineCircleGrid(Grid::polar_units_[UNIT_ONE], 3, + Grid::ortho_units_[UNIT_ONE], 0.5f)); + root_->attach(polar_grids_); + + // not visible at init + setColor( glm::vec4(0.f) ); +} + +Group *RotationGrid::root () +{ + polar_grids_->setActive(unit_); + + // return the node to draw + return root_; +} + +//glm::vec2 GeometryGrid::approx(const glm::vec2 &in ) const +//{ +// glm::vec2 out; + +// glm::vec3 coord = Rendering::manager().unProject( in, root_->transform_); +// coord = glm::round( coord / unit()) * unit(); +// out = Rendering::manager().project( coord, root_->transform_, false); + +// return out; +//} diff --git a/src/Grid.h b/src/Grid.h new file mode 100644 index 0000000..532d88f --- /dev/null +++ b/src/Grid.h @@ -0,0 +1,89 @@ +#ifndef GRID_H +#define GRID_H + +#include + +#include "Scene.h" + +class Grid +{ + +public: + typedef enum { + GRID_ORTHO = 0, + GRID_POLAR + } Shapes; + + Grid(Group *parent, Shapes s = GRID_ORTHO); + virtual ~Grid() {} + + // if active, the view will use it to approximate coordinates + inline bool active() const { return active_; } + inline void setActive(bool on) { active_ = on; } + + // possible grid scales and shapes + typedef enum { + UNIT_PRECISE = 0, + UNIT_SMALL = 1, + UNIT_DEFAULT = 2, + UNIT_LARGE = 3, + UNIT_ONE = 4 + } Units; + inline void setUnit(Units u) { unit_ = u; } + + inline float aspectRatio() const { + return root_->scale_.x; + } + inline void setAspectRatio(float ar) { + root_->scale_.x = ar; + } + + // unit of the grid, i.e. fraction between lines + glm::vec2 step() const; + glm::vec2 snap(glm::vec2 in); + + inline glm::vec3 snap(glm::vec3 in) { + return glm::vec3( snap(glm::vec2(in)), in.z); + } + inline glm::vec4 snap(glm::vec4 in) { + return glm::vec4( snap(glm::vec2(in)), in.z, in.w); + } + + // Node to render in scene + virtual Group *root () { return root_; } + virtual void setColor (const glm::vec4 &c); + +// get point on grid approximating the given coordinates +// virtual glm::vec2 approx(const glm::vec2 &p) const { return p; } + + // get the center point of the grid +// virtual glm::vec2 center() const { return glm::vec2(0.f); } + +protected: + bool active_; + Shapes shape_; + Units unit_; + Group *parent_; + Group *root_; + static float ortho_units_[5]; + static float polar_units_[5]; +}; + +class TranslationGrid : public Grid +{ + Switch *ortho_grids_; +public: + TranslationGrid(Group *parent); + Group *root () override; +}; + +class RotationGrid : public Grid +{ + Switch *polar_grids_; +public: + RotationGrid(Group *parent); + Group *root () override; +}; + + +#endif // GRID_H diff --git a/src/ImGuiVisitor.cpp b/src/ImGuiVisitor.cpp index a8ac2ef..ac61755 100644 --- a/src/ImGuiVisitor.cpp +++ b/src/ImGuiVisitor.cpp @@ -810,7 +810,7 @@ void ImGuiVisitor::visit (SessionFileSource& s) std::ostringstream oss; int f = 100 - int(s.session()->fading() * 100.f); ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - if (ImGui::SliderInt("##Fading", &f, 0, 100, f > 99 ? "None" : "%d %%") ) + if (ImGui::SliderInt("##Fading", &f, 0, 100, f > 99 ? ICON_FA_ADJUST " None" : ICON_FA_ADJUST " %d %%") ) s.session()->setFadingTarget( float(100 - f) * 0.01f ); if (ImGui::IsItemDeactivatedAfterEdit()){ oss << s.name() << ": Fading " << f << " %"; diff --git a/src/LayerView.cpp b/src/LayerView.cpp index 1ec5779..cc11a73 100644 --- a/src/LayerView.cpp +++ b/src/LayerView.cpp @@ -78,11 +78,17 @@ LayerView::LayerView() : View(LAYER), aspect_ratio(1.f) persp_right_->translation_.x = 1.f; scene.bg()->attach(persp_right_); + // replace grid with appropriate one + if (grid) delete grid; + grid = new LayerGrid(scene.root()); } void LayerView::draw() { + // Display grid + grid->root()->visible_ = (grid->active() && current_action_ongoing_); + View::draw(); // initialize the verification of the selection @@ -187,12 +193,16 @@ void LayerView::update(float dt) float s = CLAMP(scene.root()->scale_.x, LAYER_MIN_SCALE, LAYER_MAX_SCALE); scene.root()->scale_.x = s; scene.root()->scale_.y = s; + + // change grid color + ImVec4 c = ImGuiToolkit::HighlightColor(); + grid->setColor( glm::vec4(c.x, c.y, c.z, 0.3) ); } if (Mixer::manager().view() == this ) { // update the selection overlay - ImVec4 c = ImGuiToolkit::HighlightColor(); + const ImVec4 c = ImGuiToolkit::HighlightColor(); updateSelectionOverlay(glm::vec4(c.x, c.y, c.z, c.w)); } @@ -318,13 +328,26 @@ View::Cursor LayerView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::pair // compute delta translation glm::vec3 dest_translation = s->stored_status_->translation_ + gl_Position_to - gl_Position_from; - // discretized translation with ALT - if (UserInterface::manager().altModifier()) - dest_translation.x = ROUND(dest_translation.x, 5.f); + // snap to grid (polar) + if (grid->active()) + dest_translation = grid->snap(dest_translation * 0.5f) * 2.f; // apply change float d = setDepth( s, MAX( -dest_translation.x, 0.f) ); + // + // grab all others in selection + // + // compute effective depth translation of current source s + float dp = s->group(mode_)->translation_.z - s->stored_status_->translation_.z; + // loop over selection + for (auto it = Mixer::selection().begin(); it != Mixer::selection().end(); ++it) { + if ( *it != s && !(*it)->locked() ) { + // set depth and request update + setDepth( *it, (*it)->stored_status_->translation_.z + dp); + } + } + // store action in history std::ostringstream info; info << "Depth " << std::fixed << std::setprecision(2) << d << " "; @@ -437,3 +460,59 @@ void LayerView::updateSelectionOverlay(glm::vec4 color) overlay_selection_frame_->scale_ = glm::vec3(1.f) + glm::vec3(0.07f, 0.07f, 1.f) / overlay_selection_->scale_; } } + +LayerGrid::LayerGrid(Group *parent) : Grid(parent) +{ + root_ = new Group; + root_->visible_ = false; + parent_->attach(root_); + + // create custom grids specific for layers in diagonal + perspective_grids_ = new Switch; + root_->attach(perspective_grids_); + + // Generate groups for all units + for (uint u = UNIT_PRECISE; u <= UNIT_ONE; u = u + 1) { + Group *g = new Group; + float d = MIN_DEPTH; + // Fill background + for (; d < LAYER_BACKGROUND ; d += Grid::ortho_units_[u] * 2.f) { + HLine *l = new HLine(3.f); + l->translation_.x = -d +1.f; + l->translation_.y = -d / LAYER_PERSPECTIVE - 1.f; + l->scale_.x = 3.5f; + g->attach(l); + } + // Fill workspace + for (; d < LAYER_FOREGROUND ; d += Grid::ortho_units_[u] * 2.f) { + HLine *l = new HLine(3.f); + l->translation_.x = -d +1.f; + l->translation_.y = -d / LAYER_PERSPECTIVE - 1.15f; + l->scale_.x = 3.5f; + g->attach(l); + } + // Fill foreground + for (; d < MAX_DEPTH ; d += Grid::ortho_units_[u] * 2.f) { + HLine *l = new HLine(3.f); + l->translation_.x = -d +1.f; + l->translation_.y = -d / LAYER_PERSPECTIVE - 1.3f; + l->scale_.x = 3.5f; + g->attach(l); + } + // add this group to the grids + perspective_grids_->attach(g); + } + + // not visible at init +// setColor( glm::vec4(0.f) ); +} + +Group *LayerGrid::root () +{ + //adjust the grid to the unit scale + perspective_grids_->setActive(unit_); + + // return the node to draw + return root_; +} + diff --git a/src/LayerView.h b/src/LayerView.h index 7d43fb6..1de48b9 100644 --- a/src/LayerView.h +++ b/src/LayerView.h @@ -3,6 +3,14 @@ #include "View.h" +class LayerGrid : public Grid +{ + Switch *perspective_grids_; +public: + LayerGrid(Group *parent); + Group *root () override; +}; + class LayerView : public View { public: diff --git a/src/MixingView.cpp b/src/MixingView.cpp index b5f95fc..516db83 100644 --- a/src/MixingView.cpp +++ b/src/MixingView.cpp @@ -154,12 +154,9 @@ MixingView::MixingView() : View(MIXING), limbo_scale_(MIXING_MIN_THRESHOLD) tmp->shader()->color = glm::vec4( COLOR_CIRCLE_ARROW, 0.1f ); slider_arrows_->attach(tmp); -// stashCircle_ = new Disk(); -// stashCircle_->scale_ = glm::vec3(0.5f, 0.5f, 1.f); -// stashCircle_->translation_ = glm::vec3(2.f, -1.0f, 0.f); -// stashCircle_->color = glm::vec4( COLOR_STASH_CIRCLE, 0.6f ); -// scene.bg()->attach(stashCircle_); - + // replace grid with appropriate one + if (grid) delete grid; + grid = new MixingGrid(scene.root()); } @@ -169,6 +166,9 @@ void MixingView::draw() if (mixingCircle_->texture() == 0) mixingCircle_->setTexture(textureMixingQuadratic()); + // Display grid + grid->root()->visible_ = (grid->active() && current_action_ongoing_); + // temporarily force shaders to use opacity blending for rendering icons Shader::force_blending_opacity = true; // draw scene of this view @@ -378,6 +378,10 @@ void MixingView::update(float dt) float s = CLAMP(scene.root()->scale_.x, MIXING_MIN_SCALE, MIXING_MAX_SCALE); scene.root()->scale_.x = s; scene.root()->scale_.y = s; + + // change grid color + const ImVec4 c = ImGuiToolkit::HighlightColor(); + grid->setColor( glm::vec4(c.x, c.y, c.z, 0.3) ); } // the current view is the mixing view @@ -396,7 +400,7 @@ void MixingView::update(float dt) mixingCircle_->shader()->color = glm::vec4(f, f, f, 1.f); // update the selection overlay - ImVec4 c = ImGuiToolkit::HighlightColor(); + const ImVec4 c = ImGuiToolkit::HighlightColor(); updateSelectionOverlay(glm::vec4(c.x, c.y, c.z, c.w)); } @@ -495,6 +499,10 @@ View::Cursor MixingView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::pai // No source is given if (!s) { + // snap to grid (polar) + if (grid->active()) + gl_Position_to = grid->snap(gl_Position_to); + // if interaction with slider if (pick.first == slider_) { @@ -544,8 +552,15 @@ View::Cursor MixingView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::pai // // Interaction with source // - // compute delta translation - s->group(mode_)->translation_ = s->stored_status_->translation_ + gl_Position_to - gl_Position_from; + // compute position after translation + glm::vec3 newpos = s->stored_status_->translation_ + gl_Position_to - gl_Position_from; + + // snap to grid (polar) + if (grid->active()) + newpos = grid->snap(newpos); + + // apply translation + s->group(mode_)->translation_ = newpos; // manage mixing group if (s->mixinggroup_ != nullptr ) { @@ -563,22 +578,19 @@ View::Cursor MixingView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::pai // request update s->touch(); -// // trying to enter stash -// if ( glm::distance( glm::vec2(s->group(mode_)->translation_), glm::vec2(stashCircle_->translation_)) < stashCircle_->scale_.x) { - -// // refuse to put an active source in stash -// if (s->active()) -// s->group(mode_)->translation_ = s->stored_status_->translation_; -// else { -// Mixer::manager().conceal(s); -// s->group(mode_)->scale_ = glm::vec3(MIXING_ICON_SCALE) - glm::vec3(0.1f, 0.1f, 0.f); -// } -// } -// else if ( Mixer::manager().concealed(s) ) { -// Mixer::manager().uncover(s); -// s->group(mode_)->scale_ = glm::vec3(MIXING_ICON_SCALE); -// } - + // + // grab all others from selection + // + // compute effective translation of current source s + newpos = s->group(mode_)->translation_ - s->stored_status_->translation_; + // loop over selection + for (auto it = Mixer::selection().begin(); it != Mixer::selection().end(); ++it) { + if ( *it != s && !(*it)->locked() && ( s->mixinggroup_ == nullptr || !s->mixinggroup_->contains(*it)) ) { + // translate and request update + (*it)->group(mode_)->translation_ = (*it)->stored_status_->translation_ + newpos; + (*it)->touch(); + } + } std::ostringstream info; if (s->active()) { @@ -591,6 +603,7 @@ View::Cursor MixingView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::pai // store action in history current_action_ = s->name() + ": " + info.str(); + // update cursor ret.info = info.str(); return ret; @@ -834,3 +847,56 @@ uint textureMixingQuadratic() } return texid; } + + +MixingGrid::MixingGrid(Group *parent) : Grid(parent, Grid::GRID_POLAR) +{ + root_ = new Group; + root_->visible_ = false; + parent_->attach(root_); + + polar_grids_ = new Switch; + polar_grids_->attach(new LineCircleGrid(Grid::polar_units_[UNIT_PRECISE], 50, + Grid::ortho_units_[UNIT_PRECISE], 0.5f)); + polar_grids_->attach(new LineCircleGrid(Grid::polar_units_[UNIT_SMALL], 30, + Grid::ortho_units_[UNIT_SMALL], 0.5f)); + polar_grids_->attach(new LineCircleGrid(Grid::polar_units_[UNIT_DEFAULT], 15, + Grid::ortho_units_[UNIT_DEFAULT], 0.5f)); + polar_grids_->attach(new LineCircleGrid(Grid::polar_units_[UNIT_LARGE], 6, + Grid::ortho_units_[UNIT_LARGE], 0.5f)); + polar_grids_->attach(new LineCircleGrid(Grid::polar_units_[UNIT_ONE], 3, + Grid::ortho_units_[UNIT_ONE], 0.5f)); + root_->attach(polar_grids_); + + // not visible at init + setColor( glm::vec4(0.f) ); +} + +Group *MixingGrid::root () +{ + //adjust the grid to the unit scale + polar_grids_->setActive(unit_); + + // return the node to draw + return root_; +} + + + + +// // trying to enter stash +// if ( glm::distance( glm::vec2(s->group(mode_)->translation_), glm::vec2(stashCircle_->translation_)) < stashCircle_->scale_.x) { + +// // refuse to put an active source in stash +// if (s->active()) +// s->group(mode_)->translation_ = s->stored_status_->translation_; +// else { +// Mixer::manager().conceal(s); +// s->group(mode_)->scale_ = glm::vec3(MIXING_ICON_SCALE) - glm::vec3(0.1f, 0.1f, 0.f); +// } +// } +// else if ( Mixer::manager().concealed(s) ) { +// Mixer::manager().uncover(s); +// s->group(mode_)->scale_ = glm::vec3(MIXING_ICON_SCALE); +// } + diff --git a/src/MixingView.h b/src/MixingView.h index aff6fbc..64257af 100644 --- a/src/MixingView.h +++ b/src/MixingView.h @@ -3,7 +3,14 @@ #include "View.h" -//class MixingGroup; +class MixingGrid : public Grid +{ + Switch *polar_grids_; + +public: + MixingGrid(Group *parent); + Group *root () override; +}; class MixingView : public View { @@ -37,7 +44,6 @@ private: Group *slider_arrows_; Disk *button_white_; Disk *button_black_; -// Disk *stashCircle_; Mesh *mixingCircle_; Mesh *circle_; Mesh *limbo_; diff --git a/src/MousePointer.cpp b/src/MousePointer.cpp index 0a5b586..30b0a50 100644 --- a/src/MousePointer.cpp +++ b/src/MousePointer.cpp @@ -21,23 +21,46 @@ #include "imgui.h" +#include "defines.h" #include "Metronome.h" +#include "View.h" +#include "Mixer.h" +//#include "RenderingManager.h" #include "MousePointer.h" std::vector< std::tuple > Pointer::Modes = { { ICON_POINTER_DEFAULT, std::string("Default") }, + { ICON_POINTER_GRID, std::string("Grid") }, { ICON_POINTER_LINEAR, std::string("Linear") }, { ICON_POINTER_SPRING, std::string("Spring") }, { ICON_POINTER_WIGGLY, std::string("Wiggly") }, { ICON_POINTER_METRONOME, std::string("Metronome") } }; +void PointerGrid::initiate(const glm::vec2&) +{ + Mixer::manager().view()->grid->setUnit( (Grid::Units) round( 4.f * strength_ ) ); + Mixer::manager().view()->grid->setActive(true); +} + +void PointerGrid::update(const glm::vec2 &pos, float dt) +{ + Pointer::update(pos, dt); + Mixer::manager().view()->grid->setUnit( (Grid::Units) round( 4.f * strength_ ) ); +} + +void PointerGrid::terminate() +{ + Mixer::manager().view()->grid->setActive(false); +} + + #define POINTER_LINEAR_MIN_SPEED 40.f #define POINTER_LINEAR_MAX_SPEED 800.f #define POINTER_LINEAR_THICKNESS 4.f #define POINTER_LINEAR_ARROW 40.f -void PointerLinear::update(glm::vec2 pos, float dt) +void PointerLinear::update(const glm::vec2 &pos, float dt) { float speed = POINTER_LINEAR_MIN_SPEED + (POINTER_LINEAR_MAX_SPEED - POINTER_LINEAR_MIN_SPEED) * strength_; @@ -49,23 +72,23 @@ void PointerLinear::update(glm::vec2 pos, float dt) void PointerLinear::draw() { ImGuiIO& io = ImGui::GetIO(); + const ImU32 color = ImGui::GetColorU32(ImGuiCol_HeaderActive); const glm::vec2 start = glm::vec2( io.MousePos.x, io.MousePos.y); - const glm::vec2 end = glm::vec2( pos_.x / io.DisplayFramebufferScale.x, pos_.y / io.DisplayFramebufferScale.y ); - const ImVec2 _end = ImVec2( end.x, end.y ); + const ImVec2 _end = IMVEC(pos_); // draw line - ImGui::GetBackgroundDrawList()->AddLine(io.MousePos, _end, ImGui::GetColorU32(ImGuiCol_HeaderActive), POINTER_LINEAR_THICKNESS); - ImGui::GetBackgroundDrawList()->AddCircleFilled(_end, 6.0, ImGui::GetColorU32(ImGuiCol_HeaderActive)); + ImGui::GetBackgroundDrawList()->AddLine(io.MousePos, _end, color, POINTER_LINEAR_THICKNESS); + ImGui::GetBackgroundDrawList()->AddCircleFilled(_end, 6.0, color); // direction vector - glm::vec2 delta = start - end; + glm::vec2 delta = start - pos_; float l = glm::length(delta); delta = glm::normalize( delta ); // draw dots regularly to show speed for (float p = 0.f; p < l; p += 200.f * (strength_ + 0.1f)) { glm::vec2 point = start - delta * p; - ImGui::GetBackgroundDrawList()->AddCircleFilled(ImVec2(point.x,point.y), 4.0, ImGui::GetColorU32(ImGuiCol_HeaderActive)); + ImGui::GetBackgroundDrawList()->AddCircleFilled(IMVEC(point), 4.0, color); } // draw arrow head @@ -75,42 +98,39 @@ void PointerLinear::draw() delta *= POINTER_LINEAR_ARROW; const glm::vec2 pointA = start - delta + ortho * 0.5f; const glm::vec2 pointB = start - delta - ortho * 0.5f; - - ImGui::GetBackgroundDrawList()->AddTriangleFilled(io.MousePos, ImVec2(pointA.x, pointA.y), - ImVec2(pointB.x, pointB.y), ImGui::GetColorU32(ImGuiCol_HeaderActive)); + ImGui::GetBackgroundDrawList()->AddTriangleFilled(io.MousePos, IMVEC(pointA), IMVEC(pointB), color); } } -#define POINTER_WIGGLY_MIN_RADIUS 30.f +#define POINTER_WIGGLY_MIN_RADIUS 3.f #define POINTER_WIGGLY_MAX_RADIUS 300.f #define POINTER_WIGGLY_SMOOTHING 10 -void PointerWiggly::update(glm::vec2 pos, float) +void PointerWiggly::update(const glm::vec2 &pos, float) { float radius = POINTER_WIGGLY_MIN_RADIUS + (POINTER_WIGGLY_MAX_RADIUS - POINTER_WIGGLY_MIN_RADIUS) * strength_; // change pos to a random point in a close radius - pos += glm::diskRand( radius ); + glm::vec2 p = pos + glm::diskRand( radius ); // smooth a little and apply const float emaexp = 2.0 / float( POINTER_WIGGLY_SMOOTHING + 1); - pos_ = emaexp * pos + (1.f - emaexp) * pos_; + pos_ = emaexp * p + (1.f - emaexp) * pos_; } void PointerWiggly::draw() { ImGuiIO& io = ImGui::GetIO(); - const ImVec2 _end = ImVec2( pos_.x / io.DisplayFramebufferScale.x, pos_.y / io.DisplayFramebufferScale.y ); - ImGui::GetBackgroundDrawList()->AddLine(io.MousePos, _end, ImGui::GetColorU32(ImGuiCol_HeaderActive), 5.f); + const ImU32 color = ImGui::GetColorU32(ImGuiCol_HeaderActive); + ImGui::GetBackgroundDrawList()->AddLine(io.MousePos, IMVEC(pos_), color, 5.f); const float radius = POINTER_WIGGLY_MIN_RADIUS + (POINTER_WIGGLY_MAX_RADIUS - POINTER_WIGGLY_MIN_RADIUS) * strength_; - ImGui::GetBackgroundDrawList()->AddCircle(io.MousePos, radius * 0.5f, - ImGui::GetColorU32(ImGuiCol_HeaderActive), 0, 2.f + 4.f * strength_); + ImGui::GetBackgroundDrawList()->AddCircle(io.MousePos, radius * 0.5f, color, 0, 2.f + 4.f * strength_); } #define POINTER_METRONOME_RADIUS 30.f -void PointerMetronome::update(glm::vec2 pos, float dt) +void PointerMetronome::update(const glm::vec2 &pos, float dt) { if ( Metronome::manager().timeToBeat() < std::chrono::milliseconds( (uint)floor(dt * 1000.f) )) { pos_ = pos; @@ -120,26 +140,26 @@ void PointerMetronome::update(glm::vec2 pos, float dt) void PointerMetronome::draw() { ImGuiIO& io = ImGui::GetIO(); - const ImVec2 end = ImVec2(pos_.x / io.DisplayFramebufferScale.x, pos_.y / io.DisplayFramebufferScale.y); - ImGui::GetBackgroundDrawList()->AddLine(io.MousePos, end, ImGui::GetColorU32(ImGuiCol_HeaderActive), 4.f); - ImGui::GetBackgroundDrawList()->AddCircle(io.MousePos, POINTER_METRONOME_RADIUS, ImGui::GetColorU32(ImGuiCol_HeaderActive), 0, 3.f); - ImGui::GetBackgroundDrawList()->AddCircleFilled(end, 6.0, ImGui::GetColorU32(ImGuiCol_HeaderActive)); + const ImU32 color = ImGui::GetColorU32(ImGuiCol_HeaderActive); + ImGui::GetBackgroundDrawList()->AddLine(io.MousePos, IMVEC(pos_), color, 4.f); + ImGui::GetBackgroundDrawList()->AddCircle(io.MousePos, POINTER_METRONOME_RADIUS, color, 0, 3.f); + ImGui::GetBackgroundDrawList()->AddCircleFilled(IMVEC(pos_), 6.0, color); double t = Metronome::manager().phase(); t -= floor(t); - ImGui::GetBackgroundDrawList()->AddCircleFilled(io.MousePos, t * POINTER_METRONOME_RADIUS, ImGui::GetColorU32(ImGuiCol_HeaderActive), 0); + ImGui::GetBackgroundDrawList()->AddCircleFilled(io.MousePos, t * POINTER_METRONOME_RADIUS, color, 0); } #define POINTER_SPRING_MIN_MASS 6.f #define POINTER_SPRING_MAX_MASS 40.f -void PointerSpring::initiate(glm::vec2 pos) +void PointerSpring::initiate(const glm::vec2 &pos) { Pointer::initiate(pos); velocity_ = glm::vec2(0.f); } -void PointerSpring::update(glm::vec2 pos, float dt) +void PointerSpring::update(const glm::vec2 &pos, float dt) { // percentage of loss of energy at every update const float viscousness = 0.75; @@ -166,8 +186,9 @@ void PointerSpring::update(glm::vec2 pos, float dt) void PointerSpring::draw() { ImGuiIO& io = ImGui::GetIO(); - glm::vec2 _start = glm::vec2( io.MousePos.x * io.DisplayFramebufferScale.x, io.MousePos.y * io.DisplayFramebufferScale.y ); + const ImU32 color = ImGui::GetColorU32(ImGuiCol_HeaderActive); + glm::vec2 _start = glm::vec2( io.MousePos.x, io.MousePos.y ); const glm::vec2 delta = pos_ - _start; glm::vec2 ortho = glm::normalize( glm::vec2( glm::cross( glm::vec3(delta, 0.f), glm::vec3(0.f, 0.f, 1.f)) )); ortho *= 0.05f * glm::length( velocity_ ); @@ -176,72 +197,67 @@ void PointerSpring::draw() glm::vec2 _third = _start + delta * 1.f / 9.f + ortho; glm::vec2 _twothird = _start + delta * 2.f / 9.f - ortho; glm::vec2 _end = _start + delta * 3.f / 9.f; + ImGui::GetBackgroundDrawList()->AddBezierCurve(IMVEC(_start), IMVEC(_third), IMVEC(_twothird), IMVEC(_end), color, 5.f); - ImVec2 start = ImVec2(_start.x / io.DisplayFramebufferScale.x, _start.y / io.DisplayFramebufferScale.y); - ImVec2 third = ImVec2(_third.x / io.DisplayFramebufferScale.x, _third.y / io.DisplayFramebufferScale.y); - ImVec2 twothird = ImVec2(_twothird.x / io.DisplayFramebufferScale.x, _twothird.y / io.DisplayFramebufferScale.y); - ImVec2 end = ImVec2(_end.x / io.DisplayFramebufferScale.x, _end.y / io.DisplayFramebufferScale.y); - ImGui::GetBackgroundDrawList()->AddBezierCurve(start, third, twothird, end, - ImGui::GetColorU32(ImGuiCol_HeaderActive), 5.f); _start = _end; _third = _start + delta * 1.f / 9.f + ortho; _twothird = _start + delta * 2.f / 9.f - ortho; _end = _start + delta * 3.f / 9.f; + ImGui::GetBackgroundDrawList()->AddBezierCurve(IMVEC(_start), IMVEC(_third), IMVEC(_twothird), IMVEC(_end), color, 5.f); - start = ImVec2(_start.x / io.DisplayFramebufferScale.x, _start.y / io.DisplayFramebufferScale.y); - third = ImVec2(_third.x / io.DisplayFramebufferScale.x, _third.y / io.DisplayFramebufferScale.y); - twothird = ImVec2(_twothird.x / io.DisplayFramebufferScale.x, _twothird.y / io.DisplayFramebufferScale.y); - end = ImVec2(_end.x / io.DisplayFramebufferScale.x, _end.y / io.DisplayFramebufferScale.y); - ImGui::GetBackgroundDrawList()->AddBezierCurve(start, third, twothird, end, - ImGui::GetColorU32(ImGuiCol_HeaderActive), 5.f); _start = _end; _third = _start + delta * 1.f / 9.f + ortho; _twothird = _start + delta * 2.f / 9.f - ortho; - - start = ImVec2(_start.x / io.DisplayFramebufferScale.x, _start.y / io.DisplayFramebufferScale.y); - third = ImVec2(_third.x / io.DisplayFramebufferScale.x, _third.y / io.DisplayFramebufferScale.y); - twothird = ImVec2(_twothird.x / io.DisplayFramebufferScale.x, _twothird.y / io.DisplayFramebufferScale.y); - end = ImVec2(pos_.x / io.DisplayFramebufferScale.x, pos_.y / io.DisplayFramebufferScale.y); - ImGui::GetBackgroundDrawList()->AddBezierCurve(start, third, twothird, end, - ImGui::GetColorU32(ImGuiCol_HeaderActive), 5.f); + _end = pos_; + ImGui::GetBackgroundDrawList()->AddBezierCurve(IMVEC(_start), IMVEC(_third), IMVEC(_twothird), IMVEC(_end), color, 5.f); // represent the weight with a filled circle - float mass = POINTER_SPRING_MAX_MASS - (POINTER_SPRING_MAX_MASS - POINTER_SPRING_MIN_MASS) * strength_; - ImGui::GetBackgroundDrawList()->AddCircleFilled(end, mass, ImGui::GetColorU32(ImGuiCol_HeaderActive)); + const float mass = POINTER_SPRING_MAX_MASS - (POINTER_SPRING_MAX_MASS - POINTER_SPRING_MIN_MASS) * strength_; + ImGui::GetBackgroundDrawList()->AddCircleFilled(IMVEC(_end), mass, color, 0); } -MousePointer::MousePointer() : mode_(Pointer::POINTER_DEFAULT), active_(nullptr) +MousePointer::MousePointer() : mode_(Pointer::POINTER_DEFAULT) { - active_ = new Pointer; + pointer_[Pointer::POINTER_DEFAULT] = new Pointer; + pointer_[Pointer::POINTER_GRID] = new PointerGrid; + pointer_[Pointer::POINTER_LINEAR] = new PointerLinear; + pointer_[Pointer::POINTER_SPRING] = new PointerSpring; + pointer_[Pointer::POINTER_WIGGLY] = new PointerWiggly; + pointer_[Pointer::POINTER_METRONOME] = new PointerMetronome; } -void MousePointer::setActiveMode(Pointer::Mode m) -{ - if (mode_ != m) { - mode_ = m; +//void PointerGrid::draw() +//{ - if (active_) - delete active_; - switch (mode_) { - case Pointer::POINTER_SPRING: - active_ = new PointerSpring; - break; - case Pointer::POINTER_METRONOME: - active_ = new PointerMetronome; - break; - case Pointer::POINTER_LINEAR: - active_ = new PointerLinear; - break; - case Pointer::POINTER_WIGGLY: - active_ = new PointerWiggly; - break; - default: - case Pointer::POINTER_DEFAULT: - active_ = new Pointer; - break; - } - } -} +//// const float scale = Mixer::manager().view()->grid->unit(); +//// const ImU32 color = ImGui::GetColorU32(ImGuiCol_Header); +// glm::vec2 newpos = Mixer::manager().view()->grid->approx(pos_); +// ImGui::GetBackgroundDrawList()->AddCircleFilled(IMVEC(newpos), 9.0, ImGui::GetColorU32(ImGuiCol_HeaderActive)); +// ImGui::GetBackgroundDrawList()->AddCircleFilled(IMVEC(pos_), 6.0, ImGui::GetColorU32(ImGuiCol_HeaderHovered)); + +//// // draw points of grid +//// for (float x = -2.f ; x < 2.1f ; x += 1.f ) { +//// for (float y = -2.f ; y < 2.1f ; y += 1.f ) { +//// glm::vec2 G = pos_ + scene_x_ * ( x * scale ) + scene_y_ * ( y * scale ); +//// ImGui::GetBackgroundDrawList()->AddCircleFilled(ImVec2(G.x, G.y), 4.0, color); +//// } +//// } + +//// // draw lines of grid +//// for (float x = -2.f ; x < 2.1f ; x += 1.f ) { +//// glm::vec2 A = pos_ + scene_x_ * ( x * scale ) + scene_y_ * ( -3.f * scale ); +//// glm::vec2 B = pos_ + scene_x_ * ( x * scale ) + scene_y_ * ( 3.f * scale ); +//// ImGui::GetBackgroundDrawList()->AddLine(IMVEC(A), IMVEC(B), color, 2.5f); +//// } +//// for (float y = -2.f ; y < 2.1f ; y += 1.f ) { +//// glm::vec2 A = pos_ + scene_x_ * ( -3.f * scale ) + scene_y_ * ( y * scale ); +//// glm::vec2 B = pos_ + scene_x_ * ( 3.f * scale ) + scene_y_ * ( y * scale ); +//// ImGui::GetBackgroundDrawList()->AddLine(IMVEC(A), IMVEC(B), color, 2.5f); +//// } + +//// ImGui::GetBackgroundDrawList()->AddLine(ImVec2(P.x, P.y), ImVec2(Y.x, Y.y), color, POINTER_GRID_THICKNESS); + +//} diff --git a/src/MousePointer.h b/src/MousePointer.h index c86eed0..f3d7037 100644 --- a/src/MousePointer.h +++ b/src/MousePointer.h @@ -2,6 +2,7 @@ #define POINTER_H #include +#include #include #include @@ -9,14 +10,26 @@ #define ICON_POINTER_OPTION 12, 9 #define ICON_POINTER_SPRING 13, 9 #define ICON_POINTER_LINEAR 14, 9 +#define ICON_POINTER_GRID 15, 9 #define ICON_POINTER_WIGGLY 10, 3 #define ICON_POINTER_METRONOME 6, 13 +/// +/// \brief The Pointer class takes a position at each update and +/// computes a new position that is filtered. +/// +/// A position can be given at initiation and a termination can be used to +/// finish up. Draw should perform a visual representation of the cursor +/// +/// By default, a Pointer does not alter the position +/// The Pointer class has to be inerited to define other behaviors +/// class Pointer { public: typedef enum { POINTER_DEFAULT = 0, + POINTER_GRID, POINTER_LINEAR, POINTER_SPRING, POINTER_WIGGLY, @@ -29,49 +42,80 @@ public: virtual ~Pointer() {} inline glm::vec2 pos() { return pos_; } - virtual void initiate(glm::vec2 pos) { pos_ = pos; } - virtual void update(glm::vec2 pos, float) { pos_ = pos; } + virtual void initiate(const glm::vec2 &pos) { pos_ = pos; } + virtual void update(const glm::vec2 &pos, float) { pos_ = pos; } + virtual void terminate() {} virtual void draw() {} - inline void setStrength(float percent) { strength_ = glm::clamp(percent, 0.f, 1.f); } - inline float strength() const { return strength_; } + inline void setStrength(float percent) { strength_ = glm::clamp(percent, 0.f, 1.f); } + inline void incrementStrength(float inc) { setStrength( strength_ + inc); } + inline float strength() const { return strength_; } protected: glm::vec2 pos_; float strength_; }; +/// +/// \brief The PointerGrid activates the view grid +/// +class PointerGrid : public Pointer +{ +public: + PointerGrid() {} + void initiate(const glm::vec2&) override; + void update(const glm::vec2 &pos, float) override; + void terminate() override; +// void draw() override; +}; + +/// +/// \brief The PointerLinear moves the pointer on a line +/// Strength modulates the value of the constant speed +/// class PointerLinear : public Pointer { public: PointerLinear() {} - void update(glm::vec2 pos, float dt) override; + void update(const glm::vec2 &pos, float dt) override; void draw() override; }; +/// +/// \brief The PointerSpring moves the pointer as a spring-mass system +/// Strength modulates the mass of the pointer +/// class PointerSpring : public Pointer { glm::vec2 velocity_; public: PointerSpring() {} - void initiate(glm::vec2 pos) override; - void update(glm::vec2 pos, float dt) override; + void initiate(const glm::vec2 &pos) override; + void update(const glm::vec2 &pos, float dt) override; void draw() override; }; +/// +/// \brief The PointerWiggly moves randomly at high frequency +/// Strength modulates the radius of the movement +/// class PointerWiggly : public Pointer { public: PointerWiggly() {} - void update(glm::vec2 pos, float) override; + void update(const glm::vec2 &pos, float) override; void draw() override; }; +/// +/// \brief The PointerMetronome waits for the metronome to follow +/// TODO : modulate number of beat sync ? +/// class PointerMetronome : public Pointer { public: PointerMetronome() {} - void update(glm::vec2 pos, float dt) override; + void update(const glm::vec2 &pos, float dt) override; void draw() override; }; @@ -84,7 +128,6 @@ class MousePointer MousePointer& operator=(MousePointer const& copy) = delete; public: - static MousePointer& manager () { // The only instance @@ -92,15 +135,14 @@ public: return _instance; } - inline Pointer *active() { return active_; } - inline Pointer::Mode activeMode() { return mode_; } + inline Pointer *active() { return pointer_[mode_]; } - void setActiveMode(Pointer::Mode m); + inline Pointer::Mode activeMode() { return mode_; } + inline void setActiveMode(Pointer::Mode m) { mode_ = m; } private: - Pointer::Mode mode_; - Pointer *active_; + std::map< Pointer::Mode, Pointer* > pointer_; }; #endif // POINTER_H diff --git a/src/PickingVisitor.h b/src/PickingVisitor.h index 945b3b1..87c7555 100644 --- a/src/PickingVisitor.h +++ b/src/PickingVisitor.h @@ -29,6 +29,7 @@ public: PickingVisitor(glm::vec3 selectionstart, glm::vec3 selection_end, bool force = false); bool empty() const {return nodes_.empty(); } + size_t count() const {return nodes_.size(); } std::pair back() const { return nodes_.back(); } std::vector< std::pair >::const_reverse_iterator rbegin() { return nodes_.rbegin(); } std::vector< std::pair >::const_reverse_iterator rend() { return nodes_.rend(); } diff --git a/src/Primitives.cpp b/src/Primitives.cpp index 599946b..71e1a77 100644 --- a/src/Primitives.cpp +++ b/src/Primitives.cpp @@ -466,7 +466,7 @@ float LineGrid::lineWidth() const return 0.f; } -LineStrip::LineStrip(const std::vector &path, float linewidth) : Primitive(new Shader), +LineStrip::LineStrip(const std::vector &path, float linewidth, Shader *s) : Primitive(s), arrayBuffer_(0), path_(path) { linewidth_ = 0.002f * linewidth; @@ -558,6 +558,9 @@ void LineStrip::init() void LineStrip::updatePath() { + if (!vao_) + return; + // redo points_ array points_.clear(); for(size_t i = 1; i < path_.size(); ++i) @@ -621,7 +624,7 @@ void LineStrip::accept(Visitor& v) } -LineLoop::LineLoop(const std::vector &path, float linewidth) : LineStrip(path, linewidth) +LineLoop::LineLoop(const std::vector &path, float linewidth, Shader *s) : LineStrip(path, linewidth, s) { // close linestrip loop glm::vec3 begin = glm::vec3(path_[path_.size()-1], 0.f); @@ -648,6 +651,9 @@ LineLoop::LineLoop(const std::vector &path, float linewidth) : LineSt void LineLoop::updatePath() { + if (!vao_) + return; + glm::vec3 begin; glm::vec3 end; glm::vec3 dir; @@ -690,19 +696,154 @@ void LineLoop::updatePath() bbox_.extend(points_); } -#define LINE_CIRCLE_DENSITY 72 +// statically defined line loop drawing a circle (72 points) +std::vector _circle_loop = { + {1.000000, 0.000000}, + {0.996087, 0.088380}, + {0.984378, 0.176069}, + {0.964965, 0.262379}, + {0.938000, 0.346636}, + {0.903694, 0.428180}, + {0.862315, 0.506373}, + {0.814187, 0.580603}, + {0.759687, 0.650289}, + {0.699242, 0.714885}, + {0.633324, 0.773887}, + {0.562449, 0.826832}, + {0.487173, 0.873306}, + {0.408084, 0.912945}, + {0.325801, 0.945439}, + {0.240968, 0.970533}, + {0.154249, 0.988032}, + {0.066323, 0.997798}, + {-0.022122, 0.999756}, + {-0.110394, 0.993888}, + {-0.197802, 0.980242}, + {-0.283662, 0.958925}, + {-0.367302, 0.930102}, + {-0.448067, 0.894000}, + {-0.525325, 0.850902}, + {-0.598472, 0.801144}, + {-0.666936, 0.745116}, + {-0.730179, 0.683256}, + {-0.787708, 0.616049}, + {-0.839072, 0.544021}, + {-0.883869, 0.467734}, + {-0.921749, 0.387788}, + {-0.952415, 0.304806}, + {-0.975627, 0.219439}, + {-0.991203, 0.132354}, + {-0.999022, 0.044233}, + {-0.999022, -0.044233}, + {-0.991203, -0.132354}, + {-0.975627, -0.219439}, + {-0.952415, -0.304806}, + {-0.921749, -0.387788}, + {-0.883870, -0.467734}, + {-0.839072, -0.544021}, + {-0.787708, -0.616049}, + {-0.730179, -0.683256}, + {-0.666936, -0.745116}, + {-0.598473, -0.801144}, + {-0.525325, -0.850902}, + {-0.448067, -0.894001}, + {-0.367302, -0.930102}, + {-0.283662, -0.958925}, + {-0.197802, -0.980243}, + {-0.110394, -0.993888}, + {-0.022122, -0.999756}, + {0.066323, -0.997799}, + {0.154249, -0.988033}, + {0.240968, -0.970534}, + {0.325801, -0.945439}, + {0.408084, -0.912945}, + {0.487173, -0.873306}, + {0.562450, -0.826832}, + {0.633324, -0.773887}, + {0.699242, -0.714886}, + {0.759688, -0.650289}, + {0.814188, -0.580603}, + {0.862315, -0.506373}, + {0.903694, -0.428180}, + {0.938001, -0.346636}, + {0.964966, -0.262379}, + {0.984379, -0.176068}, + {0.996088, -0.088380} +}; -LineCircle::LineCircle(float linewidth) : LineLoop(std::vector(LINE_CIRCLE_DENSITY), linewidth) +LineCircle::LineCircle(float linewidth, Shader *s) : LineLoop(_circle_loop, linewidth, s) { - static float a = glm::two_pi() / static_cast(LINE_CIRCLE_DENSITY-1); - // loop to build a circle - glm::vec3 P(1.f, 0.f, 0.f); + // static float a = glm::two_pi() / static_cast(LINE_CIRCLE_DENSITY-1); + // // loop to build a circle + // glm::vec3 P(1.f, 0.f, 0.f); - for (int i = 0; i < LINE_CIRCLE_DENSITY - 1; i++ ){ - path_[i] = glm::vec2(P); - P = glm::rotateZ(P, a); - } - updatePath(); + // for (int i = 0; i < LINE_CIRCLE_DENSITY - 1; i++ ){ + // path_[i] = glm::vec2(P); + // g_printerr("{%f, %f},\n", path_[i].x, path_[i].y); + // P = glm::rotateZ(P, a); + // } } +LineCircleGrid::LineCircleGrid(float angle_step, size_t N, float step, float linewidth) +{ + shader__ = new Shader; + N = MAX(1, N); + step = MAX(0.01, step); + + // Draw N concentric circles + for (size_t n = 1; n < N ; ++n) { + float scale = (float) n * step; + LineCircle *l = new LineCircle(linewidth / scale, shader__); + l->scale_ = glm::vec3( scale, scale, 1.f); + // add cirle to group + attach(l); + } + + // Draw radius lines every angle step + glm::vec3 O(0.f, 0.f, 0.f); + glm::vec3 P(1.f, 0.f, 0.f); + std::vector points; + for (int a = 0 ; a < (int)( (2.f * M_PI) / angle_step ) + 1 ; ++a ){ + points.push_back(O); + P = glm::rotateZ(P, angle_step); + points.push_back(P); + } + // add to group + LineStrip *radius = new LineStrip(points, linewidth * 0.5f, shader__); + radius->scale_ = glm::vec3( glm::vec2( (float) N * step ), 1.f); + attach(radius); +} + +LineCircleGrid::~LineCircleGrid() +{ + // prevent nodes from deleting the shader + for (NodeSet::iterator node = begin(); node != end(); ++node) { + Primitive *p = dynamic_cast(*node); + if (p) + p->replaceShader(nullptr); + } + delete shader__; +} + +void LineCircleGrid::setLineWidth(float v) +{ + for (NodeSet::iterator node = begin(); node != end(); ++node) { + LineStrip *vl = dynamic_cast(*node); + if (vl) + vl->setLineWidth(v); + } +} + +float LineCircleGrid::lineWidth() const +{ + Node *n = front(); + + if (n) { + LineStrip *vl = dynamic_cast(n); + if (vl) + return vl->lineWidth(); + } + return 0.f; +} + diff --git a/src/Primitives.h b/src/Primitives.h index 09786fe..9a63e6f 100644 --- a/src/Primitives.h +++ b/src/Primitives.h @@ -175,7 +175,7 @@ public: class LineStrip : public Primitive { public: - LineStrip(const std::vector &path, float linewidth = 1.f); + LineStrip(const std::vector &path, float linewidth = 1.f, Shader *s = new Shader); virtual ~LineStrip(); virtual void init () override; @@ -202,7 +202,7 @@ protected: class LineLoop : public LineStrip { public: - LineLoop(const std::vector &path, float linewidth = 1.f); + LineLoop(const std::vector &path, float linewidth = 1.f, Shader *s = new Shader); protected: void updatePath() override; @@ -215,10 +215,28 @@ protected: class LineCircle : public LineLoop { public: - LineCircle(float linewidth = 1.f); + LineCircle(float linewidth = 1.f, Shader *s = new Shader); }; +/** + * @brief The LineCircle class is a circular LineLoop (diameter = 1.0) + */ +class LineCircleGrid : public Group { + + Shader *shader__; + +public: + LineCircleGrid(float angle_step, size_t N, float step, float linewidth = 1.f); + ~LineCircleGrid(); + + void setLineWidth(float v); + float lineWidth() const; + + inline void setColor(glm::vec4 c) { shader__->color = c; } + inline glm::vec4 color() const { return shader__->color; } +}; + #endif // PRIMITIVES_H diff --git a/src/Settings.cpp b/src/Settings.cpp index c846e30..f4b8202 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -216,6 +216,7 @@ void Settings::Save(uint64_t runtime) // Pointer XMLElement *PointerNode = xmlDoc.NewElement( "MousePointer" ); PointerNode->SetAttribute("mode", application.mouse_pointer); + PointerNode->SetAttribute("proportional_grid", application.proportional_grid); for (size_t i = 0; i < application.mouse_pointer_strength.size(); ++i ) { float v = application.mouse_pointer_strength[i]; PointerNode->InsertEndChild( XMLElementFromGLM(&xmlDoc, glm::vec2((float)i, v)) ); @@ -565,6 +566,7 @@ void Settings::Load() XMLElement * pointernode = pRoot->FirstChildElement("MousePointer"); if (pointernode != nullptr) { pointernode->QueryIntAttribute("mode", &application.mouse_pointer); + pointernode->QueryBoolAttribute("proportional_grid", &application.proportional_grid); XMLElement* strengthNode = pointernode->FirstChildElement("vec2"); for( ; strengthNode ; strengthNode = strengthNode->NextSiblingElement()) diff --git a/src/Settings.h b/src/Settings.h index d31962f..250af98 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -271,6 +271,7 @@ struct Application int accent_color; bool save_version_snapshot; bool smooth_transition; + bool proportional_grid; int mouse_pointer; std::vector mouse_pointer_strength; bool action_history_follow_view; @@ -337,6 +338,7 @@ struct Application accent_color = 0; smooth_transition = false; save_version_snapshot = false; + proportional_grid = true; mouse_pointer = 0; mouse_pointer_strength = {0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f}; action_history_follow_view = false; diff --git a/src/TextureView.cpp b/src/TextureView.cpp index 27418f2..9345165 100644 --- a/src/TextureView.cpp +++ b/src/TextureView.cpp @@ -82,7 +82,6 @@ TextureView::TextureView() : View(TEXTURE), edit_source_(nullptr), need_edit_upd preview_frame_->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f ); scene.bg()->attach(preview_frame_); // marks on the frame to show scale - show_scale_ = false; horizontal_mark_ = new Mesh("mesh/h_mark.ply"); horizontal_mark_->translation_ = glm::vec3(0.f, -1.f, 0.0f); horizontal_mark_->scale_ = glm::vec3(2.5f, -2.5f, 0.0f); @@ -227,6 +226,15 @@ TextureView::TextureView() : View(TEXTURE), edit_source_(nullptr), need_edit_upd stored_mask_size_ = glm::vec3(0.f); show_cursor_forced_ = false; + scene_brush_pos = glm::vec3(0.f); + + // replace grid with appropriate one + translation_grid_ = new TranslationGrid(scene.root()); + translation_grid_->root()->visible_ = false; + rotation_grid_ = new RotationGrid(scene.root()); + rotation_grid_->root()->visible_ = false; + if (grid) delete grid; + grid = translation_grid_; } void TextureView::update(float dt) @@ -236,6 +244,11 @@ void TextureView::update(float dt) // a more complete update is requested (e.g. after switching to view) if (View::need_deep_update_ > 0 || edit_source_ != Mixer::manager().currentSource()) { need_edit_update_ = true; + + // change grid color + const ImVec4 c = ImGuiToolkit::HighlightColor(); + translation_grid_->setColor( glm::vec4(c.x, c.y, c.z, 0.3) ); + rotation_grid_->setColor( glm::vec4(c.x, c.y, c.z, 0.3) ); } } @@ -308,12 +321,17 @@ View::Cursor TextureView::over (glm::vec2 pos) if (edit_source_ != nullptr) { + // coordinates of mouse in scene reference frame glm::vec3 scene_pos = Rendering::manager().unProject(pos, scene.root()->transform_); - glm::vec2 P(scene_pos); - glm::vec2 S(preview_surface_->scale_); - mask_cursor_circle_->translation_ = glm::vec3(P, 0.f); - mask_cursor_square_->translation_ = glm::vec3(P, 0.f); - mask_cursor_crop_->translation_ = glm::vec3(P, 0.f); + + // if the mouse button is down, use the grabbing coordinates instead + if (current_action_ongoing_) + scene_pos = scene_brush_pos; + + // put the cursor at the coordinates of the mouse / cursor + mask_cursor_circle_->translation_ = glm::vec3(glm::vec2(scene_pos), 0.f); + mask_cursor_square_->translation_ = glm::vec3(glm::vec2(scene_pos), 0.f); + mask_cursor_crop_->translation_ = glm::vec3(glm::vec2(scene_pos), 0.f); ImGuiIO& io = ImGui::GetIO(); if (!io.WantCaptureMouse || show_cursor_forced_) @@ -321,8 +339,9 @@ View::Cursor TextureView::over (glm::vec2 pos) // show paint brush cursor if (edit_source_->maskShader()->mode == MaskShader::PAINT) { if (mask_cursor_paint_ > 0) { + glm::vec2 S(preview_surface_->scale_); S += glm::vec2(Settings::application.brush.x); - if ( ABS(P.x) < S.x && ABS(P.y) < S.y ) { + if ( ABS(scene_pos.x) < S.x && ABS(scene_pos.y) < S.y ) { mask_cursor_circle_->visible_ = Settings::application.brush.z < 1.0; mask_cursor_square_->visible_ = Settings::application.brush.z > 0.0; edit_source_->maskShader()->option = mask_cursor_paint_; @@ -354,6 +373,58 @@ View::Cursor TextureView::over (glm::vec2 pos) return Cursor(); } +void TextureView::adaptGridToSource(Source *s, Node *picked) +{ + // remember active state + bool _active = grid->active(); + + // Reset + grid = translation_grid_; + rotation_grid_->setActive( false ); + translation_grid_->setActive( false ); + rotation_grid_->root()->translation_ = glm::vec3(0.f); + rotation_grid_->root()->scale_ = glm::vec3(1.f); + translation_grid_->root()->translation_ = glm::vec3(0.f); + translation_grid_->root()->rotation_ = glm::vec3(0.f); + + if (s != nullptr) { + + if ( picked == s->handles_[mode_][Handles::ROTATE] ) { + // shift grid at center of source + rotation_grid_->root()->translation_ = s->group(mode_)->translation_; + rotation_grid_->root()->scale_.x = glm::length( + glm::vec2(s->frame()->aspectRatio() * s->group(mode_)->scale_.x, + s->group(mode_)->scale_.y) ); + rotation_grid_->root()->scale_.y = rotation_grid_->root()->scale_.x; + // Swap grid to rotation grid + grid = rotation_grid_; + } + else if ( picked == s->handles_[mode_][Handles::RESIZE] || + picked == s->handles_[mode_][Handles::RESIZE_V] || + picked == s->handles_[mode_][Handles::RESIZE_H] ){ + translation_grid_->root()->translation_ = glm::vec3(0.f); + translation_grid_->root()->rotation_.z = s->group(mode_)->rotation_.z; + } + else if ( picked == s->handles_[mode_][Handles::SCALE] ){ + translation_grid_->root()->translation_ = s->group(mode_)->translation_; + translation_grid_->root()->rotation_.z = s->group(mode_)->rotation_.z; + } + + // set grid aspect ratio + if (Settings::application.proportional_grid) + translation_grid_->setAspectRatio( s->frame()->aspectRatio() ); + else + translation_grid_->setAspectRatio( 1.f ); + } + + // restore active state + grid->setActive( _active ); + + // only the active source is visible + rotation_grid_->root()->visible_ = rotation_grid_->active(); + translation_grid_->root()->visible_ = translation_grid_->active(); +} + std::pair TextureView::pick(glm::vec2 P) { // prepare empty return value @@ -376,11 +447,15 @@ std::pair TextureView::pick(glm::vec2 P) // special case for drawing in the mask if ( current->maskShader()->mode == MaskShader::PAINT && mask_cursor_paint_ > 0) { pick = { mask_cursor_circle_, P }; + // adapt grid to prepare grab action + adaptGridToSource(current); return pick; } // special case for cropping the mask shape else if ( current->maskShader()->mode == MaskShader::SHAPE && mask_cursor_shape_ > 0) { pick = { mask_cursor_crop_, P }; + // adapt grid to prepare grab action + adaptGridToSource(current); return pick; } @@ -392,6 +467,8 @@ std::pair TextureView::pick(glm::vec2 P) if ( is_in_source( current ) ){ // a node in the current source was clicked ! pick = *itp; + // adapt grid to prepare grab action + adaptGridToSource(current, pick.first); break; } } @@ -524,31 +601,8 @@ void TextureView::draw() adjustBackground(); } - // draw marks in axis - if (edit_source_ != nullptr && show_scale_){ - if (edit_source_->maskShader()->shape != MaskShader::HORIZONTAL){ - DrawVisitor dv(horizontal_mark_, Rendering::manager().Projection()); - glm::vec3 dT = glm::vec3( -0.2f * edit_source_->mixingsurface_->scale_.x, 0.f, 0.f); - glm::mat4 T = glm::translate(glm::identity(), dT); - dv.loop(6, T); - scene.accept(dv); - dT = glm::vec3( +0.2f * edit_source_->mixingsurface_->scale_.x, 0.f, 0.f); - T = glm::translate(glm::identity(), dT); - dv.loop(6, T); - scene.accept(dv); - } - if (edit_source_->maskShader()->shape != MaskShader::VERTICAL){ - DrawVisitor dv(vertical_mark_, Rendering::manager().Projection()); - glm::vec3 dT = glm::vec3( 0.f, -0.2f * edit_source_->mixingsurface_->scale_.y, 0.f); - glm::mat4 T = glm::translate(glm::identity(), dT); - dv.loop(6, T); - scene.accept(dv); - dT = glm::vec3( 0.f, +0.2f * edit_source_->mixingsurface_->scale_.y, 0.f); - T = glm::translate(glm::identity(), dT); - dv.loop(6, T); - scene.accept(dv); - } - } + // Display grid + grid->root()->visible_ = (grid->active() && current_action_ongoing_); // draw general view Shader::force_blending_opacity = true; @@ -938,12 +992,12 @@ void TextureView::draw() if (s != nullptr) { if (s->textureMirrored()) { - if (ImGui::Selectable( ICON_FA_BORDER_NONE " Repeat " )){ + if (ImGui::Selectable( ICON_FA_TH_LARGE " Repeat " )){ s->setTextureMirrored(false); Action::manager().store(s->name() + std::string(": Texture Repeat")); } } else { - if (ImGui::Selectable( ICON_FA_BORDER_NONE " Mirror " )){ + if (ImGui::Selectable( ICON_FA_TH_LARGE " Mirror " )){ s->setTextureMirrored(true); Action::manager().store(s->name() + std::string(": Texture Mirror")); } @@ -979,10 +1033,10 @@ void TextureView::draw() ImGui::EndPopup(); } - show_scale_ = false; } + View::Cursor TextureView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::pair pick) { std::ostringstream info; @@ -991,19 +1045,22 @@ View::Cursor TextureView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::pa // grab coordinates in scene-View reference frame glm::vec3 scene_from = Rendering::manager().unProject(from, scene.root()->transform_); glm::vec3 scene_to = Rendering::manager().unProject(to, scene.root()->transform_); - glm::vec3 scene_translation = scene_to - scene_from; // Not grabbing a source if (!s) { // work on the edited source if ( edit_source_ != nullptr ) { - info << edit_source_->name() << ": "; - if ( pick.first == mask_cursor_circle_ ) { + // set brush coordinates (used in mouse over) + scene_brush_pos = scene_to; + if ( pick.first == mask_cursor_circle_ ) { + // snap prush coordinates if grid is active + if (grid->active()) + scene_brush_pos = grid->snap(scene_brush_pos); // inform shader of a cursor action : coordinates and crop scaling - edit_source_->maskShader()->cursor = glm::vec4(scene_to.x, scene_to.y, + edit_source_->maskShader()->cursor = glm::vec4(scene_brush_pos.x, scene_brush_pos.y, edit_source_->mixingsurface_->scale_.x, edit_source_->mixingsurface_->scale_.y); edit_source_->touch(Source::SourceUpdate_Mask); // action label @@ -1020,14 +1077,15 @@ View::Cursor TextureView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::pa // match edit source AR glm::vec3 val = edit_source_->mixingsurface_->scale_; // use cursor translation to scale by quadrant + glm::vec3 scene_translation = scene_to - scene_from; val = glm::sign( hv ? glm::vec3(1.f) : scene_from) * glm::vec3(scene_translation / val); // relative change of stored mask size val += stored_mask_size_; - // apply discrete scale with ALT modifier - if (UserInterface::manager().altModifier()) { - val.x = ROUND(val.x, 5.f); - val.y = ROUND(val.y, 5.f); - show_scale_ = true; + // snap to grid following its aspect ratio + if (grid->active()) { + val.x *= grid->aspectRatio(); + val = grid->snap(val); + val.x *= 1.f / grid->aspectRatio(); } // Clamp | val | < 2.0 val = glm::sign(val) * glm::min( glm::abs(val), glm::vec3(2.f)); @@ -1059,15 +1117,14 @@ View::Cursor TextureView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::pa if (s->locked()) return ret; - Group *sourceNode = s->group(mode_); // groups_[View::APPEARANCE] + Group *sourceNode = s->group(mode_); // make sure matrix transform of stored status is updated s->stored_status_->update(0); // grab coordinates in source-root reference frame - glm::vec4 source_from = glm::inverse(s->stored_status_->transform_) * glm::vec4( scene_from, 1.f ); - glm::vec4 source_to = glm::inverse(s->stored_status_->transform_) * glm::vec4( scene_to, 1.f ); - glm::vec3 source_scaling = glm::vec3(source_to) / glm::vec3(source_from); + const glm::mat4 scene_to_source_transform = glm::inverse(s->stored_status_->transform_); + const glm::mat4 source_to_scene_transform = s->stored_status_->transform_; // which manipulation to perform? if (pick.first) { @@ -1075,76 +1132,64 @@ View::Cursor TextureView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::pa glm::vec2 corner = glm::round(pick.second); // transform from source center to corner - glm::mat4 T = GlmToolkit::transform(glm::vec3(corner.x, corner.y, 0.f), glm::vec3(0.f, 0.f, 0.f), + glm::mat4 source_to_corner_transform = GlmToolkit::transform(glm::vec3(corner, 0.f), glm::vec3(0.f, 0.f, 0.f), glm::vec3(1.f / s->frame()->aspectRatio(), 1.f, 1.f)); // transformation from scene to corner: - glm::mat4 scene_to_corner_transform = T * glm::inverse(s->stored_status_->transform_); + glm::mat4 scene_to_corner_transform = source_to_corner_transform * scene_to_source_transform; glm::mat4 corner_to_scene_transform = glm::inverse(scene_to_corner_transform); - // compute cursor movement in corner reference frame - glm::vec4 corner_from = scene_to_corner_transform * glm::vec4( scene_from, 1.f ); - glm::vec4 corner_to = scene_to_corner_transform * glm::vec4( scene_to, 1.f ); - // operation of scaling in corner reference frame - glm::vec3 corner_scaling = glm::vec3(corner_to) / glm::vec3(corner_from); - - // convert source position in corner reference frame - glm::vec4 center = scene_to_corner_transform * glm::vec4( s->stored_status_->translation_, 1.f); - // picking on the resizing handles in the corners if ( pick.first == s->handles_[mode_][Handles::RESIZE] ) { - // hide all other grips s->handles_[mode_][Handles::SCALE]->visible_ = false; s->handles_[mode_][Handles::RESIZE_H]->visible_ = false; s->handles_[mode_][Handles::RESIZE_V]->visible_ = false; s->handles_[mode_][Handles::ROTATE]->visible_ = false; s->handles_[mode_][Handles::MENU]->visible_ = false; - // inform on which corner should be overlayed (opposite) s->handles_[mode_][Handles::RESIZE]->overlayActiveCorner(-corner); - // RESIZE CORNER + // + // Manipulate the handle in the SCENE coordinates to apply grid snap + // + glm::vec4 handle = corner_to_scene_transform * glm::vec4(corner * 2.f, 0.f, 1.f ); + // move the corner we hold by the mouse translation (in scene reference frame) + handle = glm::translate(glm::identity(), scene_to - scene_from) * handle; + // snap handle coordinates to grid (if active) + if ( grid->active() ) + handle = grid->snap(handle); + // Compute coordinates coordinates back in CORNER reference frame + handle = scene_to_corner_transform * handle; + // The scaling factor is computed by dividing new handle coordinates with the ones before transform + glm::vec2 corner_scaling = glm::vec2(handle) / glm::vec2(corner * 2.f); + // proportional SCALING with SHIFT - if (UserInterface::manager().shiftModifier()) { - // calculate proportional scaling factor - float factor = glm::length( glm::vec2( corner_to ) ) / glm::length( glm::vec2( corner_from ) ); - // scale node - sourceNode->scale_ = s->stored_status_->scale_ * glm::vec3(factor, factor, 1.f); - // discretized scaling with ALT - if (UserInterface::manager().altModifier()) { - sourceNode->scale_.x = ROUND(sourceNode->scale_.x, 10.f); - factor = sourceNode->scale_.x / s->stored_status_->scale_.x; - sourceNode->scale_.y = s->stored_status_->scale_.y * factor; - } - // update corner scaling to apply to center coordinates - corner_scaling = sourceNode->scale_ / s->stored_status_->scale_; - } - // non-proportional CORNER RESIZE (normal case) - else { - // scale node - sourceNode->scale_ = s->stored_status_->scale_ * corner_scaling; - // discretized scaling with ALT - if (UserInterface::manager().altModifier()) { - sourceNode->scale_.x = ROUND(sourceNode->scale_.x, 10.f); - sourceNode->scale_.y = ROUND(sourceNode->scale_.y, 10.f); - corner_scaling = sourceNode->scale_ / s->stored_status_->scale_; - } - } - // transform source center (in corner reference frame) - center = glm::scale(glm::identity(), corner_scaling) * center; + if (UserInterface::manager().shiftModifier()) + corner_scaling = glm::vec2(glm::compMax(corner_scaling)); + + // Apply scaling to the source + sourceNode->scale_ = s->stored_status_->scale_ * glm::vec3(corner_scaling, 1.f); + + // + // Adjust translation + // + // The center of the source in CORNER reference frame + glm::vec4 corner_center = glm::vec4( corner, 0.f, 1.f); + // scale center of source in CORNER reference frame + corner_center = glm::scale(glm::identity(), glm::vec3(corner_scaling, 1.f)) * corner_center; // convert center back into scene reference frame - center = corner_to_scene_transform * center; - // apply to node - sourceNode->translation_ = glm::vec3(center); + corner_center = corner_to_scene_transform * corner_center; + // Apply scaling to the source + sourceNode->translation_ = glm::vec3(corner_center); + // show cursor depending on diagonal (corner picked) - T = glm::rotate(glm::identity(), s->stored_status_->rotation_.z, glm::vec3(0.f, 0.f, 1.f)); + glm::mat4 T = glm::rotate(glm::identity(), s->stored_status_->rotation_.z, glm::vec3(0.f, 0.f, 1.f)); T = glm::scale(T, s->stored_status_->scale_); corner = T * glm::vec4( corner, 0.f, 0.f ); ret.type = corner.x * corner.y > 0.f ? Cursor_ResizeNESW : Cursor_ResizeNWSE; info << "Texture scale " << std::fixed << std::setprecision(3) << sourceNode->scale_.x; info << " x " << sourceNode->scale_.y; - } // picking on the BORDER RESIZING handles left or right else if ( pick.first == s->handles_[mode_][Handles::RESIZE_H] ) { @@ -1159,29 +1204,41 @@ View::Cursor TextureView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::pa // inform on which corner should be overlayed (opposite) s->handles_[mode_][Handles::RESIZE_H]->overlayActiveCorner(-corner); - // SHIFT: HORIZONTAL SCALE to restore source aspect ratio + // + // Manipulate the handle in the SCENE coordinates to apply grid snap + // + glm::vec4 handle = corner_to_scene_transform * glm::vec4(corner * 2.f, 0.f, 1.f ); + // move the corner we hold by the mouse translation (in scene reference frame) + handle = glm::translate(glm::identity(), scene_to - scene_from) * handle; + // snap handle coordinates to grid (if active) + if ( grid->active() ) + handle = grid->snap(handle); + // Compute coordinates coordinates back in CORNER reference frame + handle = scene_to_corner_transform * handle; + // The scaling factor is computed by dividing new handle coordinates with the ones before transform + glm::vec2 corner_scaling = glm::vec2(handle.x, 1.f) / glm::vec2(corner.x * 2.f, 1.f); + + // Apply scaling to the source + sourceNode->scale_ = s->stored_status_->scale_ * glm::vec3(corner_scaling, 1.f); + + // SHIFT: restore previous aspect ratio if (UserInterface::manager().shiftModifier()) { - sourceNode->scale_.x = ABS(sourceNode->scale_.y) * SIGN(sourceNode->scale_.x); - corner_scaling = sourceNode->scale_ / s->stored_status_->scale_; + float ar = s->stored_status_->scale_.y / s->stored_status_->scale_.x; + sourceNode->scale_.y = ar * sourceNode->scale_.x; } - // HORIZONTAL RESIZE (normal case) - else { - // x scale only - corner_scaling = glm::vec3(corner_scaling.x, 1.f, 1.f); - // scale node - sourceNode->scale_ = s->stored_status_->scale_ * corner_scaling; - // POST-CORRECTION ; discretized scaling with ALT - if (UserInterface::manager().altModifier()) { - sourceNode->scale_.x = ROUND(sourceNode->scale_.x, 10.f); - corner_scaling = sourceNode->scale_ / s->stored_status_->scale_; - } - } - // transform source center (in corner reference frame) - center = glm::scale(glm::identity(), corner_scaling) * center; + + // + // Adjust translation + // + // The center of the source in CORNER reference frame + glm::vec4 corner_center = glm::vec4( corner, 0.f, 1.f); + // scale center of source in CORNER reference frame + corner_center = glm::scale(glm::identity(), glm::vec3(corner_scaling, 1.f)) * corner_center; // convert center back into scene reference frame - center = corner_to_scene_transform * center; - // apply to node - sourceNode->translation_ = glm::vec3(center); + corner_center = corner_to_scene_transform * corner_center; + // Apply scaling to the source + sourceNode->translation_ = glm::vec3(corner_center); + // show cursor depending on angle float c = tan(sourceNode->rotation_.z); ret.type = ABS(c) > 1.f ? Cursor_ResizeNS : Cursor_ResizeEW; @@ -1201,29 +1258,41 @@ View::Cursor TextureView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::pa // inform on which corner should be overlayed (opposite) s->handles_[mode_][Handles::RESIZE_V]->overlayActiveCorner(-corner); - // SHIFT: VERTICAL SCALE to restore source aspect ratio + // + // Manipulate the handle in the SCENE coordinates to apply grid snap + // + glm::vec4 handle = corner_to_scene_transform * glm::vec4(corner * 2.f, 0.f, 1.f ); + // move the corner we hold by the mouse translation (in scene reference frame) + handle = glm::translate(glm::identity(), scene_to - scene_from) * handle; + // snap handle coordinates to grid (if active) + if ( grid->active() ) + handle = grid->snap(handle); + // Compute coordinates coordinates back in CORNER reference frame + handle = scene_to_corner_transform * handle; + // The scaling factor is computed by dividing new handle coordinates with the ones before transform + glm::vec2 corner_scaling = glm::vec2(1.f, handle.y) / glm::vec2(1.f, corner.y * 2.f); + + // Apply scaling to the source + sourceNode->scale_ = s->stored_status_->scale_ * glm::vec3(corner_scaling, 1.f); + + // SHIFT: restore previous aspect ratio if (UserInterface::manager().shiftModifier()) { - sourceNode->scale_.y = ABS(sourceNode->scale_.x) * SIGN(sourceNode->scale_.y); - corner_scaling = sourceNode->scale_ / s->stored_status_->scale_; + float ar = s->stored_status_->scale_.x / s->stored_status_->scale_.y; + sourceNode->scale_.x = ar * sourceNode->scale_.y; } - // VERTICAL RESIZE (normal case) - else { - // y scale only - corner_scaling = glm::vec3(1.f, corner_scaling.y, 1.f); - // scale node - sourceNode->scale_ = s->stored_status_->scale_ * corner_scaling; - // POST-CORRECTION ; discretized scaling with ALT - if (UserInterface::manager().altModifier()) { - sourceNode->scale_.y = ROUND(sourceNode->scale_.y, 10.f); - corner_scaling = sourceNode->scale_ / s->stored_status_->scale_; - } - } - // transform source center (in corner reference frame) - center = glm::scale(glm::identity(), corner_scaling) * center; + + // + // Adjust translation + // + // The center of the source in CORNER reference frame + glm::vec4 corner_center = glm::vec4( corner, 0.f, 1.f); + // scale center of source in CORNER reference frame + corner_center = glm::scale(glm::identity(), glm::vec3(corner_scaling, 1.f)) * corner_center; // convert center back into scene reference frame - center = corner_to_scene_transform * center; - // apply to node - sourceNode->translation_ = glm::vec3(center); + corner_center = corner_to_scene_transform * corner_center; + // Apply scaling to the source + sourceNode->translation_ = glm::vec3(corner_center); + // show cursor depending on angle float c = tan(sourceNode->rotation_.z); ret.type = ABS(c) > 1.f ? Cursor_ResizeEW : Cursor_ResizeNS; @@ -1232,7 +1301,6 @@ View::Cursor TextureView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::pa } // picking on the CENTRER SCALING handle else if ( pick.first == s->handles_[mode_][Handles::SCALE] ) { - // hide all other grips s->handles_[mode_][Handles::RESIZE]->visible_ = false; s->handles_[mode_][Handles::RESIZE_H]->visible_ = false; @@ -1249,22 +1317,38 @@ View::Cursor TextureView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::pa overlay_scaling_->rotation_.z = s->stored_status_->rotation_.z; overlay_scaling_->update(0); - // PROPORTIONAL ONLY + // Transform scene to reference frame at the center, but scaled to match aspect ratio + const glm::mat4 center_scale = glm::scale(glm::identity(), + glm::vec3(1.f / s->frame()->aspectRatio(), 1.f, 1.f)); + + glm::mat4 scene_to_center_transform = center_scale * scene_to_source_transform; + glm::mat4 center_to_scene_transform = glm::inverse(scene_to_center_transform); + + // + // Manipulate the scaling handle in the SCENE coordinates to apply grid snap + // + glm::vec3 handle = glm::vec3( glm::round(pick.second), 0.f); + // Compute handle coordinates into SCENE reference frame + handle = center_to_scene_transform * glm::vec4( handle, 1.f ); + // move the handle we hold by the mouse translation (in scene reference frame) + handle = glm::translate(glm::identity(), scene_to - scene_from) * glm::vec4( handle, 1.f ); + // snap handle coordinates to grid (if active) + if ( grid->active() ) + handle = grid->snap(handle); + // Compute handle coordinates back in SOURCE CENTER reference frame + handle = scene_to_center_transform * glm::vec4( handle, 1.f ); + // The scaling factor is computed by dividing new handle coordinates with the ones before transform + glm::vec2 handle_scaling = glm::vec2(handle) / glm::round(pick.second); + + // proportional SCALING with SHIFT if (UserInterface::manager().shiftModifier()) { - float factor = glm::length( glm::vec2( source_to ) ) / glm::length( glm::vec2( source_from ) ); - source_scaling = glm::vec3(factor, factor, 1.f); + handle_scaling = glm::vec2(glm::compMax(handle_scaling)); overlay_scaling_cross_->visible_ = true; overlay_scaling_cross_->copyTransform(overlay_scaling_); } - // apply center scaling - sourceNode->scale_ = s->stored_status_->scale_ * source_scaling; - // POST-CORRECTION ; discretized scaling with ALT - if (UserInterface::manager().altModifier()) { - sourceNode->scale_.x = ROUND(sourceNode->scale_.x, 10.f); - sourceNode->scale_.y = ROUND(sourceNode->scale_.y, 10.f); - overlay_scaling_grid_->visible_ = true; - overlay_scaling_grid_->copyTransform(overlay_scaling_); - } + // Apply scaling to the source + sourceNode->scale_ = s->stored_status_->scale_ * glm::vec3(handle_scaling, 1.f); + // show cursor depending on diagonal corner = glm::sign(sourceNode->scale_); ret.type = (corner.x * corner.y) > 0.f ? Cursor_ResizeNWSE : Cursor_ResizeNESW; @@ -1285,71 +1369,94 @@ View::Cursor TextureView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::pa overlay_rotation_->translation_.x = s->stored_status_->translation_.x; overlay_rotation_->translation_.y = s->stored_status_->translation_.y; overlay_rotation_->update(0); - overlay_rotation_fix_->visible_ = true; + overlay_rotation_fix_->visible_ = false; overlay_rotation_fix_->copyTransform(overlay_rotation_); overlay_rotation_clock_->visible_ = false; - - // rotation center to center of source (disregarding scale) - glm::mat4 M = glm::translate(glm::identity(), s->stored_status_->translation_); - source_from = glm::inverse(M) * glm::vec4( scene_from, 1.f ); - source_to = glm::inverse(M) * glm::vec4( scene_to, 1.f ); - // compute rotation angle - float angle = glm::orientedAngle( glm::normalize(glm::vec2(source_from)), glm::normalize(glm::vec2(source_to))); - // apply rotation on Z axis - sourceNode->rotation_ = s->stored_status_->rotation_ + glm::vec3(0.f, 0.f, angle); - - // POST-CORRECTION ; discretized rotation with ALT - int degrees = int( glm::degrees(sourceNode->rotation_.z) ); - if (UserInterface::manager().altModifier()) { - degrees = (degrees / 10) * 10; - sourceNode->rotation_.z = glm::radians( float(degrees) ); - overlay_rotation_clock_->visible_ = true; - overlay_rotation_clock_->copyTransform(overlay_rotation_); - info << "Texture Angle " << degrees << UNICODE_DEGREE; - } - else - info << "Texture Angle " << std::fixed << std::setprecision(1) << glm::degrees(sourceNode->rotation_.z) << UNICODE_DEGREE; - overlay_rotation_clock_hand_->visible_ = true; overlay_rotation_clock_hand_->translation_.x = s->stored_status_->translation_.x; overlay_rotation_clock_hand_->translation_.y = s->stored_status_->translation_.y; + + // prepare variables + const float diagonal = glm::length( glm::vec2(s->frame()->aspectRatio() * s->stored_status_->scale_.x, s->stored_status_->scale_.y)); + glm::vec2 handle_polar = glm::vec2( diagonal, 0.f); + + // rotation center to center of source (disregarding scale) + glm::mat4 M = glm::translate(glm::identity(), s->stored_status_->translation_); + glm::vec3 source_from = glm::inverse(M) * glm::vec4( scene_from, 1.f ); + glm::vec3 source_to = glm::inverse(M) * glm::vec4( scene_to, 1.f ); + + // compute rotation angle on Z axis + float angle = glm::orientedAngle( glm::normalize(glm::vec2(source_from)), glm::normalize(glm::vec2(source_to))); + handle_polar.y = s->stored_status_->rotation_.z + angle; + info << "Angle " << std::fixed << std::setprecision(1) << glm::degrees(sourceNode->rotation_.z) << UNICODE_DEGREE; + + // compute scaling of diagonal to reach new coordinates + handle_polar.x *= glm::length( glm::vec2(source_to) ) / glm::length( glm::vec2(source_from) ); + + // snap polar coordiantes (diagonal lenght, angle) + if ( grid->active() ) { + handle_polar = glm::round( handle_polar / grid->step() ) * grid->step(); + // prevent null size + handle_polar.x = glm::max( grid->step().x, handle_polar.x ); + } + + // Cancel scaling diagonal with SHIFT + if (UserInterface::manager().shiftModifier()) { + handle_polar.x = diagonal; + overlay_rotation_fix_->visible_ = true; + } else { + info << std::endl << " Size " << std::fixed << std::setprecision(3) << sourceNode->scale_.x; + info << " x " << sourceNode->scale_.y ; + } + + // apply after snap + sourceNode->rotation_ = glm::vec3(0.f, 0.f, handle_polar.y); + handle_polar.x /= diagonal ; + sourceNode->scale_ = s->stored_status_->scale_ * glm::vec3(handle_polar.x, handle_polar.x, 1.f); + + // update overlay overlay_rotation_clock_hand_->rotation_.z = sourceNode->rotation_.z; overlay_rotation_clock_hand_->update(0); // show cursor for rotation ret.type = Cursor_Hand; - // + SHIFT = no scaling / NORMAL = with scaling - if (!UserInterface::manager().shiftModifier()) { - // compute scaling to match cursor - float factor = glm::length( glm::vec2( source_to ) ) / glm::length( glm::vec2( source_from ) ); - source_scaling = glm::vec3(factor, factor, 1.f); - // apply center scaling - sourceNode->scale_ = s->stored_status_->scale_ * source_scaling; - info << std::endl << " Scale " << std::fixed << std::setprecision(3) << sourceNode->scale_.x; - info << " x " << sourceNode->scale_.y ; - overlay_rotation_fix_->visible_ = false; - } } // picking anywhere but on a handle: user wants to move the source else { - ret.type = Cursor_ResizeAll; - sourceNode->translation_ = s->stored_status_->translation_ + scene_translation; - // discretized translation with ALT - if (UserInterface::manager().altModifier()) { - sourceNode->translation_.x = ROUND(sourceNode->translation_.x, 10.f); - sourceNode->translation_.y = ROUND(sourceNode->translation_.y, 10.f); - // Show grid overlay for POSITION - overlay_position_cross_->visible_ = true; - overlay_position_cross_->translation_.x = sourceNode->translation_.x; - overlay_position_cross_->translation_.y = sourceNode->translation_.y; - overlay_position_cross_->update(0); + // Default is to grab the center (0,0) of the source + glm::vec3 handle(0.f); + glm::vec3 offset(0.f); + + // Snap corner with SHIFT + if (UserInterface::manager().shiftModifier()) { + // get corner closest representative of the quadrant of the picking point + handle = glm::vec3( glm::sign(pick.second), 0.f); + // remember the offset for adjustment of translation to this corner + offset = source_to_scene_transform * glm::vec4(handle, 0.f); } + + // Compute target coordinates of manipulated handle into SCENE reference frame + glm::vec3 source_target = source_to_scene_transform * glm::vec4(handle, 1.f); + source_target.z = 0.f; + + // apply translation of target in SCENE + source_target = glm::translate(glm::identity(), scene_to - scene_from) * glm::vec4(source_target, 1.f); + + // snap coordinates to grid (if active) + if ( grid->active() ) + source_target = grid->snap(source_target); + + // Apply translation to the source + sourceNode->translation_ = source_target - offset; + // Show center overlay for POSITION overlay_position_->visible_ = true; overlay_position_->translation_.x = sourceNode->translation_.x; overlay_position_->translation_.y = sourceNode->translation_.y; overlay_position_->update(0); + // Show move cursor + ret.type = Cursor_ResizeAll; info << "Texture Shift " << std::fixed << std::setprecision(3) << sourceNode->translation_.x; info << ", " << sourceNode->translation_.y ; } @@ -1420,6 +1527,8 @@ void TextureView::terminate(bool force) (*sit)->handles_[mode_][Handles::MENU]->visible_ = true; } + // reset grid + adaptGridToSource(); } void TextureView::arrow (glm::vec2 movement) diff --git a/src/TextureView.h b/src/TextureView.h index 2811d72..32500b6 100644 --- a/src/TextureView.h +++ b/src/TextureView.h @@ -43,7 +43,6 @@ private: Frame *background_frame_; Mesh *horizontal_mark_; Mesh *vertical_mark_; - bool show_scale_; Group *mask_node_; Frame *mask_square_; Mesh *mask_circle_; @@ -69,6 +68,10 @@ private: glm::vec3 stored_mask_size_; bool show_cursor_forced_; + void adaptGridToSource(Source *s = nullptr, Node *picked = nullptr); + glm::vec3 scene_brush_pos; + TranslationGrid *translation_grid_; + RotationGrid *rotation_grid_; }; #endif // TEXTUREVIEW_H diff --git a/src/TransitionView.cpp b/src/TransitionView.cpp index 4c3e5d0..ac17bd3 100644 --- a/src/TransitionView.cpp +++ b/src/TransitionView.cpp @@ -21,7 +21,6 @@ #include #include #include -#include #include "ImGuiToolkit.h" diff --git a/src/UserInterfaceManager.cpp b/src/UserInterfaceManager.cpp index 0b4acd0..77dcadd 100644 --- a/src/UserInterfaceManager.cpp +++ b/src/UserInterfaceManager.cpp @@ -505,7 +505,6 @@ void UserInterface::handleMouse() // initiate Mouse pointer from position at mouse down event MousePointer::manager().setActiveMode( (Pointer::Mode) Settings::application.mouse_pointer ); MousePointer::manager().active()->setStrength( Settings::application.mouse_pointer_strength[Settings::application.mouse_pointer] ); - MousePointer::manager().active()->initiate(mousepos); // ask the view what was picked picked = Mixer::manager().view()->pick(mousepos); @@ -517,6 +516,9 @@ void UserInterface::handleMouse() } // something was picked else { + // initiate the pointer effect + MousePointer::manager().active()->initiate(mousepos); + // get if a source was picked Source *s = Mixer::manager().findSource(picked.first); if (s != nullptr) @@ -585,42 +587,37 @@ void UserInterface::handleMouse() if (view_drag == Mixer::manager().view()) { if ( picked.first != nullptr ) { + // Apply Mouse pointer filter + // + scrollwheel changes strength + if ( io.MouseWheel != 0) { + MousePointer::manager().active()->incrementStrength(0.1 * io.MouseWheel); + Settings::application.mouse_pointer_strength[Settings::application.mouse_pointer] = MousePointer::manager().active()->strength(); + } MousePointer::manager().active()->update(mousepos, 1.f / ( MAX(io.Framerate, 1.f) )); // action on current source + View::Cursor c; Source *current = Mixer::manager().currentSource(); if (current) { - if (!shift_modifier_active) { - // grab others from selection - for (auto it = Mixer::selection().begin(); it != Mixer::selection().end(); ++it) { - if ( *it != current && !(*it)->locked() ) - Mixer::manager().view()->grab(*it, mouseclic[ImGuiMouseButton_Left], - MousePointer::manager().active()->pos(), picked); - } - } // grab current sources - View::Cursor c = Mixer::manager().view()->grab(current, mouseclic[ImGuiMouseButton_Left], - MousePointer::manager().active()->pos(), picked); - SetMouseCursor(io.MousePos, c); - - // scrollwheel changes strength of Mouse Pointer - if ( io.MouseWheel != 0) { - MousePointer::manager().active()->setStrength( MousePointer::manager().active()->strength() + 0.1 * io.MouseWheel); - Settings::application.mouse_pointer_strength[Settings::application.mouse_pointer] = MousePointer::manager().active()->strength(); - } - - // Draw Mouse pointer effect - MousePointer::manager().active()->draw(); + c = Mixer::manager().view()->grab(current, mouseclic[ImGuiMouseButton_Left], + MousePointer::manager().active()->pos(), picked); } // action on other (non-source) elements in the view else { - View::Cursor c = Mixer::manager().view()->grab(nullptr, mouseclic[ImGuiMouseButton_Left], mousepos, picked); - SetMouseCursor(io.MousePos, c); + // grab picked object + c = Mixer::manager().view()->grab(nullptr, mouseclic[ImGuiMouseButton_Left], + MousePointer::manager().active()->pos(), picked); } + // Set cursor appearance + SetMouseCursor(io.MousePos, c); + + // Draw Mouse pointer effect + MousePointer::manager().active()->draw(); } // Selection area @@ -650,6 +647,7 @@ void UserInterface::handleMouse() if (mousedown || view_drag) Mixer::manager().view()->terminate(); + view_drag = nullptr; mousedown = false; } @@ -667,6 +665,7 @@ void UserInterface::handleMouse() mousedown = false; picked = { nullptr, glm::vec2(0.f) }; Mixer::manager().view()->terminate(); + MousePointer::manager().active()->terminate(); SetMouseCursor(io.MousePos); } } @@ -4622,7 +4621,7 @@ void Navigator::RenderMousePointerSelector(const ImVec2 &size) { ImGuiContext& g = *GImGui; ImVec2 top = ImGui::GetCursorPos(); - + bool enabled = Settings::application.current_view < View::TRANSITION; /// /// interactive button of the given size: show menu if clic or mouse over /// @@ -4630,13 +4629,17 @@ void Navigator::RenderMousePointerSelector(const ImVec2 &size) if ( ImGui::InvisibleButton("##MenuMousePointerButton", size) || ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup) ) { counter_menu_timeout=0; - ImGui::OpenPopup( "MenuMousePointer" ); + if (enabled) + ImGui::OpenPopup( "MenuMousePointer" ); } ImVec2 bottom = ImGui::GetCursorScreenPos(); // Change color of icons depending on context menu status const ImVec4* colors = ImGui::GetStyle().Colors; - ImGui::PushStyleColor( ImGuiCol_Text, ImGui::IsPopupOpen("MenuMousePointer") ? colors[ImGuiCol_DragDropTarget] : colors[ImGuiCol_Text] ); + if (enabled) + ImGui::PushStyleColor( ImGuiCol_Text, ImGui::IsPopupOpen("MenuMousePointer") ? colors[ImGuiCol_DragDropTarget] : colors[ImGuiCol_Text] ); + else + ImGui::PushStyleColor( ImGuiCol_Text, colors[ImGuiCol_TextDisabled] ); // Draw centered icon of Mouse pointer ImVec2 margin = (size - ImVec2(g.FontSize, g.FontSize)) * 0.42f; @@ -4709,6 +4712,8 @@ void Navigator::RenderMainPannelSettings() if (ImGui::RadioButton("##Color", &v, v)){ Settings::application.accent_color = (v+1)%3; ImGuiToolkit::SetAccentColor(static_cast(Settings::application.accent_color)); + // ask Views to update + View::need_deep_update_++; } if (ImGui::IsItemHovered()) ImGuiToolkit::ToolTip("Change accent color"); @@ -4719,6 +4724,9 @@ void Navigator::RenderMainPannelSettings() Settings::application.scale = CLAMP(Settings::application.scale, 0.5f, 2.f); ImGui::GetIO().FontGlobalScale = Settings::application.scale; } + ImGuiToolkit::Indication("Scale the mouse pointer guiding grid to match aspect ratio.", ICON_FA_BORDER_NONE); + ImGui::SameLine(); + ImGuiToolkit::ButtonSwitch( "Scaled grid", &Settings::application.proportional_grid); // // Recording preferences diff --git a/src/View.cpp b/src/View.cpp index cfd00a0..64f5beb 100644 --- a/src/View.cpp +++ b/src/View.cpp @@ -39,13 +39,16 @@ uint View::need_deep_update_ = 1; -View::View(Mode m) : mode_(m), current_action_ongoing_(false), dt_(16.f) +View::View(Mode m) : grid(nullptr), mode_(m), current_action_ongoing_(false), dt_(16.f) { show_context_menu_ = MENU_NONE; overlay_selection_ = nullptr; overlay_selection_frame_ = nullptr; overlay_selection_icon_ = nullptr; + + // detault grid + grid = new Grid(scene.root()); } void View::restoreSettings() @@ -296,4 +299,3 @@ void View::lock(Source *s, bool on) else Action::manager().store(s->name() + std::string(": unlock.")); } - diff --git a/src/View.h b/src/View.h index 78ffdd0..774303f 100644 --- a/src/View.h +++ b/src/View.h @@ -6,6 +6,7 @@ #include #include "Scene.h" +#include "Grid.h" class Session; class SessionFileSource; @@ -17,7 +18,6 @@ class Disk; class Handles; class Source; - class View { public: @@ -99,6 +99,9 @@ public: // accessible scene Scene scene; + // Grid used to snap mouse pointer + Grid *grid; + // reordering scene when necessary static uint need_deep_update_; diff --git a/src/defines.h b/src/defines.h index 1988561..1dbada5 100644 --- a/src/defines.h +++ b/src/defines.h @@ -33,6 +33,7 @@ #define EXP100(val) (exp(log(10.0)/50.0*(float)(val))-1.0) #define EUCLIDEAN(P1, P2) sqrt((P1.x() - P2.x()) * (P1.x() - P2.x()) + (P1.y() - P2.y()) * (P1.y() - P2.y())) #define ROUND(val, factor) float( int( val * factor ) ) / factor; +#define IMVEC(v) ImVec2( v.x, v.y ) #define SCENE_UNIT 5.f #define MIN_SCALE 0.01f