Sat, 04 Mar 2017 00:54:46 +0200
Added polar grid rendering (which is disabled for now).
src/basics.cpp | file | annotate | diff | comparison | revisions | |
src/basics.h | file | annotate | diff | comparison | revisions | |
src/canvas.cpp | file | annotate | diff | comparison | revisions | |
src/grid.cpp | file | annotate | diff | comparison | revisions | |
src/grid.h | file | annotate | diff | comparison | revisions |
--- a/src/basics.cpp Fri Mar 03 23:23:28 2017 +0200 +++ b/src/basics.cpp Sat Mar 04 00:54:46 2017 +0200 @@ -280,3 +280,83 @@ return lines; } + +/* + * Computes the shortest distance from a point to a rectangle. + * + * The code originates from the Unity3D wiki, and was translated from C# to Qt by me (Teemu Piippo): + * + * Original code: + * http://wiki.unity3d.com/index.php/Distance_from_a_point_to_a_rectangle + * + * Copyright 2013 Philip Peterson. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +qreal distanceFromPointToRectangle(const QPointF& point, const QRectF& rectangle) +{ + // Calculate a distance between a point and a rectangle. + // The area around/in the rectangle is defined in terms of + // several regions: + // + // O--x + // | + // y + // + // + // I | II | III + // ======+==========+====== --yMin + // VIII | IX (in) | IV + // ======+==========+====== --yMax + // VII | VI | V + // + // + // Note that the +y direction is down because of Unity's GUI coordinates. + + if (point.x() < rectangle.left()) + { + // Region I, VIII, or VII + if (point.y() < rectangle.top()) // I + return QLineF {point, rectangle.topLeft()}.length(); + else if (point.y() > rectangle.bottom()) // VII + return QLineF {point, rectangle.bottomLeft()}.length(); + else // VIII + return rectangle.left() - point.x(); + } + else if (point.x() > rectangle.right()) + { + // Region III, IV, or V + if (point.y() < rectangle.top()) // III + return QLineF {point, rectangle.topRight()}.length(); + else if (point.y() > rectangle.bottom()) // V + return QLineF {point, rectangle.bottomRight()}.length(); + else // IV + return point.x() - rectangle.right(); + } + else + { + // Region II, IX, or VI + if (point.y() < rectangle.top()) // II + return rectangle.top() - point.y(); + else if (point.y() > rectangle.bottom()) // VI + return point.y() - rectangle.bottom(); + else // IX + return 0; + } +}
--- a/src/basics.h Fri Mar 03 23:23:28 2017 +0200 +++ b/src/basics.h Sat Mar 04 00:54:46 2017 +0200 @@ -202,6 +202,7 @@ double getRadialPoint(int segment, int divisions, double(*func)(double)); QVector<QLineF> makeCircle(int segments, int divisions, double radius); +qreal distanceFromPointToRectangle(const QPointF& point, const QRectF& rectangle); /* * Implements a ring adapter over T. This class corrects indices given to the element-operator so that they're within bounds. @@ -327,3 +328,57 @@ else return x / qAbs(x); } + +/* + * Returns the maximum of a single parameter (the parameter itself). + */ +template <typename T> +T max(T a) +{ + return a; +} + +/* + * Returns the maximum of two parameters. + */ +template <typename T> +T max(T a, T b) +{ + return a > b ? a : b; +} + +/* + * Returns the maximum of n parameters. + */ +template <typename T, typename... Rest> +T max(T a, Rest&&... rest) +{ + return max(a, max(rest...)); +} + +/* + * Returns the minimum of a single parameter (the parameter itself). + */ +template <typename T> +T min(T a) +{ + return a; +} + +/* + * Returns the minimum of two parameters. + */ +template <typename T> +T min(T a, T b) +{ + return a < b ? a : b; +} + +/* + * Returns the minimum of n parameters. + */ +template <typename T, typename... Rest> +T min(T a, Rest&&... rest) +{ + return min(a, min(rest...)); +}
--- a/src/canvas.cpp Fri Mar 03 23:23:28 2017 +0200 +++ b/src/canvas.cpp Sat Mar 04 00:54:46 2017 +0200 @@ -95,47 +95,113 @@ */ void Canvas::drawFixedCameraBackdrop() { + static const enum { Cartesian, Polar } gridType = Cartesian; + // Find the top left corner of the grid Vertex topLeft = currentCamera().idealize(currentCamera().convert2dTo3d({0, 0})); Vertex bottomRight = currentCamera().idealize(currentCamera().convert2dTo3d({width(), height()})); qreal gridSize = grid()->coordinateSnap(); - qreal x0 = sign(topLeft.x()) * (fabs(topLeft.x()) - fmod(fabs(topLeft.x()), gridSize)); - qreal y0 = sign(topLeft.y()) * (fabs(topLeft.y()) - fmod(fabs(topLeft.y()), gridSize)); glEnable(GL_LINE_STIPPLE); glBegin(GL_LINES); - static const auto prepareGridLine = [](qreal value) -> bool + if (gridType == Cartesian) { - if (not isZero(value)) + qreal x0 = sign(topLeft.x()) * (fabs(topLeft.x()) - fmod(fabs(topLeft.x()), gridSize)); + qreal y0 = sign(topLeft.y()) * (fabs(topLeft.y()) - fmod(fabs(topLeft.y()), gridSize)); + + static const auto prepareGridLine = [](qreal value) -> bool { - if (isZero(fmod(value, 10.0))) - glColor4f(0, 0, 0, 0.6); + if (not isZero(value)) + { + if (isZero(fmod(value, 10.0))) + glColor4f(0, 0, 0, 0.6); + else + glColor4f(0, 0, 0, 0.25); + + return true; + } else - glColor4f(0, 0, 0, 0.25); + { + return false; + } + }; - return true; + for (qreal x = x0; x < bottomRight.x(); x += gridSize) + { + if (prepareGridLine(x)) + { + glVertex(currentCamera().realize({x, -10000, 999})); + glVertex(currentCamera().realize({x, 10000, 999})); + } } - else + + for (qreal y = y0; y < bottomRight.y(); y += gridSize) { - return false; - } - }; - - for (qreal x = x0; x < bottomRight.x(); x += gridSize) - { - if (prepareGridLine(x)) - { - glVertex(currentCamera().realize({x, -10000, 999})); - glVertex(currentCamera().realize({x, 10000, 999})); + if (prepareGridLine(y)) + { + glVertex(currentCamera().realize({-10000, y, 999})); + glVertex(currentCamera().realize({10000, y, 999})); + } } } - - for (qreal y = y0; y < bottomRight.y(); y += gridSize) + else { - if (prepareGridLine(y)) + const QPointF pole = grid()->pole(); + const qreal size = grid()->coordinateSnap(); + Vertex topLeft = currentCamera().idealize(currentCamera().convert2dTo3d({0, 0})); + Vertex bottomRight = currentCamera().idealize(currentCamera().convert2dTo3d({width(), height()})); + QPointF topLeft2d {topLeft.x(), topLeft.y()}; + QPointF bottomLeft2d {topLeft.x(), bottomRight.y()}; + QPointF bottomRight2d {bottomRight.x(), bottomRight.y()}; + QPointF topRight2d {bottomRight.x(), topLeft.y()}; + qreal smallestRadius = distanceFromPointToRectangle(pole, QRectF{topLeft2d, bottomRight2d}); + qreal largestRadius = max(QLineF {topLeft2d, pole}.length(), + QLineF {bottomLeft2d, pole}.length(), + QLineF {bottomRight2d, pole}.length(), + QLineF {topRight2d, pole}.length()); + + // Snap the radii to the grid. + smallestRadius = round(smallestRadius / size) * size; + largestRadius = round(largestRadius / size) * size; + + // Is the pole at (0, 0)? If so, then don't render the polar axes above the real ones. + bool poleIsOrigin = isZero(pole.x()) and isZero(pole.y()); + glColor4f(0, 0, 0, 0.25); + + // Render the axes + for (int i = 0; i < grid()->polarDivisions() / 2; ++i) { - glVertex(currentCamera().realize({-10000, y, 999})); - glVertex(currentCamera().realize({10000, y, 999})); + qreal azimuth = (2.0 * pi) * i / grid()->polarDivisions(); + + if (not poleIsOrigin or not isZero(fmod(azimuth, pi / 4))) + { + QPointF extremum = {cos(azimuth) * 10000, sin(azimuth) * 10000}; + QPointF A = pole + extremum; + QPointF B = pole - extremum; + glVertex(currentCamera().realize({A.x(), A.y(), 999})); + glVertex(currentCamera().realize({B.x(), B.y(), 999})); + } + } + + for (qreal radius = smallestRadius; radius <= largestRadius; radius += size) + { + if (not isZero(radius)) + { + Vertex points[48]; + + for (int i = 0; i < grid()->polarDivisions(); ++i) + { + qreal azimuth = (2.0 * pi) * i / grid()->polarDivisions(); + QPointF point = pole + QPointF {radius * cos(azimuth), radius * sin(azimuth)}; + points[i] = currentCamera().realize({point.x(), point.y(), 999}); + } + + for (int i = 0; i < grid()->polarDivisions(); ++i) + { + glVertex(points[i]); + glVertex(ring(points, grid()->polarDivisions())[i + 1]); + } + } } }
--- a/src/grid.cpp Fri Mar 03 23:23:28 2017 +0200 +++ b/src/grid.cpp Sat Mar 04 00:54:46 2017 +0200 @@ -72,3 +72,28 @@ double size = coordinateSnap(); return {round(point.x() / size) * size, round(point.y() / size) * size}; } + +/* + * Returns the pole of the grid, in ideal X/Y co-ordinates. Z is left up for the caller to decide. + */ +QPointF Grid::pole() const +{ + return {12, -17}; +} + +/* + * Returns the amount of divisions (slices) to be used in the polar grid. + */ +int Grid::polarDivisions() const +{ + switch (m_config->grid()) + { + default: + case Coarse: + case Medium: + return LowResolution; + + case Fine: + return HighResolution; + } +}