/*
Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
This file is part of CamCOPS.
CamCOPS 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.
CamCOPS 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 CamCOPS. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QPointF>
#include <QtGlobal>
#include <QWidget> // for QWIDGETSIZE_MAX
#include "graphics/linesegment.h"
namespace geometry
{
/*
Standard Cartesian/polar coordinate systems
===============================================================================
- Positive x is to the right.
- Positive y is UP.
- Positive theta is ANTICLOCKWISE, and theta = 0 is at the 3 o'clock position.
... so for a point (x=1, y=0), positive rotation moves it in the direction of
INCREASING y.
When you rotate by theta, you rotate anticlockwise.
https://en.wikipedia.org/wiki/Rotation_of_axes
The Qt coordinate system
===============================================================================
- Positive x is to the right.
- Positive y is DOWN. (This matches commonplace screen coordinates; the origin
is at the top left.)
https://doc.qt.io/qt-6.5/coordsys.html
- When you rotate a coordinate system, rotation angles are CLOCKWISE.
https://doc.qt.io/qt-6.5/qpainter.html#rotate
... so for a point (x=1, y=0), positive rotation moves it in the direction of
INCREASING y.
- But when you draw a pie, rotation angles are ANTICLOCKWISE, and zero degrees
is in the 3 o'clock position.
https://doc.qt.io/qt-6.5/qpainter.html#drawPie
- Other ANTICLOCKWISE bits:
- QTranform::rotate
https://doc.qt.io/qt-6.5/qtransform.html#rotate
- Qt also uses a "positive ANTICLOCKWISE" system for its graphs, though that's
more obvious as it's mimicking standard graph geometry here.
https://doc.qt.io/qt-6.5/qtcharts-polarchart-example.html
Which representation to use internally for polar coordinates?
===============================================================================
- Any sophisticated representations are going to assume a standard Cartesian
system and the most important part of that isn't "up"/"down" but the fact
that positive angles are anticlockwise WITH RESPECT TO "x right, y up", i.e.
that positive rotation moves the point (x=1, y=0) in the direction of
INCREASING y.
That's helpful so we can use standard representations like
x = r * cos(theta) y = r * sin(theta)
not
x = r * cos(theta) y = -r * sin(theta)
- That means angles are clockwise in the standard Qt coordinates.
- So we'll use that when we refer to "polar", and convert explicitly for those
places (like pie drawing) where anticlockwise angles are required.
Compass headings
===============================================================================
- These are based on the idea of "North up" (though also support a
transformation via an "alternative North"), and positive rotation is
CLOCKWISE.
Other notes on Qt coordinates
===============================================================================
- QPainter::drawText()
"The y-position is used as the baseline of the font."
0123456789
0 |
1 SSOOMMEE TTEEXXTT
2 SSOOMMEE TTEEXXTT
3 - SSOOMMEE TTEEXXTT - [descenders go below line?]
4 |
So if you draw at y = 3, it'll be bottom-aligned there.
To top-align it, add its height to the y coordinate.
To vcentre-align it, add half its height to the y coordinate.
To left-align it, plot at the unmodified x coordinate.
To centre-align it, subtract half its width from the x coordinate.
To right-align it, subtract its width from the x coordinate.
*/
extern const qreal DEG_0;
extern const qreal DEG_90;
extern const qreal DEG_270;
extern const qreal DEG_180;
extern const qreal DEG_360;
// Converts degrees to sixteenths of a degree.
int sixteenthsOfADegree(qreal degrees);
// Converts clockwise to anticlockwise degrees (!).
inline qreal clockwiseToAnticlockwise(qreal degrees) { return -degrees; }
// Converts anticlockwise to clockwise degrees (!).
inline qreal anticlockwiseToClockwise(qreal degrees) { return -degrees; }
// Returns a heading normalized to [0, 360).
qreal normalizeHeading(qreal heading_deg);
// Are the two headings fuzzy-equal?
bool headingNearlyEq(qreal heading_deg, qreal value_deg);
// Is heading_deg in the range (first_bound_deg, second_bound_deg)?
// Or, if inclusive is true, in [first_bound_deg, second_bound_deg]?
bool headingInRange(qreal first_bound_deg,
qreal heading_deg,
qreal second_bound_deg,
bool inclusive = false);
// Converts a compass heading from a "true" to a "pseudo" system, based on
// pseudo_north_deg.
qreal convertHeadingFromTrueNorth(qreal true_north_heading_deg,
qreal pseudo_north_deg,
bool normalize = true);
// Converts a compass heading from a "pseudo" to a "true" system, based on
// pseudo_north_deg.
qreal convertHeadingToTrueNorth(qreal pseudo_north_heading_deg,
qreal pseudo_north_deg,
bool normalize = true);
// Returns a point (relative to the origin) equivalent to the specified
// polar coordinates.
QPointF polarToCartesian(qreal r, qreal theta_degrees);
// Returns the distance between two points.
qreal distanceBetween(const QPointF& from, const QPointF& to);
// Returns the heading (in polar degrees, 0 = along x axis), "from" -> "to".
qreal polarThetaDeg(const QPointF& from, const QPointF& to);
// Returns the heading (in polar degrees, 0 = along x axis), origin -> "to".
qreal polarThetaDeg(const QPointF& to);
// Converts a polar angle to a compass heading.
qreal polarThetaToHeading(qreal theta_deg, qreal north_deg = 0.0);
// Converts a compass heading to a polar angle.
qreal headingToPolarThetaDeg(qreal heading_deg, qreal north_deg = 0.0,
bool normalize = true);
// Returns a compass heading, "from" -> "to".
qreal headingDegrees(const QPointF& from, const QPointF& to,
qreal north_deg = 0.0);
// Return a line segment starting at "point", travelling in compass direction
// "heading" (where north_deg indicates our North direction), with line length
// "radius".
LineSegment lineFromPointInHeadingWithRadius(const QPointF& point,
qreal heading_deg,
qreal north_deg = 0.0,
qreal radius = QWIDGETSIZE_MAX);
// (1) Draw a line from "from" to "to".
// (2) Draw a line from "point" in direction "heading", where increasing
// values of "heading" are clockwise, and a heading of 0 degrees is
// the North direction (where that is defined by north_deg degrees
// clockwise of "screen up").
// (3) Do the two lines cross?
bool lineCrossesHeadingWithinRadius(const QPointF& from, const QPointF& to,
const QPointF& point, qreal heading_deg,
qreal north_deg = 0.0,
qreal radius = QWIDGETSIZE_MAX);
// Does the line "from" -> "to" pass below "point"?
bool linePassesBelowPoint(const QPointF& from, const QPointF& to,
const QPointF& point);
} // namespace geometry