diff --git a/rsc/images/icons.dds b/rsc/images/icons.dds index e048669..680e070 100644 Binary files a/rsc/images/icons.dds and b/rsc/images/icons.dds differ diff --git a/rsc/mesh/icon_clock_hand.ply b/rsc/mesh/icon_clock_hand.ply index c8e50e0..47bf669 100644 --- a/rsc/mesh/icon_clock_hand.ply +++ b/rsc/mesh/icon_clock_hand.ply @@ -1,20 +1,23 @@ ply format ascii 1.0 -comment Created by Blender 2.90.0 - www.blender.org -element vertex 4 +comment Created by Blender 3.4.1 - www.blender.org +element vertex 6 property float x property float y property float z -property uchar red -property uchar green -property uchar blue -property uchar alpha -element face 2 +property float nx +property float ny +property float nz +element face 4 property list uchar uint vertex_indices end_header -0.864086 -0.043969 0.000000 255 255 255 255 -1.229462 0.040299 0.000000 255 255 255 255 -0.864086 0.040300 0.000000 255 255 255 255 -1.229462 -0.043970 0.000000 255 255 255 255 +0.864086 -0.043969 0.000000 0.000000 0.000000 1.000000 +2.903103 0.040299 0.000000 0.000000 0.000000 1.000000 +0.864086 0.040300 0.000000 0.000000 0.000000 1.000000 +2.903103 -0.043970 0.000000 0.000000 0.000000 1.000000 +2.998506 0.005674 0.000000 0.000000 0.000000 1.000000 +2.998506 -0.009345 0.000000 0.000000 0.000000 1.000000 3 0 1 2 3 0 3 1 +3 3 4 1 +3 3 5 4 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4fb477f..68d8eb7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -97,6 +97,7 @@ set(VMIX_SRCS DisplaysView.cpp ScreenCaptureSource.cpp MousePointer.cpp + Grid.cpp ) ##### diff --git a/src/DisplaysView.cpp b/src/DisplaysView.cpp index 75f9ec9..f701316 100644 --- a/src/DisplaysView.cpp +++ b/src/DisplaysView.cpp @@ -1041,8 +1041,8 @@ View::Cursor DisplaysView::grab (Source *, glm::vec2 from, glm::vec2 to, std::pa return ret; } -View::Cursor DisplaysView::over (glm::vec2 pos) -{ +//View::Cursor DisplaysView::over (glm::vec2 pos, bool) +//{ // output_zoomarea_->visible_ = 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