// orbit.cpp // // Copyright (C) 2001, Chris Laurel // // CometOrbit, InitHyp,InitPar,InitEll,Init3D: Copyright (C) 1995,2007,2008 Johannes Gajdosik // // This program 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 2 // of the License, or (at your option) any later version. #include #include #include #include #include "Solve.hpp" #include "Orbit.hpp" using namespace std; #define EPSILON 1e-10 #if defined(_MSC_VER) // cuberoot is missing in VC++ !? #define cbrt(x) pow((x),1./3.) #endif static void InitHyp(double q,double n,double e,double dt,double &a1,double &a2) { const double a = q/(e-1.0); const double M = n * dt; double H = M; for (;;) { // Newton const double Hp = H; H = H-(e*sinh(H)-H-M)/(e*cosh(H)-1); if (fabs(H - Hp) < EPSILON) break; } const double h1 = q*sqrt((e+1.0)/(e-1.0)); a1 = a*(e-cosh(H)); a2 = h1*sinh(H); } static void InitPar(double q,double n,double dt,double &a1,double &a2) { const double A = n*dt; const double h = sqrt(A*A+1.0); double c = cbrt(fabs(A)+h); c = c*c; const double tan_nu_h = 2*A/(1+c+1/c); a1 = q*(1-tan_nu_h*tan_nu_h); a2 = 2.0*q*tan_nu_h; } static void InitEll(double q,double n,double e,double dt,double &a1,double &a2) { const double a = q/(1.0-e); double M = fmod(n*dt,2*M_PI); if (M < 0.0) M += 2.0*M_PI; double H = M; for (;;) { // Newton const double Hp = H; H = H-(M-H+e*sin(H))/(e*cos(H)-1); if (fabs(H-Hp) < EPSILON) break; } const double h1 = q*sqrt((1.0+e)/(1.0-e)); a1 = a*(cos(H)-e); a2 = h1*sin(H); } void Init3D(double i,double Omega,double o,double a1,double a2, double &x1,double &x2,double &x3) { const double co = cos(o); const double so = sin(o); const double cOm = cos(Omega); const double sOm = sin(Omega); const double ci = cos(i); const double si = sin(i); const double d11=-so*sOm*ci+co*cOm; const double d12=-co*sOm*ci-so*cOm; const double d21= so*cOm*ci+co*sOm; const double d22= co*cOm*ci-so*sOm; const double d31= so*si; const double d32= co*si; x1 = d11*a1+d12*a2; x2 = d21*a1+d22*a2; x3 = d31*a1+d32*a2; } CometOrbit::CometOrbit(double pericenterDistance, double eccentricity, double inclination, double ascendingNode, double argOfPerhelion, double timeAtPerihelion, double meanMotion, double parentRotObliquity, double parentRotAscendingnode, double parentRotJ2000Longitude) :q(pericenterDistance),e(eccentricity),i(inclination), Om(ascendingNode),o(argOfPerhelion),t0(timeAtPerihelion), n(meanMotion) { const double c_obl = cos(parentRotObliquity); const double s_obl = sin(parentRotObliquity); const double c_nod = cos(parentRotAscendingnode); const double s_nod = sin(parentRotAscendingnode); const double cj = cos(parentRotJ2000Longitude); const double sj = sin(parentRotJ2000Longitude); // rotateToVsop87[0] = c_nod; // rotateToVsop87[1] = -s_nod * c_obl; // rotateToVsop87[2] = s_nod * s_obl; // rotateToVsop87[3] = s_nod; // rotateToVsop87[4] = c_nod * c_obl; // rotateToVsop87[5] = -c_nod * s_obl; // rotateToVsop87[6] = 0.0; // rotateToVsop87[7] = s_obl; // rotateToVsop87[8] = c_obl; rotateToVsop87[0] = c_nod*cj-s_nod*c_obl*sj; rotateToVsop87[1] = -c_nod*sj-s_nod*c_obl*cj; rotateToVsop87[2] = s_nod*s_obl; rotateToVsop87[3] = s_nod*cj+c_nod*c_obl*sj; rotateToVsop87[4] = -s_nod*sj+c_nod*c_obl*cj; rotateToVsop87[5] = -c_nod*s_obl; rotateToVsop87[6] = s_obl*sj; rotateToVsop87[7] = s_obl*cj; rotateToVsop87[8] = c_obl; } void CometOrbit::positionAtTimevInVSOP87Coordinates(double JD,double *v) const { JD -= t0; double a1,a2; if (e < 1.0) InitEll(q,n,e,JD,a1,a2); else if (e > 1.0) InitHyp(q,n,e,JD,a1,a2); else InitPar(q,n,JD,a1,a2); double p0,p1,p2; Init3D(i,Om,o,a1,a2,p0,p1,p2); v[0] = rotateToVsop87[0]*p0 + rotateToVsop87[1]*p1 + rotateToVsop87[2]*p2; v[1] = rotateToVsop87[3]*p0 + rotateToVsop87[4]*p1 + rotateToVsop87[5]*p2; v[2] = rotateToVsop87[6]*p0 + rotateToVsop87[7]*p1 + rotateToVsop87[8]*p2; } EllipticalOrbit::EllipticalOrbit(double pericenterDistance, double eccentricity, double inclination, double ascendingNode, double argOfPeriapsis, double meanAnomalyAtEpoch, double period, double epoch, double parentRotObliquity, double parentRotAscendingnode, double parentRotJ2000Longitude) : pericenterDistance(pericenterDistance), eccentricity(eccentricity), inclination(inclination), ascendingNode(ascendingNode), argOfPeriapsis(argOfPeriapsis), meanAnomalyAtEpoch(meanAnomalyAtEpoch), period(period), epoch(epoch) { const double c_obl = cos(parentRotObliquity); const double s_obl = sin(parentRotObliquity); const double c_nod = cos(parentRotAscendingnode); const double s_nod = sin(parentRotAscendingnode); const double cj = cos(parentRotJ2000Longitude); const double sj = sin(parentRotJ2000Longitude); rotateToVsop87[0] = c_nod*cj-s_nod*c_obl*sj; rotateToVsop87[1] = -c_nod*sj-s_nod*c_obl*cj; rotateToVsop87[2] = s_nod*s_obl; rotateToVsop87[3] = s_nod*cj+c_nod*c_obl*sj; rotateToVsop87[4] = -s_nod*sj+c_nod*c_obl*cj; rotateToVsop87[5] = -c_nod*s_obl; rotateToVsop87[6] = s_obl*sj; rotateToVsop87[7] = s_obl*cj; rotateToVsop87[8] = c_obl; } // Standard iteration for solving Kepler's Equation struct SolveKeplerFunc1 : public unary_function { double ecc; double M; SolveKeplerFunc1(double _ecc, double _M) : ecc(_ecc), M(_M) {}; double operator()(double x) const { return M + ecc * sin(x); } }; // Faster converging iteration for Kepler's Equation; more efficient // than above for orbits with eccentricities greater than 0.3. This // is from Jean Meeus's _Astronomical Algorithms_ (2nd ed), p. 199 struct SolveKeplerFunc2 : public unary_function { double ecc; double M; SolveKeplerFunc2(double _ecc, double _M) : ecc(_ecc), M(_M) {}; double operator()(double x) const { return x + (M + ecc * sin(x) - x) / (1 - ecc * cos(x)); } }; double sign(double x) { if (x < 0.) return -1.; else if (x > 0.) return 1.; else return 0.; } struct SolveKeplerLaguerreConway : public unary_function { double ecc; double M; SolveKeplerLaguerreConway(double _ecc, double _M) : ecc(_ecc), M(_M) {}; double operator()(double x) const { double s = ecc * sin(x); double c = ecc * cos(x); double f = x - s - M; double f1 = 1 - c; double f2 = s; x += -5 * f / (f1 + sign(f1) * sqrt(abs(16 * f1 * f1 - 20 * f * f2))); return x; } }; struct SolveKeplerLaguerreConwayHyp : public unary_function { double ecc; double M; SolveKeplerLaguerreConwayHyp(double _ecc, double _M) : ecc(_ecc), M(_M) {}; double operator()(double x) const { double s = ecc * sinh(x); double c = ecc * cosh(x); double f = s - x - M; double f1 = c - 1; double f2 = s; x += -5 * f / (f1 + sign(f1) * sqrt(abs(16 * f1 * f1 - 20 * f * f2))); return x; } }; typedef pair Solution; double EllipticalOrbit::eccentricAnomaly(double M) const { if (eccentricity == 0.0) { // Circular orbit return M; } else if (eccentricity < 0.2) { // Low eccentricity, so use the standard iteration technique Solution sol = solveIteration_fixed(SolveKeplerFunc1(eccentricity, M), M, 5); return sol.first; } else if (eccentricity < 0.9) { // Higher eccentricity elliptical orbit; use a more complex but // much faster converging iteration. Solution sol = solveIteration_fixed(SolveKeplerFunc2(eccentricity, M), M, 6); // Debugging // qDebug("ecc: %f, error: %f mas\n", // eccentricity, radToDeg(sol.second) * 3600000); return sol.first; } else if (eccentricity < 1.0) { // Extremely stable Laguerre-Conway method for solving Kepler's // equation. Only use this for high-eccentricity orbits, as it // requires more calcuation. double E = M + 0.85 * eccentricity * sign(sin(M)); Solution sol = solveIteration_fixed(SolveKeplerLaguerreConway(eccentricity, M), E, 8); return sol.first; } else if (eccentricity == 1.0) { // Nearly parabolic orbit; very common for comets // TODO: handle this return M; } else { // Laguerre-Conway method for hyperbolic (ecc > 1) orbits. double E = log(2 * M / eccentricity + 1.85); Solution sol = solveIteration_fixed(SolveKeplerLaguerreConwayHyp(eccentricity, M), E, 30); return sol.first; } } Vec3d EllipticalOrbit::positionAtE(double E) const { double x, z; if (eccentricity < 1.0) { double a = pericenterDistance / (1.0 - eccentricity); x = a * (cos(E) - eccentricity); z = a * sqrt(1 - eccentricity * eccentricity) * -sin(E); } else if (eccentricity > 1.0) { double a = pericenterDistance / (1.0 - eccentricity); x = -a * (eccentricity - cosh(E)); z = -a * sqrt(eccentricity * eccentricity - 1) * -sinh(E); } else { // TODO: Handle parabolic orbits x = 0.0; z = 0.0; } Mat4d R = (Mat4d::zrotation(ascendingNode) * Mat4d::xrotation(inclination) * Mat4d::zrotation(argOfPeriapsis)); return R * Vec3d(x, -z, 0); } // Return the offset from the center Vec3d EllipticalOrbit::positionAtTime(double t) const { t = t - epoch; double meanMotion = 2.0 * M_PI / period; double meanAnomaly = meanAnomalyAtEpoch + t * meanMotion; double E = eccentricAnomaly(meanAnomaly); return positionAtE(E); } //void EllipticalOrbit::positionAtTime(double JD, double * X, double * Y, double * Z) const //{ // Vec3d pos = positionAtTime(JD); // *X=pos[2]; // *Y=pos[0]; // *Z=pos[1]; //} //void EllipticalOrbit::positionAtTimev(double JD, double* v) //{ // Vec3d pos = positionAtTime(JD); // v[0]=pos[2]; // v[1]=pos[0]; // v[2]=pos[1]; //} void EllipticalOrbit::positionAtTimevInVSOP87Coordinates(double JD, double* v) const { Vec3d pos = positionAtTime(JD); v[0] = rotateToVsop87[0]*pos[0] + rotateToVsop87[1]*pos[1] + rotateToVsop87[2]*pos[2]; v[1] = rotateToVsop87[3]*pos[0] + rotateToVsop87[4]*pos[1] + rotateToVsop87[5]*pos[2]; v[2] = rotateToVsop87[6]*pos[0] + rotateToVsop87[7]*pos[1] + rotateToVsop87[8]*pos[2]; } double EllipticalOrbit::getPeriod() const { return period; } double EllipticalOrbit::getBoundingRadius() const { // TODO: watch out for unbounded parabolic and hyperbolic orbits return pericenterDistance * ((1.0 + eccentricity) / (1.0 - eccentricity)); } void EllipticalOrbit::sample(double, double, int nSamples, OrbitSampleProc& proc) const { double dE = 2. * M_PI / (double) nSamples; for (int i = 0; i < nSamples; i++) proc.sample(positionAtE(dE * i)); } Vec3d CachingOrbit::positionAtTime(double jd) const { if (jd != lastTime) { lastTime = jd; lastPosition = computePosition(jd); } return lastPosition; } void CachingOrbit::sample(double start, double t, int nSamples, OrbitSampleProc& proc) const { double dt = t / (double) nSamples; for (int i = 0; i < nSamples; i++) proc.sample(positionAtTime(start + dt * i)); }