Simplified interpolation

This commit is contained in:
Martin Asprusten 2026-04-04 11:17:30 +02:00
parent dd63c64443
commit b3366eba01
4 changed files with 348 additions and 208 deletions

View File

@ -1,5 +1,5 @@
<!doctype html>
<html lang="en">
<html lang="en-US">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/satellite.png" />

View File

@ -133,6 +133,20 @@ export function addVector(vectorOne: number[][], vectorTwo: number[][]): number[
return result;
}
export function addVectors(vectors: number[][][]) {
let resultVector = [
[0],
[0],
[0]
];
vectors.forEach(vector => {
resultVector = addVector(resultVector, vector);
});
return resultVector;
}
export function addMatrix(matrixOne: number[][], matrixTwo: number[][]): number[][] {
if (!checkIfValidMatrix(matrixOne) || !checkIfValidMatrix(matrixTwo)) {
throw new TypeError("Two valid matrices are required");
@ -227,3 +241,32 @@ export function multiplyMatrixWithScalar(scalar: number, matrix: number[][]): nu
return result;
}
export function invertTwoByTwoMatrix(matrix: number[][]): number[][] {
if (!checkIfValidMatrix(matrix)) {
throw new TypeError("A valid matrix is required");
}
if (matrix.length != 2) {
throw new TypeError("Can only invert 2x2 matrices");
}
if (matrix[0].length != 2) {
throw new TypeError("Can only invert 2x2 matrices");
}
let divisor = matrix[0][0] * matrix[1][1] - matrix[0][1] * matrix[1][0];
let multiplier = 1 / divisor;
return [
[
matrix[1][1] * multiplier,
-matrix[0][1] * multiplier
],
[
-matrix[1][0] * multiplier,
matrix[0][0] * multiplier
]
];
}

View File

@ -1,6 +1,6 @@
import type { InterpolationParameters } from "../gui/interpolate";
import type { Body } from "./constants";
import { addMatrix, addVector, getVectorMagnitude, matrixMultiply, multiplyMatrixWithScalar, normalizeVector, subtractVector, vectorCrossProduct, vectorDotProduct } from "./mathematics";
import { addMatrix, addVector, getVectorMagnitude, invertTwoByTwoMatrix, matrixMultiply, multiplyMatrixWithScalar, normalizeVector, subtractVector, vectorCrossProduct, vectorDotProduct } from "./mathematics";
export interface Orbit {
semiLatusRectum: number,
@ -362,7 +362,14 @@ export function getEccentricAndTrueAnomalyFromMeanAnomaly(meanAnomaly: number, e
}
export function getOrbitalCoordinates(timeToPeriapsis: number, orbit: Orbit, planet: Body): OrbitalCoordinates {
const meanAngularMotion = Math.sqrt(planet.gravitationalParameter * Math.abs(1 - orbit.eccentricity**2)**3 / orbit.semiLatusRectum**3);
let meanAngularMotion;
if (Math.abs(orbit.eccentricity - 1) < 0.0001) {
meanAngularMotion = Math.sqrt(8 * planet.gravitationalParameter / orbit.semiLatusRectum**3);
} else {
meanAngularMotion = Math.sqrt(planet.gravitationalParameter * Math.abs(1 - orbit.eccentricity**2)**3 / orbit.semiLatusRectum**3);
}
const meanAnomaly = meanAngularMotion * -timeToPeriapsis;
@ -412,9 +419,11 @@ export function getOrbitalCoordinatesFromAltitude(altitude: number, headingInwar
let eccentricAnomaly;
let meanAnomaly;
let meanAngularMotion: number | null = null;
if (Math.abs(orbit.eccentricity - 1) < 0.0001) {
eccentricAnomaly = trueAnomaly;
meanAnomaly = eccentricAnomaly;
meanAnomaly = Math.sqrt(2) * (Math.tan(trueAnomaly / 2) + Math.tan(trueAnomaly / 2)**3 / 3);
meanAngularMotion = Math.sqrt(8 * planet.gravitationalParameter / orbit.semiLatusRectum**3);
} else if (orbit.eccentricity < 1) {
eccentricAnomaly = Math.atan2(Math.sqrt(1 - orbit.eccentricity**2)*Math.sin(trueAnomaly), orbit.eccentricity + Math.cos(trueAnomaly));
meanAnomaly = eccentricAnomaly - orbit.eccentricity * Math.sin(eccentricAnomaly);
@ -423,7 +432,9 @@ export function getOrbitalCoordinatesFromAltitude(altitude: number, headingInwar
meanAnomaly = orbit.eccentricity * Math.sinh(eccentricAnomaly) - eccentricAnomaly;
}
const meanAngularMotion = Math.sqrt(planet.gravitationalParameter * Math.abs(1 - orbit.eccentricity**2)**3 / orbit.semiLatusRectum**3);
if (meanAngularMotion == null) {
meanAngularMotion = Math.sqrt(planet.gravitationalParameter * Math.abs(1 - orbit.eccentricity**2)**3 / orbit.semiLatusRectum**3);
}
return {
orbit: orbit,
@ -977,6 +988,10 @@ export function findCheapestIntercept(startingSituation: OrbitalCoordinates, tar
let startOrbitMeanAnomaly = startingSituation.meanAnomaly + startTime * startingSituation.meanAngularMotion;
let endOrbitMeanAnomaly = targetSituation.meanAnomaly + endTime * targetSituation.meanAngularMotion;
if (Math.abs(startingSituation.orbit.eccentricity - 1) < 0.0001) {
}
let extraStartOrbits = Math.floor(startOrbitMeanAnomaly / (2 * Math.PI));
let extraEndOrbits = Math.floor(endOrbitMeanAnomaly / (2 * Math.PI));
@ -1141,7 +1156,7 @@ export function findCheapestLanding(startingCoordinates: OrbitalCoordinates, sta
let position = startingPosition;
let mass = ship.mass;
let massUse = ship.thrust / ship.specificImpulse;
let massUse = ship.thrust / (ship.specificImpulse * 9.81);
let timeSteps = 0;
let lengthOfStep = 0.01;
@ -1369,261 +1384,326 @@ export function findOrbitThroughInterpolation(ownCoordinates: OrbitalCoordinates
return trueAnomaly;
}
const ownFirstTrueAnomaly = getTrueAnomalyFromAltitude(interpolationParameters.firstOwnAltitude, ownCoordinates.orbit.semiLatusRectum, ownCoordinates.orbit.eccentricity, ownShipHeadedInwards);
const ownSecondTrueAnomaly = getTrueAnomalyFromAltitude(interpolationParameters.secondOwnAltitude, ownCoordinates.orbit.semiLatusRectum, ownCoordinates.orbit.eccentricity, ownShipHeadedInwards);
const getPossibleTargetVectors = (ownOrbit: Orbit, ownAltitude: number, targetAltitude: number, distanceToTarget: number): [number[][], number[][], number[][], number, number] => {
let ownTrueAnomaly = getTrueAnomalyFromAltitude(ownAltitude, ownOrbit.semiLatusRectum, ownOrbit.eccentricity, ownShipHeadedInwards);
const targetFirstTrueAnomaly = getTrueAnomalyFromAltitude(interpolationParameters.firstTargetAltitude, targetSemiLatusRectum, targetEccentricity, targetHeadedInwards);
const targetSecondTrueAnomaly = getTrueAnomalyFromAltitude(interpolationParameters.secondTargetAltitude, targetSemiLatusRectum, targetEccentricity, targetHeadedInwards);
let ownLocalX = ownAltitude * Math.cos(ownTrueAnomaly);
let ownLocalY = ownAltitude * Math.sin(ownTrueAnomaly);
const getVectors = (trueAnomaly: number, orbit: Orbit) => {
const radius = orbit.semiLatusRectum / (1 + orbit.eccentricity * Math.cos(trueAnomaly));
const localX = radius * Math.cos(trueAnomaly);
const localY = radius * Math.sin(trueAnomaly);
let ownDirection = normalizeVector(
const vectorPointingTowardsShip = normalizeVector(
addVector(
multiplyMatrixWithScalar(ownLocalX, ownOrbit.coordinateAxes[0]),
multiplyMatrixWithScalar(ownLocalY, ownOrbit.coordinateAxes[1])
multiplyMatrixWithScalar(localX, orbit.coordinateAxes[0]),
multiplyMatrixWithScalar(localY, orbit.coordinateAxes[1])
)
);
let perpendicularInPlane = normalizeVector(vectorCrossProduct(ownOrbit.coordinateAxes[2], ownDirection));
let perpendicularOutOfPlane = normalizeVector(vectorCrossProduct(ownDirection, perpendicularInPlane));
// Find phase angle to target
let phaseAngle = Math.acos((ownAltitude**2 + targetAltitude**2 - distanceToTarget**2) / (2 * ownAltitude * targetAltitude));
let distanceAlongDirection = targetAltitude * Math.cos(phaseAngle);
let distanceAlongNormal = targetAltitude * Math.sin(phaseAngle);
let root = multiplyMatrixWithScalar(distanceAlongDirection, ownDirection);
let vectorOne = multiplyMatrixWithScalar(distanceAlongNormal, perpendicularInPlane);
let vectorTwo = multiplyMatrixWithScalar(distanceAlongNormal, perpendicularOutOfPlane);
return [root, vectorOne, vectorTwo, distanceAlongDirection, distanceAlongNormal];
}
let firstPositionPossibleVectors = getPossibleTargetVectors(ownCoordinates.orbit, interpolationParameters.firstOwnAltitude, interpolationParameters.firstTargetAltitude, interpolationParameters.firstDistance);
let secondPositionPossibleVectors = getPossibleTargetVectors(ownCoordinates.orbit, interpolationParameters.secondOwnAltitude, interpolationParameters.secondTargetAltitude, interpolationParameters.secondDistance);
let targetFirstTrueAnomaly = getTrueAnomalyFromAltitude(interpolationParameters.firstTargetAltitude, targetSemiLatusRectum, targetEccentricity, targetHeadedInwards);
let targetSecondTrueAnomaly = getTrueAnomalyFromAltitude(interpolationParameters.secondTargetAltitude, targetSemiLatusRectum, targetEccentricity, targetHeadedInwards);
let c1 = firstPositionPossibleVectors[0];
let p11 = firstPositionPossibleVectors[1];
let p12 = firstPositionPossibleVectors[2];
let c2 = secondPositionPossibleVectors[0];
let p21 = secondPositionPossibleVectors[1];
let p22 = secondPositionPossibleVectors[2];
// Scale everything to avoid numerical explosions
let scaleFactor = 1 / getVectorMagnitude(c2);
c1 = multiplyMatrixWithScalar(scaleFactor, c1);
p11 = multiplyMatrixWithScalar(scaleFactor, p11);
p12 = multiplyMatrixWithScalar(scaleFactor, p12);
c2 = multiplyMatrixWithScalar(scaleFactor, c2);
p21 = multiplyMatrixWithScalar(scaleFactor, p21);
p22 = multiplyMatrixWithScalar(scaleFactor, p22);
let scaledAltitudeOne = interpolationParameters.firstTargetAltitude * scaleFactor;
let scaledAltitudeTwo = interpolationParameters.secondTargetAltitude * scaleFactor;
const constantPart = scaledAltitudeOne * scaledAltitudeTwo * Math.cos(targetSecondTrueAnomaly - targetFirstTrueAnomaly);
const functionToMinimize = (angleOne: number, angleTwo: number) => {
let d1 = addVector(c1, addVector(
multiplyMatrixWithScalar(Math.cos(angleOne), p11),
multiplyMatrixWithScalar(Math.sin(angleOne), p12)
));
let d2 = addVector(c2, addVector(
multiplyMatrixWithScalar(Math.cos(angleTwo), p21),
multiplyMatrixWithScalar(Math.sin(angleTwo), p22)
));
return vectorDotProduct(d1, d2) - constantPart;
}
const partialDerivativeAngleOne = (angleOne: number, angleTwo: number) => {
let d2 = addVector(c2, addVector(
multiplyMatrixWithScalar(Math.cos(angleTwo), p21),
multiplyMatrixWithScalar(Math.sin(angleTwo), p22)
));
let d1Dx = addVector(
multiplyMatrixWithScalar(-Math.sin(angleOne), p11),
multiplyMatrixWithScalar(Math.cos(angleOne), p12)
const perpendicularVectorInPlane = normalizeVector(
vectorCrossProduct(orbit.coordinateAxes[2], vectorPointingTowardsShip)
);
return vectorDotProduct(d1Dx, d2);
const perpendicularVectorOutOfPlane = orbit.coordinateAxes[2];
return [vectorPointingTowardsShip, perpendicularVectorInPlane, perpendicularVectorOutOfPlane];
}
const partialDerivativeAngleTwo = (angleOne: number, angleTwo: number) => {
let d1 = addVector(c1, addVector(
multiplyMatrixWithScalar(Math.cos(angleOne), p11),
multiplyMatrixWithScalar(Math.sin(angleOne), p12)
));
const firstVectors = getVectors(ownFirstTrueAnomaly, ownCoordinates.orbit);
const secondVectors = getVectors(ownSecondTrueAnomaly, ownCoordinates.orbit);
let d2Dy = addVector(
multiplyMatrixWithScalar(-Math.sin(angleTwo), p21),
multiplyMatrixWithScalar(Math.cos(angleTwo), p22)
const getDistances = (ownAltitude: number, targetAltitude: number, targetDistance: number) => {
const angleBetweenPlanetAndTarget = Math.acos((ownAltitude**2 + targetAltitude**2 - targetDistance**2) / (2*ownAltitude*targetAltitude));
const distanceAlongDirection = Math.cos(angleBetweenPlanetAndTarget) * targetAltitude;
const distanceAlongPerpendicular = Math.sin(angleBetweenPlanetAndTarget) * targetAltitude;
return [distanceAlongDirection, distanceAlongPerpendicular];
}
const [firstDistanceAlongDirection, firstDistancePerpendicularToDirection] = getDistances(interpolationParameters.firstOwnAltitude, interpolationParameters.firstTargetAltitude, interpolationParameters.firstDistance);
const [secondDistanceAlongDirection, secondDistancePerpendicularToDirection] = getDistances(interpolationParameters.secondOwnAltitude, interpolationParameters.secondTargetAltitude, interpolationParameters.secondDistance);
const phaseAngleDifference = (ownSecondTrueAnomaly + interpolationParameters.secondPhaseAngle) - (ownFirstTrueAnomaly + interpolationParameters.firstPhaseAngle);
// Now, try some Newton's method to estimate the two position's the target has gone through
// To avoid the maths exploding, we'll scale everything down a bit
const scaleFactor = 1;
const c1 = multiplyMatrixWithScalar(firstDistanceAlongDirection * scaleFactor, firstVectors[0]);
const n11 = multiplyMatrixWithScalar(firstDistancePerpendicularToDirection * scaleFactor, firstVectors[1]);
const n12 = multiplyMatrixWithScalar(firstDistancePerpendicularToDirection * scaleFactor, firstVectors[2]);
const c2 = multiplyMatrixWithScalar(secondDistanceAlongDirection * scaleFactor, secondVectors[0]);
const n21 = multiplyMatrixWithScalar(secondDistancePerpendicularToDirection * scaleFactor, secondVectors[1]);
const n22 = multiplyMatrixWithScalar(secondDistancePerpendicularToDirection * scaleFactor, secondVectors[2]);
let expectedDistance = Math.sqrt(interpolationParameters.firstTargetAltitude**2 + interpolationParameters.secondTargetAltitude**2 - 2 * interpolationParameters.firstTargetAltitude * interpolationParameters.secondTargetAltitude * Math.cos(targetSecondTrueAnomaly - targetFirstTrueAnomaly));
// Scale the expected distance too
expectedDistance *= scaleFactor;
const distanceFunction = (angleOne: number, angleTwo: number) => {
let p1 = addVector(
c1,
addVector(
multiplyMatrixWithScalar(Math.cos(angleOne), n11),
multiplyMatrixWithScalar(Math.sin(angleOne), n12)
)
);
return vectorDotProduct(d1, d2Dy);
let p2 = addVector(
c2,
addVector(
multiplyMatrixWithScalar(Math.cos(angleTwo), n21),
multiplyMatrixWithScalar(Math.sin(angleTwo), n22)
)
);
return getVectorMagnitude(addVector(p2, multiplyMatrixWithScalar(-1, p1))) - expectedDistance;
}
let firstOwnTrueAnomaly = getTrueAnomalyFromAltitude(interpolationParameters.firstOwnAltitude, ownCoordinates.orbit.semiLatusRectum, ownCoordinates.orbit.eccentricity, ownShipHeadedInwards);
let secondOwnTrueAnomaly = getTrueAnomalyFromAltitude(interpolationParameters.secondOwnAltitude, ownCoordinates.orbit.semiLatusRectum, ownCoordinates.orbit.eccentricity, ownShipHeadedInwards);
let horizontalAngleDifference = (secondOwnTrueAnomaly - interpolationParameters.secondPhaseAngle) - (firstOwnTrueAnomaly - interpolationParameters.firstPhaseAngle);
const phaseAngleFunction = (angleOne: number, angleTwo: number): number => {
let d1 = addVector(c1, multiplyMatrixWithScalar(Math.cos(angleOne), p11));
let d2 = addVector(c2, multiplyMatrixWithScalar(Math.cos(angleTwo), p21));
return vectorDotProduct(d1, d2) - Math.cos(horizontalAngleDifference) * getVectorMagnitude(d1) * getVectorMagnitude(d2);
const distancePartialDerivativeAngleOne = (angleOne: number, angleTwo: number) => {
let distance = distanceFunction(angleOne, angleTwo) + expectedDistance;
return (
Math.sin(angleOne) * vectorDotProduct(n11, c2)
+ Math.sin(angleOne) * Math.cos(angleTwo) * vectorDotProduct(n21, n11)
- Math.sin(angleOne) * Math.cos(angleOne) * vectorDotProduct(n11, n11)
- Math.cos(angleOne) * vectorDotProduct(n12, c2)
- Math.cos(angleOne) * Math.sin(angleTwo) * vectorDotProduct(n12, n22)
+ Math.sin(angleOne) * Math.sin(angleOne) * vectorDotProduct(n12, n12)
) / distance;
}
const phaseAnglePartialDerivativeAngleOne = (angleOne: number, angleTwo: number) => {
let d1 = addVector(c1, multiplyMatrixWithScalar(Math.cos(angleOne), p11));
let d1Dx = multiplyMatrixWithScalar(-Math.sin(angleOne), p11);
let d2 = addVector(c2, multiplyMatrixWithScalar(Math.cos(angleTwo), p21));
return vectorDotProduct(d1Dx, d2) - Math.cos(horizontalAngleDifference) * getVectorMagnitude(d2) * Math.sin(angleOne) * Math.cos(angleOne) * firstPositionPossibleVectors[4]**2 / getVectorMagnitude(d1);
const distancePartialDerivativeAngleTwo = (angleOne: number, angleTwo: number) => {
let distance = distanceFunction(angleOne, angleTwo) + expectedDistance;
return (
Math.cos(angleTwo) * Math.sin(angleTwo) * vectorDotProduct(n22, n22)
- Math.cos(angleTwo) * vectorDotProduct(n22, c1)
- Math.cos(angleTwo) * Math.sin(angleOne) * vectorDotProduct(n22, n12)
- Math.sin(angleTwo) * Math.cos(angleTwo) * vectorDotProduct(n21, n21)
+ Math.sin(angleTwo) * vectorDotProduct(n21, c1)
+ Math.sin(angleTwo) * Math.cos(angleOne) * vectorDotProduct(n21, n11)
) / distance;
}
const planeAngleFunction = (angleOne: number, angleTwo: number) => {
const horizontalOne = addVector(
c1,
multiplyMatrixWithScalar(Math.cos(angleOne), n11)
);
const gradientDescent = (startingGuessAngleOne: number, startingGuessAngleTwo: number): [number, number, number] => {
let angleOne = startingGuessAngleOne;
let angleTwo = startingGuessAngleTwo;
const horizontalTwo = addVector(
c2,
multiplyMatrixWithScalar(Math.cos(angleTwo), n21)
);
let distanceAlongDirection = getVectorMagnitude(firstPositionPossibleVectors[0]);
let distanceAlongPerpendicular = getVectorMagnitude(firstPositionPossibleVectors[1]);
let value = minimizeFunction(angleOne, angleTwo);
// Do up to 100 minimizing steps
for (var i = 0; i < 100; i++) {
let dfDx = partialDerivativeAngleOne(angleOne, angleTwo);
let dfDy = partialDerivativeAngleTwo(angleOne, angleTwo);
let phaseAngleValue = distanceAlongPerpendicular * Math.cos(angleOne) / distanceAlongDirection - Math.tan(interpolationParameters.firstPhaseAngle);
let phaseAngleDerivative = -distanceAlongPerpendicular * Math.sin(angleOne) / distanceAlongDirection;
let changeAngleOne = dfDx * phaseAngleValue / (dfDy * phaseAngleDerivative);
let changeAngleTwo = (dfDx * phaseAngleValue - phaseAngleDerivative * value) / (dfDy * phaseAngleDerivative);
let trialAngleOne = angleOne;
let trialAngleTwo = angleTwo;
let trialValue = value;
let divisor = 1;
// We don't want things to blow up
while (Math.abs(changeAngleOne) / 2**divisor > Math.PI / 1000 || Math.abs(changeAngleTwo) / 2**divisor > Math.PI / 1000) {
divisor +=1;
return vectorDotProduct(horizontalOne, horizontalTwo) / (getVectorMagnitude(horizontalOne) * getVectorMagnitude(horizontalTwo)) - Math.cos(phaseAngleDifference);
}
const planeAnglePartialDerivativeAngleOne = (angleOne: number, angleTwo: number) => {
const horizontalOne = addVector(
c1,
multiplyMatrixWithScalar(Math.cos(angleOne), n11)
);
const horizontalTwo = addVector(
c2,
multiplyMatrixWithScalar(Math.cos(angleTwo), n21)
);
return (
-Math.sin(angleOne) * vectorDotProduct(n11, c2)
-Math.sin(angleOne) * Math.cos(angleTwo) * vectorDotProduct(n11, n21)
-Math.cos(angleOne) * Math.sin(angleOne) * vectorDotProduct(n11, n11) * vectorDotProduct(horizontalOne, horizontalTwo) / getVectorMagnitude(horizontalOne)
) / (getVectorMagnitude(horizontalOne) * getVectorMagnitude(horizontalTwo));
}
const planeAnglePartialDerivativeAngleTwo = (angleOne: number, angleTwo: number) => {
const horizontalOne = addVector(
c1,
multiplyMatrixWithScalar(Math.cos(angleOne), n11)
);
const horizontalTwo = addVector(
c2,
multiplyMatrixWithScalar(Math.cos(angleTwo), n21)
);
return (
-Math.sin(angleTwo) * vectorDotProduct(n21, c1)
-Math.sin(angleTwo) * Math.cos(angleOne) * vectorDotProduct(n21, n11)
-Math.cos(angleTwo) * Math.sin(angleTwo) * vectorDotProduct(n21, n21) * vectorDotProduct(horizontalOne, horizontalTwo) / getVectorMagnitude(horizontalTwo)
) / (getVectorMagnitude(horizontalOne) * getVectorMagnitude(horizontalTwo))
}
// Try to Newton's method up in this bitch
const getFunctionVector = (anglesVector: number[][]) => {
return [
[distanceFunction(anglesVector[0][0], anglesVector[1][0])],
[planeAngleFunction(anglesVector[0][0], anglesVector[1][0])]
]
};
const getJacobianMatrix = (anglesVector: number[][]) => {
let angleOne = anglesVector[0][0];
let angleTwo = anglesVector[1][0];
return[
[distancePartialDerivativeAngleOne(angleOne, angleTwo), distancePartialDerivativeAngleTwo(angleOne, angleTwo)],
[planeAnglePartialDerivativeAngleOne(angleOne, angleTwo), planeAnglePartialDerivativeAngleTwo(angleOne, angleTwo)]
]
};
const performNewtonMethod = (initialGuess: number[][], iterations: number) => {
let anglesVector = [
[initialGuess[0][0]],
[initialGuess[1][0]]
];
for (var i = 0; i < iterations; i++) {
let functionVector = getFunctionVector(anglesVector);
let jacobianMatrix = getJacobianMatrix(anglesVector);
let update = matrixMultiply(invertTwoByTwoMatrix(jacobianMatrix), functionVector);
// Don't make updates too big
while (getVectorMagnitude(update) > Math.PI / 100) {
update = multiplyMatrixWithScalar(0.5, update);
}
/**
// Also don't update if it brings us further away from where we want to be
let trialFunctionVector = getFunctionVector(addVector(anglesVector, multiplyMatrixWithScalar(-1, update)));
let counter = 0;
let deadEnd = false;
while (Math.abs(trialValue) >= Math.abs(value)) {
trialAngleOne = angleOne - changeAngleOne / (2 ** divisor);
trialAngleTwo = angleTwo - changeAngleTwo / (2 ** divisor);
trialValue = minimizeFunction(trialAngleOne, trialAngleTwo);
divisor += 1;
if (divisor >= 32) {
while (Math.abs(trialFunctionVector[0][0]) >= Math.abs(functionVector[0][0])) {
counter += 1;
if (counter >= 16) {
deadEnd = true;
break;
}
update = multiplyMatrixWithScalar(0.5, update);
trialFunctionVector = getFunctionVector(addVector(anglesVector, multiplyMatrixWithScalar(-1, update)));
}
if (deadEnd) {
return [angleOne, angleTwo, value];
break;
}*/
anglesVector = addVector(anglesVector, multiplyMatrixWithScalar(-1, update));
};
return anglesVector;
}
value = trialValue;
angleOne = trialAngleOne;
angleTwo = trialAngleTwo;
const getAnglesFromPhaseAngles = (firstPhaseAngle: number, secondPhaseAngle: number) => {
let angleOne;
let angleTwo;
if (Math.abs(interpolationParameters.firstPhaseAngle) < Math.PI / 2) {
angleOne = Math.acos(Math.tan(firstPhaseAngle) * firstDistanceAlongDirection / firstDistancePerpendicularToDirection);
} else {
angleOne = Math.acos(Math.tan(Math.PI - firstPhaseAngle) * -firstDistanceAlongDirection / firstDistancePerpendicularToDirection);
}
return [angleOne, angleTwo, value];
if (Math.abs(interpolationParameters.secondPhaseAngle) < Math.PI / 2) {
angleTwo = Math.acos(Math.tan(secondPhaseAngle) * secondDistanceAlongDirection / secondDistancePerpendicularToDirection);
} else {
angleTwo = Math.acos(Math.tan(Math.PI - secondPhaseAngle) * -secondDistanceAlongDirection / secondDistancePerpendicularToDirection);
}
let firstCenterLength = getVectorMagnitude(firstPositionPossibleVectors[0]);
let firstPerpendicularLength = getVectorMagnitude(firstPositionPossibleVectors[1]);
let secondCenterLength = getVectorMagnitude(secondPositionPossibleVectors[0]);
let secondPerpendicularLength = getVectorMagnitude(secondPositionPossibleVectors[1]);
let angleOneGuess = Math.acos(firstCenterLength * Math.tan(interpolationParameters.firstPhaseAngle) / firstPerpendicularLength);
let angleTwoGuess = Math.acos(secondCenterLength * Math.tan(interpolationParameters.secondPhaseAngle) / secondPerpendicularLength);
let bestAngleOne = 0;
let bestAngleTwo = 0;
let bestValue: number | null = null;
[angleOneGuess, -angleOneGuess].forEach(angleOne => {
[angleTwoGuess, -angleTwoGuess].forEach(angleTwo => {
let value = minimizeFunction(angleOne, angleTwo);
if (bestValue == null || value < bestValue) {
bestValue = value;
bestAngleOne = angleOne;
bestAngleTwo = angleTwo;
return [angleOne, angleTwo];
}
});
});
[bestAngleOne, bestAngleOne, bestValue] = gradientDescent(bestAngleOne, bestAngleTwo);
let bestAngles = [[0], [0]];
let bestDistance: number | null = null;
let bestFirstPosition = addVector(
firstPositionPossibleVectors[0],
for (var i = -100; i < 101; i++) {
for (var j = -100; j < 101; j++) {
let firstPhaseAngle = interpolationParameters.firstPhaseAngle + i * Math.PI / 18000;
let secondPhaseAngle = interpolationParameters.secondPhaseAngle + j * Math.PI / 18000;
let [angleOne, angleTwo] = getAnglesFromPhaseAngles(firstPhaseAngle, secondPhaseAngle);
let distance = distanceFunction(angleOne, angleTwo);
if (bestDistance == null || Math.abs(distance) < Math.abs(bestDistance)) {
bestDistance = distance;
bestAngles = [[angleOne], [angleTwo]];
}
}
}
if (!interpolationParameters.targetAbovePlane) {
bestAngles = [[-bestAngles[0][0]], [-bestAngles[1][0]]];
}
// Do some additional Newtoning on the best angles we've found so far
//bestAngles = performNewtonMethod(bestAngles, 10000);
let targetPositionOne = addVector(multiplyMatrixWithScalar(firstDistanceAlongDirection, firstVectors[0]),
addVector(
multiplyMatrixWithScalar(Math.cos(bestAngleOne), firstPositionPossibleVectors[1]),
multiplyMatrixWithScalar(Math.sin(bestAngleOne), firstPositionPossibleVectors[2])
)
);
let bestSecondPosition = addVector(
secondPositionPossibleVectors[0],
addVector(
multiplyMatrixWithScalar(Math.cos(bestAngleTwo), secondPositionPossibleVectors[1]),
multiplyMatrixWithScalar(Math.sin(bestAngleTwo), secondPositionPossibleVectors[2])
multiplyMatrixWithScalar(Math.cos(bestAngles[0][0]) * firstDistancePerpendicularToDirection, firstVectors[1]),
multiplyMatrixWithScalar(Math.sin(bestAngles[0][0]) * firstDistancePerpendicularToDirection, firstVectors[2])
)
);
if (bestFirstPosition == null || bestSecondPosition == null) {
return null;
}
// We can now find the vector that defines the plane the target is in
let normalVector = normalizeVector(vectorCrossProduct(bestFirstPosition, bestSecondPosition));
let firstTargetPositionDirection = normalizeVector(bestFirstPosition);
let targetPositionTwo = addVector(multiplyMatrixWithScalar(secondDistanceAlongDirection, secondVectors[0]),
addVector(
multiplyMatrixWithScalar(Math.cos(bestAngles[1][0]) * secondDistancePerpendicularToDirection, secondVectors[1]),
multiplyMatrixWithScalar(Math.sin(bestAngles[1][0]) * secondDistancePerpendicularToDirection, secondVectors[2])
)
);
// Rotate the position vector about the normal vector by the true anomaly to find the vector pointing to the periapsis
let identityMatrix = [[1, 0, 0], [0, 1, 0], [0, 0, 1]];
let crossProductMatrix = [
[0, -normalVector[2][0], normalVector[1][0]],
[normalVector[2][0], 0, -normalVector[0][0]],
[-normalVector[1][0], normalVector[0][0], 0]
];
let outerProductMatrix = [
[normalVector[0][0]**2, normalVector[0][0]*normalVector[1][0], normalVector[0][0]*normalVector[2][0]],
[normalVector[0][0]*normalVector[1][0], normalVector[1][0]**2, normalVector[1][0]*normalVector[2][0]],
[normalVector[0][0]*normalVector[2][0], normalVector[1][0]*normalVector[2][0], normalVector[2][0]**2]
let normalVector = normalizeVector(vectorCrossProduct(targetPositionTwo, targetPositionOne));
// Rotate the position vector about this normal vector to get the direction of the periapsis
let ux = normalVector[0][0];
let uy = normalVector[1][0];
let uz = normalVector[2][0];
let rotationMatrix = [
[
ux*ux*(1 - Math.cos(-targetFirstTrueAnomaly)) + Math.cos(-targetFirstTrueAnomaly),
ux*uy*(1 - Math.cos(-targetFirstTrueAnomaly)) - uz * Math.sin(-targetFirstTrueAnomaly),
ux*uz*(1 - Math.cos(-targetFirstTrueAnomaly)) + uy * Math.sin(-targetFirstTrueAnomaly)
],
[
ux*ux*(1 - Math.cos(-targetFirstTrueAnomaly)) + uz * Math.sin(-targetFirstTrueAnomaly),
uy*uy*(1 - Math.cos(-targetFirstTrueAnomaly)) + Math.cos(-targetFirstTrueAnomaly),
uy*uz*(1 - Math.cos(-targetFirstTrueAnomaly)) - ux * Math.sin(-targetFirstTrueAnomaly)
],
[
ux*uz*(1 - Math.cos(-targetFirstTrueAnomaly)) - uy * Math.sin(-targetFirstTrueAnomaly),
uy*uz*(1 - Math.cos(-targetFirstTrueAnomaly)) + ux * Math.sin(-targetFirstTrueAnomaly),
uz*uz*(1 - Math.cos(-targetFirstTrueAnomaly)) + Math.cos(-targetFirstTrueAnomaly)
]
];
let rotationMatrix = addMatrix(
multiplyMatrixWithScalar(Math.cos(-firstTargetTrueAnomaly), identityMatrix),
addMatrix(
multiplyMatrixWithScalar(Math.sin(-firstTargetTrueAnomaly), crossProductMatrix),
multiplyMatrixWithScalar(1 - Math.cos(-firstTargetTrueAnomaly), outerProductMatrix)
)
);
let targetOrbitXVector = matrixMultiply(rotationMatrix, firstTargetPositionDirection);
let targetOrbitYVector = vectorCrossProduct(normalVector, targetOrbitXVector);
let periapsisVector = normalizeVector(matrixMultiply(rotationMatrix, targetPositionOne));
let targetOrbit: Orbit = {
semiLatusRectum: targetSemiLatusRectum,
eccentricity: targetEccentricity,
coordinateAxes: [targetOrbitXVector, targetOrbitYVector, normalVector]
coordinateAxes: [
periapsisVector,
normalizeVector(vectorCrossProduct(normalVector, periapsisVector)),
normalVector
]
};
let timePassed = getTimeBetweenTrueAnomalies(ownCoordinates.trueAnomaly, getTrueAnomalyFromAltitude(interpolationParameters.secondOwnAltitude, ownCoordinates.orbit.semiLatusRectum, ownCoordinates.orbit.eccentricity, ownShipHeadedInwards), ownCoordinates.orbit, body);
let targetCoordinates: OrbitalCoordinates = getOrbitalCoordinatesFromAltitude(
interpolationParameters.secondTargetAltitude,
targetHeadedInwards,
targetOrbit,
body
);
return [getOrbitalCoordinatesFromAltitude(interpolationParameters.secondTargetAltitude, targetHeadedInwards, targetOrbit, body), timePassed];
let epochTrueAnomaly = ownCoordinates.trueAnomaly;
while (epochTrueAnomaly + 2 * Math.PI < ownFirstTrueAnomaly) {
epochTrueAnomaly += 2 * Math.PI;
}
const timeElapsed = getTimeBetweenTrueAnomalies(epochTrueAnomaly, ownSecondTrueAnomaly, ownCoordinates.orbit, body);
return [
targetCoordinates,
timeElapsed
]
}

View File

@ -7,6 +7,7 @@ export interface InterpolationParameters {
targetApoapsis: number,
targetSpeed: number,
targetAltitude: number,
targetAbovePlane: boolean,
firstOwnAltitude: number,
firstTargetAltitude: number,
firstDistance: number,
@ -23,6 +24,7 @@ export const DefaultInterpolationParameters: InterpolationParameters = {
targetApoapsis: 200000,
targetSpeed: 0,
targetAltitude: 0,
targetAbovePlane: true,
firstOwnAltitude: 0,
firstTargetAltitude: 0,
firstDistance: 0,
@ -78,6 +80,13 @@ export class InterpolateOrbitGui {
let speedInput = createInputWithLabel("Target speed:", 0);
let altitudeInput = createInputWithLabel("Target altitude:", 0);
let abovePlaneId = crypto.randomUUID();
let abovePlaneButton = document.createElement("input");
abovePlaneButton.setAttribute("type", "checkbox");
abovePlaneButton.setAttribute("id", abovePlaneId);
let abovePlaneLabel = createLabel(abovePlaneId, "Target is currently above orbital plane");
addToParent([abovePlaneButton, abovePlaneLabel]);
let instantOneHeader = document.createElement("h4");
instantOneHeader.appendChild(document.createTextNode("Measurement one:"));
this.parentDiv.appendChild(instantOneHeader);
@ -102,6 +111,7 @@ export class InterpolateOrbitGui {
let apoapsis = parseFloat(apoapsisInput.value);
let speed = parseFloat(speedInput.value);
let altitude = parseFloat(altitudeInput.value);
let abovePlane = abovePlaneButton.checked;
let firstTargetAltitude = parseFloat(firstTargetAltitudeInput.value);
let firstOwnAltitude = parseFloat(firstOwnAltitudeInput.value);
let firstDistance = parseFloat(firstDistanceInput.value);
@ -122,6 +132,7 @@ export class InterpolateOrbitGui {
targetApoapsis: apoapsis,
targetSpeed: speed,
targetAltitude: altitude,
targetAbovePlane: abovePlane,
firstTargetAltitude: firstTargetAltitude,
firstOwnAltitude: firstOwnAltitude,
firstDistance: firstDistance,
@ -140,6 +151,7 @@ export class InterpolateOrbitGui {
apoapsisInput,
speedInput,
altitudeInput,
abovePlaneButton,
firstOwnAltitudeInput,
firstTargetAltitudeInput,
firstDistanceInput,
@ -171,6 +183,11 @@ export class InterpolateOrbitGui {
apoapsisInput.value = value.targetApoapsis.toString();
speedInput.value = value.targetSpeed.toString();
altitudeInput.value = value.targetAltitude.toString();
if (value.targetAbovePlane) {
abovePlaneButton.setAttribute("checked", "");
} else {
abovePlaneButton.removeAttribute("checked");
}
firstOwnAltitudeInput.value = value.firstOwnAltitude.toString();
firstTargetAltitudeInput.value = value.firstTargetAltitude.toString();
firstDistanceInput.value = value.firstDistance.toString();