From b176a4a4128152a4fa1dbbe3fed1c58a7a80b7c1 Mon Sep 17 00:00:00 2001 From: Martin Asprusten Date: Mon, 6 Apr 2026 13:14:11 +0200 Subject: [PATCH] Some bug fixes --- src/calculations/orbit-calculations.ts | 81 +++++++++++++++++--------- 1 file changed, 55 insertions(+), 26 deletions(-) diff --git a/src/calculations/orbit-calculations.ts b/src/calculations/orbit-calculations.ts index 0f482c3..1b9ac0e 100644 --- a/src/calculations/orbit-calculations.ts +++ b/src/calculations/orbit-calculations.ts @@ -192,13 +192,8 @@ export class LambertSolutions { transferGoalTrueAnomaly -= 2 * Math.PI; } - if (transferStartTrueAnomaly < 2 * Math.PI && transferGoalTrueAnomaly > 2 * Math.PI) { - transferStartTrueAnomaly -= 2 * Math.PI; - transferGoalTrueAnomaly -= 2 * Math.PI; - } - let closestPoint; - if (transferStartTrueAnomaly < 0 && transferGoalTrueAnomaly >= 0) { + if ((transferStartTrueAnomaly < 0 && transferGoalTrueAnomaly >= 0) || (transferStartTrueAnomaly < 2 * Math.PI && transferGoalTrueAnomaly >= 2 * Math.PI)) { closestPoint = semiLatusRectum / (1 + eccentricity); } else { closestPoint = Math.min(this.positionOneMagnitude, this.positionTwoMagnitude); @@ -570,7 +565,7 @@ export function getOrbitalPeriod(semiMajor: number, body: Body) { return Math.sqrt(semiMajor**3 / body.gravitationalParameter); } -export function perform2dGradientDescent(functionToMinimize: ((variableOne: number, variableTwo: number) => number | null), initialGuessVariableOne: number, initialGuessVaribaleTwo: number, maxDifference?: number, maxTries?: number): [number, number] | null { +export function perform2dGradientDescent(functionToMinimize: ((variableOne: number, variableTwo: number) => number | null), initialGuessVariableOne: number, initialGuessVaribaleTwo: number, maxDifference?: number, maxTries?: number, variableOneBounds?: [number, number], variableTwoBounds?: [number, number]): [number, number] | null { let variableOne = initialGuessVariableOne; let variableTwo = initialGuessVaribaleTwo; let bestValue = functionToMinimize(variableOne, variableTwo); @@ -649,6 +644,18 @@ export function perform2dGradientDescent(functionToMinimize: ((variableOne: numb break; } + if (variableOneBounds) { + if (variableOneUpdate < variableOneBounds[0] || variableOneUpdate > variableOneBounds[1]) { + break; + } + } + + if (variableTwoBounds) { + if (variableTwoUpdate < variableTwoBounds[0] || variableTwoUpdate > variableTwoBounds[1]) { + break; + } + } + variableOne = variableOneUpdate; variableTwo = variableTwoUpdate; bestValue = bestValueImprovement; @@ -992,17 +999,26 @@ export function findCheapestIntercept(startingSituation: OrbitalCoordinates, tar } - let extraStartOrbits = Math.floor(startOrbitMeanAnomaly / (2 * Math.PI)); - let extraEndOrbits = Math.floor(endOrbitMeanAnomaly / (2 * Math.PI)); + let startTrueAnomaly: number = 0; + let endTrueAnomaly: number = 0; - startOrbitMeanAnomaly = startOrbitMeanAnomaly % (2 * Math.PI); - endOrbitMeanAnomaly = endOrbitMeanAnomaly % (2 * Math.PI); + ["start", "end"].forEach(orbit => { + let meanAnomaly = orbit == "start" ? startOrbitMeanAnomaly : endOrbitMeanAnomaly; + let eccentricity = orbit == "start" ? startingSituation.orbit.eccentricity : targetSituation.orbit.eccentricity; + let extraOrbits = 0; + if (eccentricity < 1) { + extraOrbits = Math.floor(meanAnomaly / (2 * Math.PI)); + meanAnomaly = meanAnomaly % (2 * Math.PI); + } + let [_, trueAnomaly] = getEccentricAndTrueAnomalyFromMeanAnomaly(meanAnomaly, eccentricity); + trueAnomaly += extraOrbits * 2 * Math.PI; - let [_, startTrueAnomaly] = getEccentricAndTrueAnomalyFromMeanAnomaly(startOrbitMeanAnomaly, startingSituation.orbit.eccentricity); - let [__, endTrueAnomaly] = getEccentricAndTrueAnomalyFromMeanAnomaly(endOrbitMeanAnomaly, targetSituation.orbit.eccentricity); - - startTrueAnomaly += extraStartOrbits * 2 * Math.PI; - endTrueAnomaly += extraEndOrbits * 2 * Math.PI; + if (orbit == "start") { + startTrueAnomaly = trueAnomaly; + } else if (orbit == "end") { + endTrueAnomaly = trueAnomaly; + } + }); let targetTransferTime = endTime - startTime; let lambertSolutions = new LambertSolutions(startingSituation.orbit, startTrueAnomaly, targetSituation.orbit, endTrueAnomaly + extraTrueAnomaly, body, backwards); @@ -1024,7 +1040,12 @@ export function findCheapestIntercept(startingSituation: OrbitalCoordinates, tar return null; } - if (transfer.farthestPointDistance > body.sphereOfInfluence || transfer.closestPointDistance < body.closestSafeDistance) { + if (transfer.farthestPointDistance >= body.sphereOfInfluence || transfer.closestPointDistance <= body.closestSafeDistance) { + return null; + } + + // Sometimes, we get eccentric orbits that are impossible to do + if (transfer.transferOrbitTrueAnomalyAtManoeuvreOne < Math.PI && transfer.transferOrbitTrueanomalyAtManoeuvreTwo > Math.PI && transfer.transferOrbit.eccentricity - 1 >= 0) { return null; } @@ -1066,7 +1087,7 @@ export function findCheapestIntercept(startingSituation: OrbitalCoordinates, tar return; } - let result = perform2dGradientDescent(interceptFunction, foundStartTime, foundEndTime, 0, 30); + let result = perform2dGradientDescent(interceptFunction, foundStartTime, foundEndTime, 0, 30, [0, 1e99]); if (result) { let foundTransfer = getTransfer(result[0], result[1], backwards); if (foundTransfer) { @@ -1463,22 +1484,30 @@ export function findOrbitThroughInterpolation(ownCoordinates: OrbitalCoordinates } const getAnglesFromPhaseAngles = (firstPhaseAngle: number, secondPhaseAngle: number) => { - let angleOne; - let angleTwo; + let cosAngleOne; + let cosAngleTwo; if (Math.abs(interpolationParameters.firstPhaseAngle) < Math.PI / 2) { - angleOne = Math.acos(Math.tan(firstPhaseAngle) * firstDistanceAlongDirection / firstDistancePerpendicularToDirection); + cosAngleOne = Math.tan(firstPhaseAngle) * firstDistanceAlongDirection / firstDistancePerpendicularToDirection; } else { - angleOne = Math.acos(Math.tan(Math.PI - firstPhaseAngle) * -firstDistanceAlongDirection / firstDistancePerpendicularToDirection); + cosAngleOne = Math.tan(Math.PI - firstPhaseAngle) * -firstDistanceAlongDirection / firstDistancePerpendicularToDirection; } if (Math.abs(interpolationParameters.secondPhaseAngle) < Math.PI / 2) { - angleTwo = Math.acos(Math.tan(secondPhaseAngle) * secondDistanceAlongDirection / secondDistancePerpendicularToDirection); + cosAngleTwo = Math.tan(secondPhaseAngle) * secondDistanceAlongDirection / secondDistancePerpendicularToDirection; } else { - angleTwo = Math.acos(Math.tan(Math.PI - secondPhaseAngle) * -secondDistanceAlongDirection / secondDistancePerpendicularToDirection); + cosAngleTwo = Math.tan(Math.PI - secondPhaseAngle) * -secondDistanceAlongDirection / secondDistancePerpendicularToDirection; } - return [angleOne, angleTwo]; + if (Math.abs(cosAngleOne) > 1) { + cosAngleOne = 1 * Math.sign(cosAngleOne); + } + + if (Math.abs(cosAngleTwo) > 1) { + cosAngleTwo = 1 * Math.sign(cosAngleTwo); + } + + return [Math.acos(cosAngleOne), Math.acos(cosAngleTwo)]; } let bestAngles = [[0], [0]]; @@ -1519,7 +1548,7 @@ export function findOrbitThroughInterpolation(ownCoordinates: OrbitalCoordinates ) ); - let normalVector = normalizeVector(vectorCrossProduct(targetPositionTwo, targetPositionOne)); + let normalVector = normalizeVector(vectorCrossProduct(targetPositionOne, targetPositionTwo)); // Rotate the position vector about this normal vector to get the direction of the periapsis let ux = normalVector[0][0];