1 /*
2     SPDX-FileCopyrightText: 2010 Henry de Valence <hdevalence@gmail.com>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #pragma once
8 
9 #ifdef KSTARS_LITE
10 #include "skymaplite.h"
11 #else
12 #include "skymap.h"
13 #endif
14 #include "skyobjects/skypoint.h"
15 
16 #if __GNUC__ > 5
17 #pragma GCC diagnostic push
18 #pragma GCC diagnostic ignored "-Wignored-attributes"
19 #endif
20 #if __GNUC__ > 6
21 #pragma GCC diagnostic ignored "-Wint-in-bool-context"
22 #endif
23 #include <Eigen/Core>
24 #if __GNUC__ > 5
25 #pragma GCC diagnostic pop
26 #endif
27 
28 #include <QPointF>
29 
30 #include <cstddef>
31 #include <cmath>
32 
33 class KStarsData;
34 
35 /** This is just a container that holds information needed to do projections. */
36 class ViewParams
37 {
38     public:
39         float width, height;
40         float zoomFactor;
41         bool useRefraction;
42         bool useAltAz;
43         bool fillGround; ///<If the ground is filled, then points below horizon are invisible
44         SkyPoint *focus;
ViewParams()45         ViewParams() : width(0), height(0), zoomFactor(0),
46             useRefraction(false), useAltAz(false), fillGround(false),
47             focus(nullptr) {}
48 };
49 
50 /**
51  * @class Projector
52  *
53  * The Projector class is the primary class that serves as an interface to handle projections.
54  */
55 class Projector
56 {
57         Q_GADGET
58     public:
59         /**
60          * Constructor.
61          *
62          * @param p the ViewParams for this projection
63          */
64         explicit Projector(const ViewParams &p);
65 
66         virtual ~Projector() = default;
67 
68         /** Update cached values for projector */
69         void setViewParams(const ViewParams &p);
viewParams()70         ViewParams viewParams() const
71         {
72             return m_vp;
73         }
74 
75         enum Projection
76         {
77             Lambert,
78             AzimuthalEquidistant,
79             Orthographic,
80             Equirectangular,
81             Stereographic,
82             Gnomonic,
83             UnknownProjection
84         };
85         Q_ENUM(Projection)
86 
87         /** Return the type of this projection */
88         Q_INVOKABLE virtual Projection type() const = 0;
89 
90         /** Return the FOV of this projection */
91         double fov() const;
92 
93         /**
94          * Check if the current point on screen is a valid point on the sky. This is needed
95          * to avoid a crash of the program if the user clicks on a point outside the sky (the
96          * corners of the sky map at the lowest zoom level are the invalid points).
97          * @param p the screen pixel position
98          */
99         virtual bool unusablePoint(const QPointF &p) const;
100 
101         /**
102          * Given the coordinates of the SkyPoint argument, determine the
103          * pixel coordinates in the SkyMap.
104          *
105          * Since most of the projections used by KStars are very similar,
106          * if this function were to be reimplemented in each projection subclass
107          * we would end up changing maybe 5 or 6 lines out of 150.
108          * Instead, we have a default implementation that uses the projectionK
109          * and projectionL functions to take care of the differences between
110          * e.g. Orthographic and Stereographic. There is also the cosMaxFieldAngle
111          * function, which is used for testing whether a point is on the visible
112          * part of the projection, and the radius function which gives the radius of
113          * the projection in screen coordinates.
114          *
115          * While this seems ugly, it is less ugly than duplicating 150 loc to change 5.
116          *
117          * @return Eigen::Vector2f containing screen pixel x, y coordinates of SkyPoint.
118          * @param o pointer to the SkyPoint for which to calculate x, y coordinates.
119          * @param oRefract true = use Options::useRefraction() value.
120          *   false = do not use refraction.  This argument is only needed
121          *   for the Horizon, which should never be refracted.
122          * @param onVisibleHemisphere pointer to a bool to indicate whether the point is
123          *   on the visible part of the Celestial Sphere.
124          */
125         virtual Eigen::Vector2f toScreenVec(const SkyPoint *o, bool oRefract = true,
126                                             bool *onVisibleHemisphere = nullptr) const;
127 
128         /**
129          * This is exactly the same as toScreenVec but it returns a QPointF.
130          * It just calls toScreenVec and converts the result.
131          * @see toScreenVec()
132          */
133         QPointF toScreen(const SkyPoint *o, bool oRefract = true, bool *onVisibleHemisphere = nullptr) const;
134 
135         /**
136          * @short Determine RA, Dec coordinates of the pixel at (dx, dy), which are the
137          * screen pixel coordinate offsets from the center of the Sky pixmap.
138          * @param p the screen pixel position to convert
139          * @param LST pointer to the local sidereal time, as a dms object.
140          * @param lat pointer to the current geographic laitude, as a dms object
141          * @param onlyAltAz the returned SkyPoint's RA & DEC are not computed, only Alt/Az.
142          */
143         virtual SkyPoint fromScreen(const QPointF &p, dms *LST, const dms *lat, bool onlyAltAz = false) const;
144 
145         /**
146          * ASSUMES *p1 did not clip but *p2 did.  Returns the QPointF on the line
147          * between *p1 and *p2 that just clips.
148          */
149         QPointF clipLine(SkyPoint *p1, SkyPoint *p2) const;
150 
151         /**
152          * ASSUMES *p1 did not clip but *p2 did.  Returns the Eigen::Vector2f on the line
153          * between *p1 and *p2 that just clips.
154          */
155         Eigen::Vector2f clipLineVec(SkyPoint *p1, SkyPoint *p2) const;
156 
157         /** Check whether the projected point is on-screen */
158         bool onScreen(const QPointF &p) const;
159         bool onScreen(const Eigen::Vector2f &p) const;
160 
161         /**
162          * @short Determine if the skypoint p is likely to be visible in the display window.
163          *
164          * checkVisibility() is an optimization function.  It determines whether an object
165          * appears within the bounds of the skymap window, and therefore should be drawn.
166          * The idea is to save time by skipping objects which are off-screen, so it is
167          * absolutely essential that checkVisibility() is significantly faster than
168          * the computations required to draw the object to the screen.
169          *
170          * If the ground is to be filled, the function first checks whether the point is
171          * below the horizon, because they will be covered by the ground anyways.
172          * Importantly, it does not call the expensive EquatorialToHorizontal function.
173          * This means that the horizontal coordinates MUST BE CORRECT! The vast majority
174          * of points are already synchronized, so recomputing the horizontal coordinates is
175          * a waste.
176          *
177          * The function then checks the difference between the Declination/Altitude
178          * coordinate of the Focus position, and that of the point p.  If the absolute
179          * value of this difference is larger than fov, then the function returns false.
180          * For most configurations of the sky map window, this simple check is enough to
181          * exclude a large number of objects.
182          *
183          * Next, it determines if one of the poles of the current Coordinate System
184          * (Equatorial or Horizontal) is currently inside the sky map window.  This is
185          * stored in the member variable 'bool SkyMap::isPoleVisible, and is set by the
186          * function SkyMap::setMapGeometry(), which is called by SkyMap::paintEvent().
187          * If a Pole is visible, then it will return true immediately.  The idea is that
188          * when a pole is on-screen it is computationally expensive to determine whether
189          * a particular position is on-screen or not: for many valid Dec/Alt values, *all*
190          * values of RA/Az will indeed be onscreen, but for other valid Dec/Alt values,
191          * only *most* RA/Az values are onscreen.  It is cheaper to simply accept all
192          * "horizontal" RA/Az values, since we have already determined that they are
193          * on-screen in the "vertical" Dec/Alt coordinate.
194          *
195          * Finally, if no Pole is onscreen, it checks the difference between the Focus
196          * position's RA/Az coordinate and that of the point p.  If the absolute value of
197          * this difference is larger than XMax, the function returns false.  Otherwise,
198          * it returns true.
199          *
200          * @param p pointer to the skypoint to be checked.
201          * @return true if the point p was found to be inside the Sky map window.
202          * @see SkyMap::setMapGeometry()
203          * @see SkyMap::fov()
204          * @note If you are creating skypoints using equatorial coordinates, then
205          * YOU MUST CALL EQUATORIALTOHORIZONTAL BEFORE THIS FUNCTION!
206          */
207         bool checkVisibility(const SkyPoint *p) const;
208 
209         /**
210          * Determine the on-screen position angle of a SkyPont with recept with NCP.
211          * This is the object's sky position angle (w.r.t. North).
212          * of "North" at the position of the object (w.r.t. the screen Y-axis).
213          * The latter is determined by constructing a test point with the same RA but
214          * a slightly increased Dec as the object, and calculating the angle w.r.t. the
215          * Y-axis of the line connecting the object to its test point.
216          */
217         double findNorthPA(const SkyPoint *o, float x, float y) const;
218 
219         /**
220          * Determine the on-screen position angle of a SkyObject.  This is the sum
221          * of the object's sky position angle (w.r.t. North), and the position angle
222          * of "North" at the position of the object (w.r.t. the screen Y-axis).
223          * The latter is determined by constructing a test point with the same RA but
224          * a slightly increased Dec as the object, and calculating the angle w.r.t. the
225          * Y-axis of the line connecting the object to its test point.
226          */
227         double findPA(const SkyObject *o, float x, float y) const;
228 
229         /**
230          * Get the ground polygon
231          * @param labelpoint This point will be set to something suitable for attaching a label
232          * @param drawLabel this tells whether to draw a label.
233          * @return the ground polygon
234          */
235         virtual QVector<Eigen::Vector2f> groundPoly(SkyPoint *labelpoint = nullptr, bool *drawLabel = nullptr) const;
236 
237         /**
238          * @brief updateClipPoly calculate the clipping polygen given the current FOV.
239          */
240         virtual void updateClipPoly();
241 
242         /**
243          * @return the clipping polygen covering the visible sky area. Anything outside this polygon is
244          * clipped by QPainter.
245          */
246         virtual QPolygonF clipPoly() const;
247 
248     protected:
249         /**
250          * Get the radius of this projection's sky circle.
251          * @return the radius in radians
252          */
radius()253         virtual double radius() const
254         {
255             return 2 * M_PI;
256         }
257 
258         /**
259          * This function handles some of the projection-specific code.
260          * @see toScreen()
261          */
projectionK(double x)262         virtual double projectionK(double x) const
263         {
264             return x;
265         }
266 
267         /**
268          * This function handles some of the projection-specific code.
269          * @see toScreen()
270          */
projectionL(double x)271         virtual double projectionL(double x) const
272         {
273             return x;
274         }
275 
276         /**
277          * This function returns the cosine of the maximum field angle, i.e., the maximum angular
278          * distance from the focus for which a point should be projected. Default is 0, i.e.,
279          * 90 degrees.
280          */
cosMaxFieldAngle()281         virtual double cosMaxFieldAngle() const
282         {
283             return 0;
284         }
285 
286         /**
287          * Helper function for drawing ground.
288          * @return the point with Alt = 0, az = @p az
289          */
290         static SkyPoint pointAt(double az);
291 
292         KStarsData *m_data { nullptr };
293         ViewParams m_vp;
294         double m_sinY0 { 0 };
295         double m_cosY0 { 0 };
296         double m_fov { 0 };
297         QPolygonF m_clipPolygon;
298 
299     private:
300         //Used by CheckVisibility
301         double m_xrange { 0 };
302         bool m_isPoleVisible { false };
303 };
304