From 3a80dd057d4cbd1243ccc6ed8dd9ca8c00d37426 Mon Sep 17 00:00:00 2001 From: "Pedro J. Garcia" Date: Wed, 16 Jul 2025 23:27:49 -0300 Subject: [PATCH 1/4] Implement orbital precession Orbital precession is enabled by two parameters within an EllipticalOrbit block: NodalPrecessionPeriod and ApsidalPrecessionPeriod, both with default units of years. One or both parameters can be specified simultaneously. --- src/celastro/astro.h | 2 + src/celengine/parseobject.cpp | 16 ++++- src/celephem/orbit.cpp | 119 ++++++++++++++++++++++++++++++---- src/celephem/orbit.h | 53 ++++++++++++--- test/unit/kepler_test.cpp | 56 ++++++++++++++++ 5 files changed, 224 insertions(+), 22 deletions(-) diff --git a/src/celastro/astro.h b/src/celastro/astro.h index 7a528427dc..5e5fff0db3 100644 --- a/src/celastro/astro.h +++ b/src/celastro/astro.h @@ -244,6 +244,8 @@ struct KeplerElements double argPericenter{ 0.0 }; double meanAnomaly{ 0.0 }; double period{ 0.0 }; + double nodalPeriod{ 0.0 }; + double apsidalPeriod{ 0.0 }; }; diff --git a/src/celengine/parseobject.cpp b/src/celengine/parseobject.cpp index 4b1b79db9c..dc56f2ea28 100644 --- a/src/celengine/parseobject.cpp +++ b/src/celengine/parseobject.cpp @@ -133,6 +133,11 @@ GetDefaultUnits(bool usePlanetUnits, double& distanceScale) * # One or none of the following: * MeanAnomaly (default: 0.0) * MeanLongitude (default: 0.0) + * + * # For precessing orbits (default unit is Julian years): + * # Default values result in no precession + * NodalPrecessionPeriod (default: 0.0) + * ApsidalPrecessionPeriod (default: 0.0) * } \endcode * * If usePlanetUnits is true: @@ -198,6 +203,10 @@ CreateKeplerianOrbit(const AssociativeArray* orbitData, return nullptr; } + elements.nodalPeriod = orbitData->getTime("NodalPrecessionPeriod", 1.0, astro::DAYS_PER_YEAR).value_or(0.0); + + elements.apsidalPeriod = orbitData->getTime("ApsidalPrecessionPeriod", 1.0, astro::DAYS_PER_YEAR).value_or(0.0); + elements.inclination = orbitData->getAngle("Inclination").value_or(0.0); elements.longAscendingNode = orbitData->getAngle("AscendingNode").value_or(0.0); @@ -228,7 +237,12 @@ CreateKeplerianOrbit(const AssociativeArray* orbitData, if (elements.eccentricity < 1.0) { - return std::make_shared(elements, epoch); + if (elements.nodalPeriod == 0.0 && elements.apsidalPeriod == 0.0) + { + return std::make_shared(elements, epoch); + } + + return std::make_shared(elements, epoch); } return std::make_shared(elements, epoch); diff --git a/src/celephem/orbit.cpp b/src/celephem/orbit.cpp index 1e2e2ecbed..37c6b45366 100644 --- a/src/celephem/orbit.cpp +++ b/src/celephem/orbit.cpp @@ -127,7 +127,12 @@ std::unique_ptr CreateKeplerOrbit(const astro::KeplerElements& elements, double epoch) { if (elements.eccentricity < 1.0) - return std::make_unique(elements, epoch); + { + if (elements.nodalPeriod == 0.0 && elements.apsidalPeriod == 0.0) + return std::make_unique(elements, epoch); + + return std::make_unique(elements, epoch); + } return std::make_unique(elements, epoch); } @@ -269,15 +274,12 @@ Eigen::Vector3d Orbit::velocityAtTime(double tdb) const } -EllipticalOrbit::EllipticalOrbit(const astro::KeplerElements& _elements, double _epoch) : +EllipticalOrbitBase::EllipticalOrbitBase(const astro::KeplerElements& _elements, double _epoch) : semiMajorAxis(_elements.semimajorAxis), eccentricity(_elements.eccentricity), meanAnomalyAtEpoch(_elements.meanAnomaly), period(_elements.period), - epoch(_epoch), - orbitPlaneRotation((math::ZRotation(_elements.longAscendingNode) * - math::XRotation(_elements.inclination) * - math::ZRotation(_elements.argPericenter)).toRotationMatrix()) + epoch(_epoch) { assert(eccentricity >= 0.0 && eccentricity < 1.0); assert(semiMajorAxis >= 0.0); @@ -286,7 +288,7 @@ EllipticalOrbit::EllipticalOrbit(const astro::KeplerElements& _elements, double } -double EllipticalOrbit::eccentricAnomaly(double M) const +double EllipticalOrbitBase::eccentricAnomaly(double M) const { if (eccentricity == 0.0) { @@ -313,6 +315,26 @@ double EllipticalOrbit::eccentricAnomaly(double M) const } +double EllipticalOrbitBase::getPeriod() const +{ + return period; +} + + +double EllipticalOrbitBase::getBoundingRadius() const +{ + // TODO: watch out for unbounded parabolic and hyperbolic orbits + return semiMajorAxis * (1.0 + eccentricity); +} + + +EllipticalOrbit::EllipticalOrbit(const astro::KeplerElements& _elements, double _epoch) : + EllipticalOrbitBase(_elements, _epoch), + orbitPlaneRotation((math::ZRotation(_elements.longAscendingNode) * + math::XRotation(_elements.inclination) * + math::ZRotation(_elements.argPericenter)).toRotationMatrix()) {} + + // Compute the position at the specified eccentric // anomaly E. Eigen::Vector3d EllipticalOrbit::positionAtE(double E) const @@ -370,16 +392,89 @@ Eigen::Vector3d EllipticalOrbit::velocityAtTime(double t) const } -double EllipticalOrbit::getPeriod() const +PrecessingOrbit::PrecessingOrbit(const astro::KeplerElements& _elements, double _epoch) : + EllipticalOrbitBase(_elements, _epoch), + longAscendingNodeAtEpoch(_elements.longAscendingNode), + argPericenterAtEpoch(_elements.argPericenter), + nodalPeriod(_elements.nodalPeriod), + apsidalPeriod(_elements.apsidalPeriod), + inclinationRotation(math::XRotation(_elements.inclination)) {} + + +// Compute the position at the specified eccentric +// anomaly E. +Eigen::Vector3d PrecessingOrbit::positionAtE(double E, double longAscendingNode, double argPericenter) const { - return period; + double x = semiMajorAxis * (std::cos(E) - eccentricity); + double y = semiMinorAxis * std::sin(E); + + Eigen::Matrix3d orbitPlaneRotation = (math::ZRotation(longAscendingNode) * + inclinationRotation * + math::ZRotation(argPericenter)).toRotationMatrix(); + Eigen::Vector3d p = orbitPlaneRotation * Eigen::Vector3d(x, y, 0); + + // Convert to Celestia's internal coordinate system + return Eigen::Vector3d(p.x(), p.z(), -p.y()); } -double EllipticalOrbit::getBoundingRadius() const +// Compute the velocity at the specified eccentric +// anomaly E. +Eigen::Vector3d PrecessingOrbit::velocityAtE(double E, double longAscendingNode, double argPericenter, double meanMotion) const { - // TODO: watch out for unbounded parabolic and hyperbolic orbits - return semiMajorAxis * (1.0 + eccentricity); + double sinE; + double cosE; + math::sincos(E, sinE, cosE); + + double edot = meanMotion / (1.0 - eccentricity * cosE); + + double x = -semiMajorAxis * sinE * edot; + double y = semiMinorAxis * cosE * edot; + + Eigen::Matrix3d orbitPlaneRotation = (math::ZRotation(longAscendingNode) * + inclinationRotation * + math::ZRotation(argPericenter)).toRotationMatrix(); + Eigen::Vector3d v = orbitPlaneRotation* Eigen::Vector3d(x, y, 0); + + // Convert to Celestia's coordinate system + return Eigen::Vector3d(v.x(), v.z(), -v.y()); +} + + +// Return the offset from the center +Eigen::Vector3d PrecessingOrbit::positionAtTime(double t) const +{ + t = t - epoch; + + // Period is assumed to be sidereal + double meanMotion = 2.0 * celestia::numbers::pi / period; + double nodalPrecessionRate = nodalPeriod != 0.0 ? 2.0 * celestia::numbers::pi / -nodalPeriod : 0.0; + double apsidalPrecessionRate = apsidalPeriod != 0.0 ? 2.0 * celestia::numbers::pi / apsidalPeriod : 0.0; + + double longAscendingNode = longAscendingNodeAtEpoch + t * nodalPrecessionRate; + double argPericenter = argPericenterAtEpoch + t * apsidalPrecessionRate; + double meanAnomaly = meanAnomalyAtEpoch + t * (meanMotion - apsidalPrecessionRate - nodalPrecessionRate); + double E = eccentricAnomaly(meanAnomaly); + + return positionAtE(E, longAscendingNode, argPericenter); +} + + +Eigen::Vector3d PrecessingOrbit::velocityAtTime(double t) const +{ + t = t - epoch; + + // Period is assumed to be sidereal + double meanMotion = 2.0 * celestia::numbers::pi / period; + double nodalPrecessionRate = nodalPeriod != 0.0 ? 2.0 * celestia::numbers::pi / -nodalPeriod : 0.0; + double apsidalPrecessionRate = apsidalPeriod != 0.0 ? 2.0 * celestia::numbers::pi / apsidalPeriod : 0.0; + + double longAscendingNode = longAscendingNodeAtEpoch + t * nodalPrecessionRate; + double argPericenter = argPericenterAtEpoch + t * apsidalPrecessionRate; + double meanAnomaly = meanAnomalyAtEpoch + t * (meanMotion - apsidalPrecessionRate - nodalPrecessionRate); + double E = eccentricAnomaly(meanAnomaly); + + return velocityAtE(E, longAscendingNode, argPericenter, meanMotion); } diff --git a/src/celephem/orbit.h b/src/celephem/orbit.h index b4a4b60c70..37e12c3dde 100644 --- a/src/celephem/orbit.h +++ b/src/celephem/orbit.h @@ -67,22 +67,18 @@ class Orbit }; -class EllipticalOrbit final : public Orbit +class EllipticalOrbitBase : public Orbit { public: - EllipticalOrbit(const astro::KeplerElements&, double _epoch = 2451545.0); - ~EllipticalOrbit() override = default; + ~EllipticalOrbitBase() override = default; - // Compute the orbit for a specified Julian date - Eigen::Vector3d positionAtTime(double) const override; - Eigen::Vector3d velocityAtTime(double) const override; double getPeriod() const override; double getBoundingRadius() const override; -private: +protected: + EllipticalOrbitBase(const astro::KeplerElements&, double _epoch = 2451545.0); + double eccentricAnomaly(double) const; - Eigen::Vector3d positionAtE(double) const; - Eigen::Vector3d velocityAtE(double, double) const; double semiMajorAxis; double semiMinorAxis; @@ -90,11 +86,50 @@ class EllipticalOrbit final : public Orbit double meanAnomalyAtEpoch; double period; double epoch; +}; + + +class EllipticalOrbit final : public EllipticalOrbitBase +{ +public: + EllipticalOrbit(const astro::KeplerElements&, double _epoch = 2451545.0); + ~EllipticalOrbit() override = default; + + // Compute the orbit for a specified Julian date + Eigen::Vector3d positionAtTime(double) const override; + Eigen::Vector3d velocityAtTime(double) const override; + +private: + Eigen::Vector3d positionAtE(double) const; + Eigen::Vector3d velocityAtE(double, double) const; Eigen::Matrix3d orbitPlaneRotation; }; +class PrecessingOrbit final : public EllipticalOrbitBase +{ +public: + PrecessingOrbit(const astro::KeplerElements&, double _epoch = 2451545.0); + ~PrecessingOrbit() override = default; + + // Compute the orbit for a specified Julian date + Eigen::Vector3d positionAtTime(double) const override; + Eigen::Vector3d velocityAtTime(double) const override; + +private: + Eigen::Vector3d positionAtE(double, double, double) const; + Eigen::Vector3d velocityAtE(double, double, double, double) const; + + double longAscendingNodeAtEpoch; + double argPericenterAtEpoch; + double nodalPeriod; + double apsidalPeriod; + + Eigen::Quaterniond inclinationRotation; +}; + + class HyperbolicOrbit final : public Orbit { public: diff --git a/test/unit/kepler_test.cpp b/test/unit/kepler_test.cpp index d16c45f900..4c005d5156 100644 --- a/test/unit/kepler_test.cpp +++ b/test/unit/kepler_test.cpp @@ -99,6 +99,62 @@ TEST_CASE("Elliptical orbits") } } +TEST_CASE("Precessing orbits") +{ + constexpr std::array testEccentricities{ 0.0, 0.2, 0.6 }; + + for (double period : testPeriods) + { + double semimajor = std::cbrt(GMsun * math::square(period) / fourpi2); + for (double meanAnomalyDeg : testAngles) + for (double inclinationDeg : testInclinations) + { + auto testNodes = inclinationDeg == 0.0 || inclinationDeg == 180.0 + ? celestia::util::array_view(fixedZero) + : celestia::util::array_view(testAngles); + for (double nodeDeg : testNodes) + { + auto testNodalPeriods = nodeDeg == 0.0 + ? celestia::util::array_view(fixedZero) + : celestia::util::array_view(testPeriods); + for (double nodalPeriod : testNodalPeriods) + for (double eccentricity : testEccentricities) + { + auto testPericenters = eccentricity == 0.0 + ? celestia::util::array_view(fixedZero) + : celestia::util::array_view(testAngles); + for (double pericenterDeg : testPericenters) + { + auto testApsidalPeriods = pericenterDeg == 0.0 + ? celestia::util::array_view(fixedZero) + : celestia::util::array_view(testPeriods); + for (double apsidalPeriod : testApsidalPeriods) + { + astro::KeplerElements expected; + expected.period = period; + expected.semimajorAxis = semimajor; + expected.eccentricity = eccentricity; + expected.inclination = math::degToRad(inclinationDeg); + expected.longAscendingNode = math::degToRad(nodeDeg); + expected.argPericenter = math::degToRad(pericenterDeg); + expected.meanAnomaly = math::degToRad(meanAnomalyDeg); + expected.nodalPeriod = nodalPeriod; + expected.apsidalPeriod = apsidalPeriod; + auto orbit = celestia::ephem::PrecessingOrbit(expected, 0.0); + auto position = orbit.positionAtTime(0.0); + auto velocity = orbit.velocityAtTime(0.0); + + auto actual = astro::StateVectorToElements(position, velocity, GMsun); + + TestElements(expected, actual); + } + } + } + } + } + } +} + TEST_CASE("Hyperbolic orbits") { constexpr std::array testEccentricities{ 1.5, 2.4 }; From d35975fd92d02f82dd7238fc2c0f61591aa20208 Mon Sep 17 00:00:00 2001 From: "Pedro J. Garcia" Date: Fri, 17 Apr 2026 02:10:31 -0300 Subject: [PATCH 2/4] Add support for anomalistic orbital period Anomalistic period is measured from one periapsis passage to another, as opposed to the sidereal period, which is the default assumption. --- src/celengine/parseobject.cpp | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/src/celengine/parseobject.cpp b/src/celengine/parseobject.cpp index dc56f2ea28..77e00620b1 100644 --- a/src/celengine/parseobject.cpp +++ b/src/celengine/parseobject.cpp @@ -117,8 +117,9 @@ GetDefaultUnits(bool usePlanetUnits, double& distanceScale) * SemiMajorAxis * PericenterDistance * - * # Required + * # One of the following is required: * Period + * AnomalisticPeriod * * Eccentricity (default: 0.0) * Inclination (default: 0.0) @@ -134,18 +135,19 @@ GetDefaultUnits(bool usePlanetUnits, double& distanceScale) * MeanAnomaly (default: 0.0) * MeanLongitude (default: 0.0) * - * # For precessing orbits (default unit is Julian years): + * # One or both of the following for a precessing orbit: * # Default values result in no precession * NodalPrecessionPeriod (default: 0.0) * ApsidalPrecessionPeriod (default: 0.0) * } \endcode * * If usePlanetUnits is true: - * Period is in Julian years + * Period or AnomalisticPeriod is in Julian years * SemiMajorAxis or PericenterDistance is in AU * Otherwise: - * Period is in Julian days + * Period or AnomalisticPeriod is in Julian days * SemiMajorAxis or PericenterDistance is in kilometers. + * The default unit for precession periods is Julian years. */ std::shared_ptr CreateKeplerianOrbit(const AssociativeArray* orbitData, @@ -188,6 +190,9 @@ CreateKeplerianOrbit(const AssociativeArray* orbitData, return nullptr; } + elements.nodalPeriod = orbitData->getTime("NodalPrecessionPeriod", 1.0, astro::DAYS_PER_YEAR).value_or(0.0); + elements.apsidalPeriod = orbitData->getTime("ApsidalPrecessionPeriod", 1.0, astro::DAYS_PER_YEAR).value_or(0.0); + if (auto periodValue = orbitData->getTime("Period", 1.0, timeScale); periodValue.has_value()) { elements.period = *periodValue; @@ -197,16 +202,27 @@ CreateKeplerianOrbit(const AssociativeArray* orbitData, return nullptr; } } + else if (auto anomPeriodValue = orbitData->getTime("AnomalisticPeriod", 1.0, timeScale); anomPeriodValue.has_value()) + { + double periodCorrection = 0.0; + if (elements.nodalPeriod != 0.0) + periodCorrection += 1.0 / elements.nodalPeriod; + if (elements.apsidalPeriod != 0.0) + periodCorrection += 1.0 / elements.apsidalPeriod; + + elements.period = 1.0 / (1.0 / *anomPeriodValue + periodCorrection); + if (elements.period == 0.0) + { + GetLogger()->error("AnomalisticPeriod cannot be zero.\n"); + return nullptr; + } + } else { GetLogger()->error("Period must be specified in EllipticalOrbit.\n"); return nullptr; } - elements.nodalPeriod = orbitData->getTime("NodalPrecessionPeriod", 1.0, astro::DAYS_PER_YEAR).value_or(0.0); - - elements.apsidalPeriod = orbitData->getTime("ApsidalPrecessionPeriod", 1.0, astro::DAYS_PER_YEAR).value_or(0.0); - elements.inclination = orbitData->getAngle("Inclination").value_or(0.0); elements.longAscendingNode = orbitData->getAngle("AscendingNode").value_or(0.0); @@ -238,9 +254,7 @@ CreateKeplerianOrbit(const AssociativeArray* orbitData, if (elements.eccentricity < 1.0) { if (elements.nodalPeriod == 0.0 && elements.apsidalPeriod == 0.0) - { return std::make_shared(elements, epoch); - } return std::make_shared(elements, epoch); } From dce4e57870634254e89ca8b86a2851a9da0ddc00 Mon Sep 17 00:00:00 2001 From: "Pedro J. Garcia" Date: Sat, 18 Apr 2026 00:03:22 -0300 Subject: [PATCH 3/4] Render osculating precessing orbits when fading is disabled --- src/celengine/render.cpp | 7 ++++-- src/celephem/orbit.cpp | 46 ++++++++++++++++++++++++++++++++++++++++ src/celephem/orbit.h | 5 +++++ 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/src/celengine/render.cpp b/src/celengine/render.cpp index d7670380a3..832029eaa4 100644 --- a/src/celengine/render.cpp +++ b/src/celengine/render.cpp @@ -1002,8 +1002,11 @@ void Renderer::renderOrbit(const OrbitPathListEntry& orbitPath, const Body* body = orbitPath.body; double nearZ = -nearDist; // negate, becase z is into the screen in camera space double farZ = -farDist; + bool isFadingEnabled = util::is_set(renderFlags, RenderFlags::ShowFadingOrbits); - const auto* orbit = body != nullptr ? body->getOrbit(t) : orbitPath.star->getOrbit(); + const auto* orbit = body != nullptr ? + body->getOrbit(t)->getOrbitForSampling(t, isFadingEnabled) : + orbitPath.star->getOrbit()->getOrbitForSampling(t, isFadingEnabled); CurvePlot* cachedOrbit = nullptr; if (auto cached = orbitCache.lower_bound(orbit); @@ -1174,7 +1177,7 @@ void Renderer::renderOrbit(const OrbitPathListEntry& orbitPath, double windowStart = windowEnd - period * OrbitPeriodsShown; double windowDuration = windowEnd - windowStart; - if (LinearFadeFraction == 0.0f || !util::is_set(renderFlags, RenderFlags::ShowFadingOrbits)) + if (LinearFadeFraction == 0.0f || !isFadingEnabled) { cachedOrbit->render(modelview, nearZ, farZ, viewFrustumPlaneNormals, diff --git a/src/celephem/orbit.cpp b/src/celephem/orbit.cpp index 37c6b45366..96b2f741d6 100644 --- a/src/celephem/orbit.cpp +++ b/src/celephem/orbit.cpp @@ -274,6 +274,12 @@ Eigen::Vector3d Orbit::velocityAtTime(double tdb) const } +const Orbit* Orbit::getOrbitForSampling(double /*t*/, bool /*isFadingEnabled*/) const +{ + return this; +} + + EllipticalOrbitBase::EllipticalOrbitBase(const astro::KeplerElements& _elements, double _epoch) : semiMajorAxis(_elements.semimajorAxis), eccentricity(_elements.eccentricity), @@ -392,6 +398,12 @@ Eigen::Vector3d EllipticalOrbit::velocityAtTime(double t) const } +void EllipticalOrbit::setOrientation(const Eigen::Matrix3d& _orbitPlaneRotation) +{ + orbitPlaneRotation = _orbitPlaneRotation; +} + + PrecessingOrbit::PrecessingOrbit(const astro::KeplerElements& _elements, double _epoch) : EllipticalOrbitBase(_elements, _epoch), longAscendingNodeAtEpoch(_elements.longAscendingNode), @@ -478,6 +490,40 @@ Eigen::Vector3d PrecessingOrbit::velocityAtTime(double t) const } +// For precessing orbits, get the osculating orbit when fading is disabled +const Orbit* PrecessingOrbit::getOrbitForSampling(double t, bool isFadingEnabled) const +{ + if (!isFadingEnabled) + { + t = t - epoch; + double meanMotion = 2.0 * celestia::numbers::pi / period; + double nodalPrecessionRate = nodalPeriod != 0.0 ? 2.0 * celestia::numbers::pi / -nodalPeriod : 0.0; + double apsidalPrecessionRate = apsidalPeriod != 0.0 ? 2.0 * celestia::numbers::pi / apsidalPeriod : 0.0; + + double longAscendingNode = longAscendingNodeAtEpoch + t * nodalPrecessionRate; + double argPericenter = argPericenterAtEpoch + t * apsidalPrecessionRate; + double meanAnomaly = meanAnomalyAtEpoch + t * (meanMotion - apsidalPrecessionRate - nodalPrecessionRate); + + astro::KeplerElements elements; + elements.semimajorAxis = semiMajorAxis; + elements.eccentricity = eccentricity; + elements.meanAnomaly = meanAnomaly; + elements.period = period; + + Eigen::Matrix3d orbitPlaneRotation = (math::ZRotation(longAscendingNode) * + inclinationRotation * + math::ZRotation(argPericenter)).toRotationMatrix(); + + auto* orbit = new EllipticalOrbit(elements, t); + orbit->setOrientation(orbitPlaneRotation); + + return orbit; + } + + return this; +} + + HyperbolicOrbit::HyperbolicOrbit(const astro::KeplerElements& _elements, double _epoch) : semiMajorAxis(_elements.semimajorAxis), eccentricity(_elements.eccentricity), diff --git a/src/celephem/orbit.h b/src/celephem/orbit.h index 37e12c3dde..302693bc48 100644 --- a/src/celephem/orbit.h +++ b/src/celephem/orbit.h @@ -42,6 +42,8 @@ class Orbit virtual double getPeriod() const = 0; virtual double getBoundingRadius() const = 0; + virtual const Orbit* getOrbitForSampling(double, bool) const; + virtual void sample(double startTime, double endTime, OrbitSampleProc& proc) const; virtual bool isPeriodic() const { return true; }; @@ -99,6 +101,8 @@ class EllipticalOrbit final : public EllipticalOrbitBase Eigen::Vector3d positionAtTime(double) const override; Eigen::Vector3d velocityAtTime(double) const override; + void setOrientation(const Eigen::Matrix3d& _orbitPlaneRotation); + private: Eigen::Vector3d positionAtE(double) const; Eigen::Vector3d velocityAtE(double, double) const; @@ -116,6 +120,7 @@ class PrecessingOrbit final : public EllipticalOrbitBase // Compute the orbit for a specified Julian date Eigen::Vector3d positionAtTime(double) const override; Eigen::Vector3d velocityAtTime(double) const override; + const Orbit* getOrbitForSampling(double t, bool isFadingEnabled) const override; private: Eigen::Vector3d positionAtE(double, double, double) const; From 0e7dfaeae1e1b82281481a320e1ae2c009d20890 Mon Sep 17 00:00:00 2001 From: "Pedro J. Garcia" Date: Sat, 18 Apr 2026 00:35:19 -0300 Subject: [PATCH 4/4] Correct sidereal rotation period for precession This matches the behavior of precessing orbits --- src/celengine/parseobject.cpp | 2 +- src/celephem/rotation.cpp | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/celengine/parseobject.cpp b/src/celengine/parseobject.cpp index 77e00620b1..7fe58b5539 100644 --- a/src/celengine/parseobject.cpp +++ b/src/celengine/parseobject.cpp @@ -206,7 +206,7 @@ CreateKeplerianOrbit(const AssociativeArray* orbitData, { double periodCorrection = 0.0; if (elements.nodalPeriod != 0.0) - periodCorrection += 1.0 / elements.nodalPeriod; + periodCorrection -= 1.0 / elements.nodalPeriod; if (elements.apsidalPeriod != 0.0) periodCorrection += 1.0 / elements.apsidalPeriod; diff --git a/src/celephem/rotation.cpp b/src/celephem/rotation.cpp index 206b2bb9ae..810e16c2b6 100644 --- a/src/celephem/rotation.cpp +++ b/src/celephem/rotation.cpp @@ -276,7 +276,9 @@ PrecessingRotationModel::getPeriod() const Eigen::Quaterniond PrecessingRotationModel::spin(double tjd) const { - double rotations = (tjd - epoch) / period; + // Correct the sidereal rotation period for precession of the node + double periodCorrection = precessionPeriod != 0.0 ? 1.0 / precessionPeriod : 0.0; + double rotations = (tjd - epoch) / (1.0 / (1.0 / period - periodCorrection)); double wholeRotations = std::floor(rotations); double remainder = rotations - wholeRotations;