Warning, file /education/kstars/kstars/ekos/align/rotations.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 SPDX-FileCopyrightText: 2021 Hy Murveit <hy@murveit.com> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "rotations.h" 0008 0009 #include <cmath> 0010 0011 // In the coordinate system used in this file: 0012 // x points forward 0013 // y points to the left 0014 // z points up. 0015 // In this system, theta is the angle between x & y and is related 0016 // to our azimuth: theta = 90-azimuth. 0017 // Phi is the angle away from z, and is related to our altitude as 90-altitude. 0018 0019 namespace Rotations 0020 { 0021 0022 double d2r(double degrees) 0023 { 0024 return 2 * M_PI * degrees / 360.0; 0025 } 0026 0027 double r2d(double radians) 0028 { 0029 return 360.0 * radians / (2 * M_PI); 0030 } 0031 0032 V3 V3::normal(const V3 &v1, const V3 &v2, const V3 &v3) 0033 { 0034 // First subtract d21 = V2-V1; d32 = V3-V2 0035 const V3 d21 = V3(v2.x() - v1.x(), v2.y() - v1.y(), v2.z() - v1.z()); 0036 const V3 d32 = V3(v3.x() - v2.x(), v3.y() - v2.y(), v3.z() - v2.z()); 0037 // Now take the cross-product of d21 and d31 0038 const V3 cross = V3(d21.y() * d32.z() - d21.z() * d32.y(), 0039 d21.z() * d32.x() - d21.x() * d32.z(), 0040 d21.x() * d32.y() - d21.y() * d32.x()); 0041 // Finally normalize cross so that it is a unit vector. 0042 const double lenSq = cross.x() * cross.x() + cross.y() * cross.y() + cross.z() * cross.z(); 0043 if (lenSq == 0.0) return V3(); 0044 const double len = sqrt(lenSq); 0045 // Should we also fail if len < e.g. 5e-8 ?? 0046 return V3(cross.x() / len, cross.y() / len, cross.z() / len); 0047 } 0048 0049 double V3::length() 0050 { 0051 return sqrt(X * X + Y * Y + Z * Z); 0052 } 0053 0054 V3 azAlt2xyz(const QPointF &azAlt) 0055 { 0056 // Convert the new point to xyz 0057 // See https://mathworld.wolfram.com/SphericalCoordinates.html 0058 const double azRadians = d2r(azAlt.x()); 0059 const double altRadians = d2r(azAlt.y()); 0060 0061 const double theta = -azRadians; 0062 const double phi = (M_PI / 2.0) - altRadians; 0063 const double x = cos(theta) * sin(phi); 0064 const double y = sin(theta) * sin (phi); 0065 const double z = cos(phi); 0066 return V3(x, y, z); 0067 0068 } 0069 0070 QPointF xyz2azAlt(const V3 &xyz) 0071 { 0072 // Deal with degenerate values for the atan along the meridian (y == 0). 0073 if (xyz.y() == 0.0 && xyz.x() == 0.0) 0074 { 0075 // Straight overhead 0076 return QPointF(0.0, 90.0); 0077 } 0078 0079 const double azRadians = (xyz.y() == 0) ? 0.0 : -atan2(xyz.y(), xyz.x()); 0080 const double altRadians = (M_PI / 2.0) - acos(xyz.z()); 0081 0082 return QPointF(r2d(azRadians), r2d(altRadians)); 0083 } 0084 0085 V3 haDec2xyz(const QPointF &haDec, double latitude) 0086 { 0087 const double haRadians = d2r(haDec.x()); 0088 // Since dec=90 points at the pole. 0089 const double decRadians = d2r(haDec.y() - 90); 0090 0091 const double x = cos(decRadians); 0092 const double y = sin(haRadians) * sin(decRadians); 0093 const double z = cos(haRadians) * sin(decRadians); 0094 return rotateAroundY(V3(x, y, z), -fabs(latitude)); 0095 } 0096 0097 QPointF xyz2haDec(const V3 &xyz, double latitude) 0098 { 0099 0100 const V3 pt = rotateAroundY(xyz, latitude); 0101 const double ha = r2d(atan2(pt.y(), pt.z())); 0102 0103 V3 pole = Rotations::azAlt2xyz(QPointF(0, fabs(latitude))); 0104 const double dec = 90 - getAngle(xyz, pole); 0105 return QPointF(ha, dec); 0106 } 0107 0108 V3 getAxis(const V3 &p1, const V3 &p2, const V3 &p3) 0109 { 0110 return V3::normal(p1, p2, p3); 0111 } 0112 0113 // Returns the angle between two points on a sphere using the spherical law of 0114 // cosines: https://en.wikipedia.org/wiki/Great-circle_distance 0115 double getAngle(const V3 &p1, const V3 &p2) 0116 { 0117 QPointF a1 = xyz2azAlt(p1); 0118 QPointF a2 = xyz2azAlt(p2); 0119 return r2d(acos(sin(d2r(a1.y())) * sin(d2r(a2.y())) + 0120 cos(d2r(a1.y())) * cos(d2r(a2.y())) * cos(d2r(a1.x()) - d2r(a2.x())))); 0121 } 0122 0123 // Using the equations in 0124 // https://sites.google.com/site/glennmurray/Home/rotation-matrices-and-formulas/rotation-about-an-arbitrary-axis-in-3-dimensions 0125 // This rotates point around axis (which should be a unit vector) by degrees. 0126 V3 rotateAroundAxis(const V3 &point, const V3 &axis, double degrees) 0127 { 0128 const double cosAngle = cos(d2r(degrees)); 0129 const double sinAngle = sin(d2r(degrees)); 0130 const double pointDotAxis = (point.x() * axis.x() + point.y() * axis.y() + point.z() * axis.z()); 0131 const double x = axis.x() * pointDotAxis * (1.0 - cosAngle) + point.x() * cosAngle + (-axis.z() * point.y() + axis.y() * 0132 point.z()) * sinAngle; 0133 const double y = axis.y() * pointDotAxis * (1.0 - cosAngle) + point.y() * cosAngle + (axis.z() * point.x() + -axis.x() * 0134 point.z()) * sinAngle; 0135 const double z = axis.z() * pointDotAxis * (1.0 - cosAngle) + point.z() * cosAngle + (-axis.y() * point.x() + axis.x() * 0136 point.y()) * sinAngle; 0137 return V3(x, y, z); 0138 } 0139 0140 // Simpler version of above for altitude rotations, our main case. 0141 // Multiply [x,y,z] by the rotate-Y by "angle" rotation matrix 0142 // as in https://en.wikipedia.org/wiki/Rotation_matrix 0143 // cos(angle) 0 sin(angle) 0144 // 0 1 0 0145 // -sin(angle) 0 cos(angle) 0146 V3 rotateAroundY(const V3 &point, double degrees) 0147 { 0148 const double radians = d2r(degrees); 0149 const double cosAngle = cos(radians); 0150 const double sinAngle = sin(radians); 0151 return V3( point.x() * cosAngle + point.z() * sinAngle, 0152 point.y(), 0153 -point.x() * sinAngle + point.z() * cosAngle); 0154 } 0155 0156 V3 rotateAroundZ(const V3 &point, double degrees) 0157 { 0158 const double radians = d2r(degrees); 0159 const double cosAngle = cos(radians); 0160 const double sinAngle = sin(radians); 0161 return V3( point.x() * cosAngle - point.y() * sinAngle, 0162 point.x() * sinAngle + point.y() * cosAngle, 0163 point.z()); 0164 } 0165 0166 // Rotates in altitude then azimuth, as is done to correct for polar alignment. 0167 // Note, NOT a single rotation along a great circle, but rather two separate 0168 // rotations. 0169 QPointF rotateRaAxis(const QPointF &azAltPoint, const QPointF &azAltRotation) 0170 { 0171 const V3 point = azAlt2xyz(azAltPoint); 0172 const V3 altRotatedPoint = rotateAroundY(point, -azAltRotation.y()); 0173 0174 // Az rotation is simply adding in the az angle. 0175 const QPointF altAz = xyz2azAlt(altRotatedPoint); 0176 return QPointF(altAz.x() + azAltRotation.x(), altAz.y()); 0177 } 0178 0179 } // namespace rotations