diff --git a/include/app/RackWidget.hpp b/include/app/RackWidget.hpp index e5cac52c..b38fa734 100644 --- a/include/app/RackWidget.hpp +++ b/include/app/RackWidget.hpp @@ -49,9 +49,9 @@ struct RackWidget : widget::OpaqueWidget { /** Removes the module and transfers ownership to the caller */ void removeModule(ModuleWidget *mw); /** Sets a module's box if non-colliding. Returns true if set */ - bool requestModuleBox(ModuleWidget *mw, math::Rect requestedBox); + bool requestModulePos(ModuleWidget *mw, math::Vec pos); /** Moves a module to the closest non-colliding position */ - bool requestModuleBoxNearest(ModuleWidget *mw, math::Rect requestedBox); + bool requestModulePosNearest(ModuleWidget *mw, math::Vec pos); ModuleWidget *getModule(int moduleId); bool isEmpty(); diff --git a/src/Core/Blank.cpp b/src/Core/Blank.cpp index f4eecdc6..7ba94b16 100644 --- a/src/Core/Blank.cpp +++ b/src/Core/Blank.cpp @@ -65,7 +65,8 @@ struct ModuleResizeHandle : OpaqueWidget { newBox.size.x = std::round(newBox.size.x / RACK_GRID_WIDTH) * RACK_GRID_WIDTH; newBox.pos.x = originalBox.pos.x + originalBox.size.x - newBox.size.x; } - APP->scene->rack->requestModuleBox(m, newBox); + // TODO + // APP->scene->rack->requestModuleBox(m, newBox); } void draw(const DrawArgs &args) override { diff --git a/src/app/ModuleWidget.cpp b/src/app/ModuleWidget.cpp index 05819f6e..f34d9bea 100644 --- a/src/app/ModuleWidget.cpp +++ b/src/app/ModuleWidget.cpp @@ -405,9 +405,8 @@ void ModuleWidget::onDragEnd(const event::DragEnd &e) { void ModuleWidget::onDragMove(const event::DragMove &e) { if (!settings::lockModules) { - math::Rect newBox = box; - newBox.pos = APP->scene->rack->mousePos.minus(dragPos); - APP->scene->rack->requestModuleBoxNearest(this, newBox); + math::Vec pos = APP->scene->rack->mousePos.minus(dragPos); + APP->scene->rack->requestModulePosNearest(this, pos); } } diff --git a/src/app/RackRail.cpp b/src/app/RackRail.cpp index 617ec364..a7a4587c 100644 --- a/src/app/RackRail.cpp +++ b/src/app/RackRail.cpp @@ -58,7 +58,7 @@ void RackRail::draw(const DrawArgs &args) { // Bus board const float busBoardWidth = RACK_GRID_WIDTH * 20; - const float busBoardHeight = mm2px(38.27639); + const float busBoardHeight = busBoardSvg->handle->height; const float busBoardY = y + (RACK_GRID_HEIGHT - busBoardHeight) / 2; for (float x = 0; x < box.size.x; x += busBoardWidth) { nvgSave(args.vg); diff --git a/src/app/RackWidget.cpp b/src/app/RackWidget.cpp index a192c117..c783c1a5 100644 --- a/src/app/RackWidget.cpp +++ b/src/app/RackWidget.cpp @@ -12,6 +12,7 @@ #include "osdialog.h" #include #include +#include namespace rack { @@ -371,12 +372,12 @@ void RackWidget::addModule(ModuleWidget *m) { RackWidget_updateAdjacent(this); } -void RackWidget::addModuleAtMouse(ModuleWidget *m) { - assert(m); +void RackWidget::addModuleAtMouse(ModuleWidget *mw) { + assert(mw); // Move module nearest to the mouse position - m->box.pos = mousePos.minus(m->box.size.div(2)); - requestModuleBoxNearest(m, m->box); - addModule(m); + math::Vec pos = mousePos.minus(mw->box.size.div(2)); + requestModulePosNearest(mw, pos); + addModule(mw); } void RackWidget::removeModule(ModuleWidget *m) { @@ -399,47 +400,75 @@ void RackWidget::removeModule(ModuleWidget *m) { moduleContainer->removeChild(m); } -bool RackWidget::requestModuleBox(ModuleWidget *m, math::Rect requestedBox) { +bool RackWidget::requestModulePos(ModuleWidget *mw, math::Vec pos) { // Check intersection with other modules - for (widget::Widget *m2 : moduleContainer->children) { + math::Rect mwBox = math::Rect(pos, mw->box.size); + for (widget::Widget *mw2 : moduleContainer->children) { // Don't intersect with self - if (m == m2) + if (mw == mw2) + continue; + // Don't intersect with invisible modules + if (!mw2->visible) continue; - if (requestedBox.isIntersecting(m2->box)) { + // Check intersection + if (mwBox.isIntersecting(mw2->box)) { return false; } } // Accept requested position - m->box = requestedBox; + mw->box = mwBox; RackWidget_updateAdjacent(this); return true; } -bool RackWidget::requestModuleBoxNearest(ModuleWidget *m, math::Rect requestedBox) { - // Create possible positions - int x0 = std::round(requestedBox.pos.x / RACK_GRID_WIDTH); - int y0 = std::round(requestedBox.pos.y / RACK_GRID_HEIGHT); - std::vector positions; - for (int y = y0 - 4; y < y0 + 4; y++) { - for (int x = x0 - 200; x < x0 + 200; x++) { - positions.push_back(math::Vec(x * RACK_GRID_WIDTH, y * RACK_GRID_HEIGHT)); +bool RackWidget::requestModulePosNearest(ModuleWidget *mw, math::Vec pos) { + // Dijkstra's algorithm to generate a sorted list of Vecs closest to `pos`. + + // Comparison of distance of Vecs to `pos` + auto cmpNearest = [&](const math::Vec &a, const math::Vec &b) { + return a.minus(pos).square() > b.minus(pos).square(); + }; + // Comparison of dictionary order of Vecs + auto cmp = [&](const math::Vec &a, const math::Vec &b) { + if (a.x != b.x) + return a.x < b.x; + return a.y < b.y; + }; + // Priority queue sorted by distance from `pos` + std::priority_queue, decltype(cmpNearest)> queue(cmpNearest); + // Set of already-tested Vecs + std::set visited(cmp); + // Seed priority queue with closest Vec + math::Vec closestPos = pos.div(RACK_GRID_SIZE).round().mult(RACK_GRID_SIZE); + queue.push(closestPos); + + while (!queue.empty()) { + math::Vec testPos = queue.top(); + // Check testPos + if (requestModulePos(mw, testPos)) + return true; + // Move testPos to visited set + queue.pop(); + visited.insert(testPos); + + // Add adjacent Vecs + static const std::vector deltas = { + math::Vec(-1, 0).mult(RACK_GRID_SIZE), + math::Vec(1, 0).mult(RACK_GRID_SIZE), + math::Vec(0, -1).mult(RACK_GRID_SIZE), + math::Vec(0, 1).mult(RACK_GRID_SIZE), + }; + for (math::Vec delta : deltas) { + math::Vec newPos = testPos.plus(delta); + if (visited.find(newPos) == visited.end()) { + queue.push(newPos); + } } } - // Sort possible positions by distance to the requested position - std::sort(positions.begin(), positions.end(), [requestedBox](math::Vec a, math::Vec b) { - return a.minus(requestedBox.pos).norm() < b.minus(requestedBox.pos).norm(); - }); - - // Find a position that does not collide - for (math::Vec position : positions) { - math::Rect newBox = requestedBox; - newBox.pos = position; - if (requestModuleBox(m, newBox)) - return true; - } - // We failed to find a box with this brute force algorithm. + // We failed to find a box. This shouldn't happen on an infinite rack. + assert(0); return false; }