diff --git a/index.html b/index.html index 1ac0759..2dfe660 100644 --- a/index.html +++ b/index.html @@ -2,12 +2,210 @@ - + Kerbal calculations -
+

Kerbal calculations

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Current time in game:

+

Time to periapsis:

+
+ +

Planet:

+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ +

Orbital parameters:

+ + + + + + + + + + + + + + + + + + + + + +
+ +

What to calculate:

+ + + + + + +
+
+ +
+ + + + + + + + + + + + + + + + + + +
+
+ +
+

Target plane:

+ + + + + + + + + +
+ +
+ + +

Choose one of the following manoeuvres:

+ + + + + + + + + + + + + + + + + + + + + + + + +
Manoeuvre 1Manoeuvre 2
+
+ +
+

Target orbit:

+ + + + + + + + + + + + + + + + + + + + + +
+ +
+ diff --git a/package-lock.json b/package-lock.json index ac0d838..d48d2bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,11 +7,7 @@ "": { "name": "kerbal-calculations", "version": "0.0.0", - "dependencies": { - "ts-matrix": "^1.4.1" - }, "devDependencies": { - "typescript": "~5.9.3", "vite": "^7.3.1" } }, @@ -1029,26 +1025,6 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/ts-matrix": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/ts-matrix/-/ts-matrix-1.4.1.tgz", - "integrity": "sha512-huf8zaQF/NackiLsyFiWqX9uyZVGyHCXEmSiWd4/DDuioTlADsO82oA5FOudoKVtZVUDYG4HDzN/jSF2eB7Z7A==", - "license": "SEE LICENSE IN LICENSE.md" - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, "node_modules/vite": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", diff --git a/package.json b/package.json index 22ed0b9..fab0842 100644 --- a/package.json +++ b/package.json @@ -9,10 +9,6 @@ "preview": "vite preview" }, "devDependencies": { - "typescript": "~5.9.3", "vite": "^7.3.1" - }, - "dependencies": { - "ts-matrix": "^1.4.1" } } diff --git a/public/satellite.png b/public/satellite.png new file mode 100644 index 0000000..a975894 Binary files /dev/null and b/public/satellite.png differ diff --git a/src/calculations/constants.ts b/src/calculations/constants.ts index c46ecde..c910a57 100644 --- a/src/calculations/constants.ts +++ b/src/calculations/constants.ts @@ -1,4 +1,4 @@ -interface Planet { +export interface Planet { radius: number, gravitationalParameter: number, rotationPeriod: number, @@ -6,7 +6,7 @@ interface Planet { initialMeridianLongitude: number } -const Kerbol: Planet = { +export const Kerbol: Planet = { radius: 261600000, gravitationalParameter: 1.1723328e18, rotationPeriod: 432000, @@ -14,7 +14,7 @@ const Kerbol: Planet = { initialMeridianLongitude: 0 }; -const Moho: Planet = { +export const Moho: Planet = { radius: 250000, gravitationalParameter: 1.6860938e11, rotationPeriod: 1210000, @@ -22,7 +22,7 @@ const Moho: Planet = { initialMeridianLongitude: 0 }; -const Eve: Planet = { +export const Eve: Planet = { radius: 700000, gravitationalParameter: 8.1717302e12, rotationPeriod: 80500, @@ -30,7 +30,7 @@ const Eve: Planet = { initialMeridianLongitude: 0 }; -const Gilly: Planet = { +export const Gilly: Planet = { radius: 13000, gravitationalParameter: 8289449.8, rotationPeriod: 28255, @@ -38,7 +38,7 @@ const Gilly: Planet = { initialMeridianLongitude: 0.0859373 }; -const Kerbin: Planet = { +export const Kerbin: Planet = { radius: 600000, gravitationalParameter: 3.5316000e12, rotationPeriod: 21549.425, @@ -46,7 +46,7 @@ const Kerbin: Planet = { initialMeridianLongitude: 1.571261023 }; -const Mun: Planet = { +export const Mun: Planet = { radius: 200000, gravitationalParameter: 6.5138398e10, rotationPeriod: 138984.38, @@ -54,7 +54,7 @@ const Mun: Planet = { initialMeridianLongitude: 4.0145103174219114 }; -const Minmus: Planet = { +export const Minmus: Planet = { radius: 60000, gravitationalParameter: 1.7658000e9, rotationPeriod: 40400, diff --git a/src/calculations/mathematics.ts b/src/calculations/mathematics.ts new file mode 100644 index 0000000..eaaeb2b --- /dev/null +++ b/src/calculations/mathematics.ts @@ -0,0 +1,208 @@ +export function checkIfValidMatrix(matrix: number[][]): boolean { + if (matrix.length <= 0) { + return false; + } + + // Just make sure each row has equally many columns + var numberOfColumns = -1; + matrix.forEach(row => { + if (numberOfColumns == -1) { + numberOfColumns = row.length; + } + + if (row.length != numberOfColumns) { + return false; + } + }); + + return true; +} + +export function matrixMultiply(matrixOne: number[][], matrixTwo: number[][]): number[][] { + if (!checkIfValidMatrix(matrixOne) || !checkIfValidMatrix(matrixTwo)) { + throw new TypeError("Two valid matrices are required"); + } + + var rowsFirstMatrix = matrixOne.length; + var colsFirstMatrix = matrixOne[0].length; + + var rowsSecondMatrix = matrixTwo.length; + var colsSecondMatrix = matrixTwo[0].length; + + if (colsFirstMatrix != rowsSecondMatrix) { + throw new TypeError("The two matrices do not have the correct dimensions to be multiplied together"); + } + + var result = []; + for (var i = 0; i < rowsFirstMatrix; i++) { + var currentRow = []; + for (var j = 0; j < colsSecondMatrix; j++) { + var currentResult = 0; + for (var k = 0; k < colsFirstMatrix; k++) { + currentResult += matrixOne[i][k] * matrixTwo[k][j]; + } + currentRow.push(currentResult); + } + result.push(currentRow); + } + + return result; +} + +export function vectorDotProduct(vectorOne: number[][], vectorTwo: number[][]): number { + if (!checkIfValidMatrix(vectorOne) || !checkIfValidMatrix(vectorTwo)) { + throw new TypeError("Two valid matrices are required"); + } + + if (vectorOne[0].length != 1 || vectorTwo[0].length != 1) { + throw new TypeError("Vectors can only have one column"); + } + + if (vectorOne.length != vectorTwo.length) { + throw new TypeError("The two vectors need to have the same dimensions"); + } + + var result = 0; + for (var i = 0; i < vectorOne.length; i++) { + result += vectorOne[i][0] * vectorTwo[i][0]; + } + + return result; +} + +export function vectorCrossProduct(vectorOne: number[][], vectorTwo: number[][]): number[][] { + if (!checkIfValidMatrix(vectorOne) || !checkIfValidMatrix(vectorTwo)) { + throw new TypeError("Two valid matrices are required"); + } + + if (vectorOne[0].length != 1 || vectorTwo[0].length != 1) { + throw new TypeError("Vectors can only have one column"); + } + + if (vectorOne.length != vectorTwo.length) { + throw new TypeError("The two vectors need to have the same dimensions"); + } + + if (vectorOne.length != 3) { + throw new TypeError("The vectors need to be three-dimensional"); + } + + return [ + [vectorOne[1][0]*vectorTwo[2][0] - vectorOne[2][0]*vectorTwo[1][0]], + [vectorOne[2][0]*vectorTwo[0][0] - vectorOne[0][0]*vectorTwo[2][0]], + [vectorOne[0][0]*vectorTwo[1][0] - vectorOne[1][0]*vectorTwo[0][0]] + ]; +} + +export function matrixTranspose(matrix: number[][]): number[][] { + if (!checkIfValidMatrix(matrix)) { + throw new TypeError("A valid matrix is required"); + } + + var result = []; + + for (var j = 0; j < matrix[0].length; j++) { + var currentRow = []; + for (var i = 0; i < matrix.length; i++) { + currentRow.push(matrix[i][j]); + } + result.push(currentRow); + } + + return result; +} + +export function addVector(vectorOne: number[][], vectorTwo: number[][]): number[][] { + if (!checkIfValidMatrix(vectorOne) || !checkIfValidMatrix(vectorTwo)) { + throw new TypeError("Two valid matrices are required"); + } + + if (vectorOne[0].length != 1 || vectorTwo[0].length != 1) { + throw new TypeError("Vectors can only have one column"); + } + + if (vectorOne.length != vectorTwo.length) { + throw new TypeError("The two vectors need to have the same dimensions"); + } + + var result = []; + for (var i = 0; i < vectorOne.length; i++) { + result.push([vectorOne[i][0] + vectorTwo[i][0]]); + } + + return result; +} + +export function subtractVector(vectorOne: number[][], vectorTwo: number[][]): number[][] { + if (!checkIfValidMatrix(vectorOne) || !checkIfValidMatrix(vectorTwo)) { + throw new TypeError("Two valid matrices are required"); + } + + if (vectorOne[0].length != 1 || vectorTwo[0].length != 1) { + throw new TypeError("Vectors can only have one column"); + } + + if (vectorOne.length != vectorTwo.length) { + throw new TypeError("The two vectors need to have the same dimensions"); + } + + var result = []; + for (var i = 0; i < vectorOne.length; i++) { + result.push([vectorOne[i][0] - vectorTwo[i][0]]); + } + + return result; +} + +export function getVectorMagnitude(vector: number[][]): number { + if (!checkIfValidMatrix(vector)) { + throw new TypeError("A valid matrix is required"); + } + + if (vector[0].length != 1) { + throw new TypeError("Vectors can only have one column"); + } + + var result = 0; + for (var i = 0; i < vector.length; i++) { + result += vector[i][0]**2; + } + + return Math.sqrt(result); +} + +export function normalizeVector(vector: number[][]) { + if (!checkIfValidMatrix(vector)) { + throw new TypeError("A valid matrix is required"); + } + + if (vector[0].length != 1) { + throw new TypeError("Vectors can only have one column"); + } + + const magnitude = getVectorMagnitude(vector); + + var result = []; + for (var i = 0; i < vector.length; i++) { + result.push([vector[i][0] / magnitude]); + } + + return result; +} + +export function multiplyMatrixWithScalar(scalar: number, matrix: number[][]): number[][] { + if (!checkIfValidMatrix(matrix)) { + throw new TypeError("A valid matrix is required"); + } + + var result = []; + for (var i = 0; i < matrix.length; i++) { + var row = []; + for (var j = 0; j < matrix[i].length; j++) { + row.push(scalar * matrix[i][j]); + } + result.push(row); + } + + return result; +} \ No newline at end of file diff --git a/src/calculations/orbit-calculations.ts b/src/calculations/orbit-calculations.ts index e69de29..b937130 100644 --- a/src/calculations/orbit-calculations.ts +++ b/src/calculations/orbit-calculations.ts @@ -0,0 +1,259 @@ +import type { Planet } from "./constants"; +import { getVectorMagnitude, matrixMultiply, matrixTranspose, multiplyMatrixWithScalar, normalizeVector, subtractVector, vectorCrossProduct, vectorDotProduct } from "./mathematics"; + +export interface Axes { + semiMajor: number, + semiMinor: number, + linearEccentricity: number, + eccentricity: number +} + +export interface OrbitalElementRotations { + argumentOfPeriapsisRotation: number[][], + inclinationRotation: number[][], + longitudeOfAscendingNodeRotation: number[][], + transformationOutOfPlane: number[][], + transformationIntoPlane: number[][] +} + +export interface OrbitalCoordinates { + meanAnomaly: number, + orbitalPeriod: number, + eccentricAnomaly: number, + position: number[][], + latitude: number, + longitude: number, + longitudeOverPlanet: number, + axes: Axes, + orbitalRotations: OrbitalElementRotations, + currentTime: number, + planet: Planet +} + +export interface Manoeuvre { + time: number, + progradeAcceleration: number, + radialAcceleration: number, + normalAcceleration: number, + totalAcceleration: number +} + +const zeroManoeuvre: Manoeuvre = { + time: 0, + progradeAcceleration: 0, + radialAcceleration: 0, + normalAcceleration: 0, + totalAcceleration: 0 +} + +export interface Transfer { + originalPlaneCoordinateAxes: [number[][], number[][], number[][]], + transferPlaneCoordinateAxes: [number[][], number[][], number[][]], + targetPlaneCoordinateAxes: [number[][], number[][], number[][]], + + firstManoeuvre: Manoeuvre, + secondManoeuvre: Manoeuvre +} + +export interface LambertFunction { + (lambda: number): Transfer +} + +export interface LambertSolver { + lambertFunction: LambertFunction, + extremeLambda: number, + parabolaLambda: number +} + +export function getAxes(periapsis: number, apoapsis: number, planet: Planet): Axes { + const semiMajor = (periapsis + apoapsis) / 2 + planet.radius; + const linearEccentricity = (semiMajor - periapsis - planet.radius); + const eccentricity = linearEccentricity / semiMajor; + const semiMinor = Math.sqrt(semiMajor**2 - linearEccentricity**2); + + return { + semiMajor: semiMajor, + semiMinor: semiMinor, + linearEccentricity: linearEccentricity, + eccentricity: eccentricity + }; +} + +export function getOrbitalPeriod(axes: Axes, gravitationalParameter: number): number { + return 2 * Math.PI * Math.sqrt(axes.semiMajor**3 / gravitationalParameter); +} + +export function getMeanAnomalyFromTimeToPeriapsis(timeToPeriapsis: number, periapsis: number, apoapsis: number, planet: Planet): number { + const axes = getAxes(periapsis, apoapsis, planet); + const orbitalPeriod = getOrbitalPeriod(axes, planet.gravitationalParameter); + return (orbitalPeriod - timeToPeriapsis) * 2 * Math.PI / orbitalPeriod; +} + +export function getMeanAnomalyFromEccentricAnomaly(eccentricAnomaly: number, eccentricity: number): number { + return eccentricAnomaly - eccentricity * Math.sin(eccentricAnomaly); +} + +export function getEccentricAnomalyFromMeanAnomaly(meanAnomaly: number, eccentricity: number) { + // Use fixed point iteration + var eccentricAnomaly = meanAnomaly; + const iterationFunction = (eccentricAnomaly: number): number => { + return meanAnomaly + eccentricity * Math.sin(eccentricAnomaly); + } + + while (Math.abs(eccentricAnomaly - eccentricity*Math.sin(eccentricAnomaly) - meanAnomaly) > 0.00000001) { + eccentricAnomaly = iterationFunction(eccentricAnomaly); + } + + return eccentricAnomaly; +} + +export function getOrbitalElementRotations(inclination: number, longitudeOfAscendingNode: number, argumentOfPeriapsis: number): OrbitalElementRotations { + const argumentOfPeriapsisRotation = + [ + [Math.cos(argumentOfPeriapsis), -Math.sin(argumentOfPeriapsis), 0], + [Math.sin(argumentOfPeriapsis), Math.cos(argumentOfPeriapsis), 0], + [0, 0, 1] + ]; + + const inclinationRotation = + [ + [1, 0, 0], + [0, Math.cos(inclination), -Math.sin(inclination)], + [0, Math.sin(inclination), Math.cos(inclination)] + ]; + + const longitudeOfAscendingNodeRotation = + [ + [Math.cos(longitudeOfAscendingNode), -Math.sin(longitudeOfAscendingNode), 0], + [Math.sin(longitudeOfAscendingNode), Math.cos(longitudeOfAscendingNode), 0], + [0, 0, 1] + ]; + + const transformationOutOfPlane = matrixMultiply(longitudeOfAscendingNodeRotation, matrixMultiply(inclinationRotation, argumentOfPeriapsisRotation)); + const transformationIntoPlane = matrixTranspose(transformationOutOfPlane); + + return { + argumentOfPeriapsisRotation: argumentOfPeriapsisRotation, + inclinationRotation: inclinationRotation, + longitudeOfAscendingNodeRotation: longitudeOfAscendingNodeRotation, + transformationOutOfPlane: transformationOutOfPlane, + transformationIntoPlane: transformationIntoPlane + }; +} + +export function getOrbitalCoordinates(currentTime: number, timeToPeriapsis: number, periapsis: number, apoapsis: number, inclination: number, longitudeOfAscendingNode: number, argumentOfPeriapsis: number, planet: Planet): OrbitalCoordinates { + const axes = getAxes(periapsis, apoapsis, planet); + const orbitalPeriod = getOrbitalPeriod(axes, planet.gravitationalParameter); + const meanAnomaly = getMeanAnomalyFromTimeToPeriapsis(timeToPeriapsis, periapsis, apoapsis, planet); + const eccentricAnomaly = getEccentricAnomalyFromMeanAnomaly(meanAnomaly, axes.eccentricity); + const orbitalRotations = getOrbitalElementRotations(inclination, longitudeOfAscendingNode, argumentOfPeriapsis); + const localPosition = [ + [axes.semiMajor * Math.cos(eccentricAnomaly) - axes.linearEccentricity], + [axes.semiMinor * Math.sin(eccentricAnomaly)], + [0] + ]; + + const globalPosition = matrixMultiply(orbitalRotations.transformationOutOfPlane, localPosition); + const longitude = Math.atan2(globalPosition[1][0], globalPosition[0][0]); + const latitude = Math.atan2(globalPosition[2][0], Math.sqrt(globalPosition[0][0]**2 + globalPosition[1][0]**2)); + + const currentMeridianLongitude = planet.initialMeridianLongitude + currentTime * 2 * Math.PI / planet.rotationPeriod; + const longitudeOverPlanet = ((longitude - currentMeridianLongitude) % (2 * Math.PI) + 2 * Math.PI) % (2 * Math.PI); + + return { + meanAnomaly: meanAnomaly, + orbitalPeriod: orbitalPeriod, + eccentricAnomaly: eccentricAnomaly, + position: globalPosition, + latitude: latitude, + longitude: longitude, + longitudeOverPlanet: longitudeOverPlanet, + axes: axes, + orbitalRotations: orbitalRotations, + currentTime: currentTime, + planet: planet + }; +} + +export function calculateSimplePlaneChange(coordinates: OrbitalCoordinates, targetInclination: number, targetLongitudeOfAscendingNode: number, circularizeOrbit: boolean): [Manoeuvre, Manoeuvre] { + const targetRotations = getOrbitalElementRotations(targetInclination, targetLongitudeOfAscendingNode, 0); + const targetPlaneNormalVector = normalizeVector(matrixMultiply(coordinates.orbitalRotations.transformationIntoPlane, matrixMultiply(targetRotations.transformationOutOfPlane, [[0], [0], [1]]))); + + // Check if target plane is equal to current plane + if (1 - Math.abs(vectorDotProduct(targetPlaneNormalVector, [[0], [0], [1]])) < 0.0001) { + return [zeroManoeuvre, zeroManoeuvre]; + } + + // Find vector that is normal to both current plane vector and target plane vector (i.e. lies in both planes) + const normalToAll = vectorCrossProduct(targetPlaneNormalVector, [[0], [0], [1]]); + + // Find the true anomaly of this vector + const trueAnomaly = Math.atan2(normalToAll[1][0], normalToAll[0][0]); + + // Caclulate the two possible manoeuvres + var manoeuvres: Manoeuvre[] = []; + const anomalies = [trueAnomaly, trueAnomaly + Math.PI]; + anomalies.forEach(anomaly => { + const eccentricAnomaly = 2 * Math.atan(Math.sqrt((1 - coordinates.axes.eccentricity) / (1 + coordinates.axes.eccentricity)) * Math.tan(anomaly / 2)); + var meanAnomaly = getMeanAnomalyFromEccentricAnomaly(eccentricAnomaly, coordinates.axes.eccentricity); + + while (meanAnomaly < coordinates.meanAnomaly) { + meanAnomaly += 2*Math.PI; + } + + const manoeuvreTime = (meanAnomaly - coordinates.meanAnomaly) * coordinates.orbitalPeriod / (2 * Math.PI) + coordinates.currentTime; + + const progradeVector = normalizeVector([ + [-coordinates.axes.semiMajor * Math.sin(eccentricAnomaly)], + [coordinates.axes.semiMinor * Math.cos(eccentricAnomaly)], + [0] + ]); + + const normalVector = [ + [0], + [0], + [1] + ]; + + const radialVector = vectorCrossProduct(normalVector, progradeVector); + + const radius = coordinates.axes.semiMajor * (1 - coordinates.axes.eccentricity * Math.cos(eccentricAnomaly)); + const speed = Math.sqrt(coordinates.planet.gravitationalParameter * (2 / radius - 1 / coordinates.axes.semiMajor)); + + const velocity = multiplyMatrixWithScalar(speed, progradeVector); + var velocityChange: number[][]; + var deltaV: number; + + if (!circularizeOrbit) { + deltaV = vectorDotProduct(velocity, multiplyMatrixWithScalar(-1, targetPlaneNormalVector)); + velocityChange = multiplyMatrixWithScalar(deltaV, targetPlaneNormalVector); + deltaV = Math.abs(deltaV); + } else { + const targetSpeed = Math.sqrt(coordinates.planet.gravitationalParameter / radius); + const positionVector = [ + [coordinates.axes.semiMajor * Math.cos(eccentricAnomaly)], + [coordinates.axes.semiMinor * Math.sin(eccentricAnomaly)], + [0] + ]; + + const targetDirection = normalizeVector(vectorCrossProduct(targetPlaneNormalVector, positionVector)); + const targetVelocity = multiplyMatrixWithScalar(targetSpeed, targetDirection); + velocityChange = subtractVector(targetVelocity, velocity); + deltaV = getVectorMagnitude(velocityChange); + } + + const progradeChange = vectorDotProduct(velocityChange, progradeVector); + const radialChange = -vectorDotProduct(velocityChange, radialVector); + const normalChange = vectorDotProduct(velocityChange, normalVector); + + manoeuvres.push({ + time: manoeuvreTime, + progradeAcceleration: progradeChange, + radialAcceleration: radialChange, + normalAcceleration: normalChange, + totalAcceleration: deltaV + }); + }); + + return [manoeuvres[0], manoeuvres[1]]; +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 6396b50..84f8183 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,24 +1,217 @@ -import './style.css' -import typescriptLogo from './typescript.svg' -import viteLogo from '/vite.svg' -import { setupCounter } from './counter.ts' +import { Eve, Gilly, Kerbin, Kerbol, Minmus, Moho, Mun, type Planet } from "./calculations/constants"; +import {calculateSimplePlaneChange, getOrbitalCoordinates} from "./calculations/orbit-calculations"; -document.querySelector('#app')!.innerHTML = ` -
- - - - - - -

Vite + TypeScript

-
- -
-

- Click on the Vite and TypeScript logos to learn more -

-
-` +const dateInputYears = document.getElementById("dateYear") as HTMLInputElement; +const dateInputDays = document.getElementById("dateDay") as HTMLInputElement; +const dateInputHours = document.getElementById("dateHours") as HTMLInputElement; +const dateInputMinutes = document.getElementById("dateMinutes") as HTMLInputElement; +const dateInputSeconds = document.getElementById("dateSeconds") as HTMLInputElement; -setupCounter(document.querySelector('#counter')!) +const periapsisInputYears = document.getElementById("periapsisYears") as HTMLInputElement; +const periapsisInputDays = document.getElementById("periapsisDays") as HTMLInputElement; +const periapsisInputHours = document.getElementById("periapsisHours") as HTMLInputElement; +const periapsisInputMinutes = document.getElementById("periapsisMinutes") as HTMLInputElement; +const periapsisInputSeconds = document.getElementById("periapsisSeconds") as HTMLInputElement; + +const mohoButton = document.getElementById("moho") as HTMLInputElement; +const eveButton = document.getElementById("eve") as HTMLInputElement; +const gillyButton = document.getElementById("gilly") as HTMLInputElement; +const kerbinButton = document.getElementById("kerbin") as HTMLInputElement; +const munButton = document.getElementById("mun") as HTMLInputElement; +const minmusButton = document.getElementById("minmus") as HTMLInputElement; + +const currentPeriapsisInput = document.getElementById("currentPeriapsis") as HTMLInputElement; +const currentApoapsisInput = document.getElementById("currentApoapsis") as HTMLInputElement; +const currentInclinationInput = document.getElementById("currentInclination") as HTMLInputElement; +const currentLANInput = document.getElementById("currentLAN") as HTMLInputElement; +const currentAOPInput = document.getElementById("currentAOP") as HTMLInputElement; + +const coordinatesRadio = document.getElementById("coordinates") as HTMLInputElement; +const simplePlaneChangeRadio = document.getElementById("simplePlaneChange") as HTMLInputElement; +const orbitChangeRadio = document.getElementById("orbitChange") as HTMLInputElement; + +const coordinatesDiv = document.getElementById("coordinatesDiv"); +const coordinateCalculationButton = document.getElementById("calculateCoordinatesButton") as HTMLButtonElement; +const meanAnomalyInput = document.getElementById("calculatedMeanAnomaly") as HTMLInputElement; +const eccentricAnomalyInput = document.getElementById("calculatedEccentricAnomaly") as HTMLInputElement; +const positionXInput = document.getElementById("calculatedX") as HTMLInputElement; +const positionYInput = document.getElementById("calculatedY") as HTMLInputElement; +const positionZInput = document.getElementById("calculatedZ") as HTMLInputElement; +const latitudeInput = document.getElementById("calculatedLatitude") as HTMLInputElement; +const longitudeInput = document.getElementById("calculatedLongitude") as HTMLInputElement; +const longitudeOverPlanetInput = document.getElementById("calculatedPlanetLongitude") as HTMLInputElement; + +const simplePlaneChangeDiv = document.getElementById("simplePlaneChangeDiv"); +const targetInclinationInput = document.getElementById("targetInclination") as HTMLInputElement; +const targetLANInput = document.getElementById("targetLAN") as HTMLInputElement; +const circularizeCheckbox = document.getElementById("circularizeOrbit") as HTMLInputElement; +const simplePlaneChangeButton = document.getElementById("simplePlaneChangeButton") as HTMLButtonElement; + +const orbitChangeDiv = document.getElementById("orbitChangeDiv"); + +const simplePlaneChangeTimes = [ + document.getElementById("simpleManoeuvreTime1") as HTMLInputElement, + document.getElementById("simpleManoeuvreTime2") as HTMLInputElement +]; +const simplePlaneChangeProgrades = [ + document.getElementById("simpleManoeuvrePrograde1") as HTMLInputElement, + document.getElementById("simpleManoeuvrePrograde2") as HTMLInputElement +]; + +const simplePlaneChangeRadials = [ + document.getElementById("simpleManoeuvreRadial1") as HTMLInputElement, + document.getElementById("simpleManoeuvreRadial2") as HTMLInputElement +]; + +const simplePlaneChangeNormals = [ + document.getElementById("simpleManoeuvreNormal1") as HTMLInputElement, + document.getElementById("simpleManoeuvreNormal2") as HTMLInputElement +]; + +const simplePlaneChangeTotals = [ + document.getElementById("simpleManoeuvreTotal1") as HTMLInputElement, + document.getElementById("simpleManoeuvreTotal2") as HTMLInputElement +]; + + +function getDate(): number { + const years = parseInt(dateInputYears.value); + const days = parseInt(dateInputDays.value); + const hours = parseInt(dateInputHours.value); + const minutes = parseInt(dateInputMinutes.value); + const seconds = parseInt(dateInputSeconds.value); + + return ((((years - 1) * 426 + days - 1) * 6 + hours) * 60 + minutes) * 60 + seconds; +} + +function getTimeToPeriapsis(): number { + const years = parseInt(periapsisInputYears.value); + const days = parseInt(periapsisInputDays.value); + const hours = parseInt(periapsisInputHours.value); + const minutes = parseInt(periapsisInputMinutes.value); + const seconds = parseInt(periapsisInputSeconds.value); + + return (((years * 426 + days) * 6 + hours) * 60 + minutes) * 60 + seconds; +} + +function getPlanet(): Planet { + if (mohoButton.checked) { + return Moho; + } else if (eveButton.checked) { + return Eve; + } else if (gillyButton.checked) { + return Gilly; + } else if (kerbinButton.checked) { + return Kerbin; + } else if (munButton.checked) { + return Mun; + } else if (minmusButton.checked) { + return Minmus; + } + + return Kerbol; +} + +function selectCalculationType() { + if (coordinatesRadio.checked) { + coordinatesDiv?.style.setProperty("display", "block"); + simplePlaneChangeDiv?.style.setProperty("display", "none"); + orbitChangeDiv?.style.setProperty("display", "none"); + } else if (simplePlaneChangeRadio.checked) { + coordinatesDiv?.style.setProperty("display", "none"); + simplePlaneChangeDiv?.style.setProperty("display", "block"); + orbitChangeDiv?.style.setProperty("display", "none"); + } else if (orbitChangeRadio.checked) { + coordinatesDiv?.style.setProperty("display", "none"); + simplePlaneChangeDiv?.style.setProperty("display", "none"); + orbitChangeDiv?.style.setProperty("display", "block"); + } +} + +coordinatesRadio.onclick = selectCalculationType; +simplePlaneChangeRadio.onclick = selectCalculationType; +orbitChangeRadio.onclick = selectCalculationType; + +interface CommonInputs { + periapsis: number, + apoapsis: number, + timeToPeriapsis: number, + planet: Planet, + inclination: number, + longitudeOfAscendingNode: number, + argumentOfPeriapsis: number, + timeElapsed: number +} + +function getCommonInputs(): CommonInputs { + const periapsis = parseFloat(currentPeriapsisInput.value); + const apoapsis = parseFloat(currentApoapsisInput.value); + const timeToPeriapsis = getTimeToPeriapsis(); + const planet = getPlanet(); + const inclination = parseFloat(currentInclinationInput.value) * Math.PI / 180.0; + const longitudeOfAscendingNode = parseFloat(currentLANInput.value) * Math.PI / 180.0; + const argumentOfPeriapsis = parseFloat(currentAOPInput.value) * Math.PI / 180.0; + const timeElapsed = getDate(); + + return { + periapsis: periapsis, + apoapsis: apoapsis, + timeToPeriapsis: timeToPeriapsis, + planet: planet, + inclination: inclination, + longitudeOfAscendingNode: longitudeOfAscendingNode, + argumentOfPeriapsis: argumentOfPeriapsis, + timeElapsed: timeElapsed + } +} + +coordinateCalculationButton.addEventListener("click", _ => { + const commonInputs = getCommonInputs(); + const orbitalCoordinates = getOrbitalCoordinates( + commonInputs.timeElapsed, + commonInputs.timeToPeriapsis, + commonInputs.periapsis, + commonInputs.apoapsis, + commonInputs.inclination, + commonInputs.longitudeOfAscendingNode, + commonInputs.argumentOfPeriapsis, + commonInputs.planet + ); + + meanAnomalyInput.value = orbitalCoordinates.meanAnomaly.toFixed(4); + eccentricAnomalyInput.value = orbitalCoordinates.eccentricAnomaly.toFixed(4); + positionXInput.value = orbitalCoordinates.position[0][0].toFixed(2); + positionYInput.value = orbitalCoordinates.position[1][0].toFixed(2); + positionZInput.value = orbitalCoordinates.position[2][0].toFixed(2); + latitudeInput.value = (orbitalCoordinates.latitude * 180 / Math.PI).toFixed(6); + longitudeInput.value = (orbitalCoordinates.longitude * 180 / Math.PI).toFixed(6); + longitudeOverPlanetInput.value = (orbitalCoordinates.longitudeOverPlanet * 180 / Math.PI).toFixed(6); +}); + +simplePlaneChangeButton.addEventListener("click", _ => { + const commonInputs = getCommonInputs(); + const orbitalCoordinates = getOrbitalCoordinates( + commonInputs.timeElapsed, + commonInputs.timeToPeriapsis, + commonInputs.periapsis, + commonInputs.apoapsis, + commonInputs.inclination, + commonInputs.longitudeOfAscendingNode, + commonInputs.argumentOfPeriapsis, + commonInputs.planet + ); + + const targetInclination = parseFloat(targetInclinationInput.value) * Math.PI / 180.0; + const targetLongitudeOfAscendingNode = parseFloat(targetLANInput.value) * Math.PI / 180.0; + const manoeuvres = calculateSimplePlaneChange(orbitalCoordinates, targetInclination, targetLongitudeOfAscendingNode, circularizeCheckbox.checked); + manoeuvres.sort((a, b) => a.totalAcceleration - b.totalAcceleration); + manoeuvres.forEach((manoeuvre, index) => { + simplePlaneChangeTimes[index].value = manoeuvre.time.toFixed(0); + simplePlaneChangeProgrades[index].value = manoeuvre.progradeAcceleration.toFixed(1); + simplePlaneChangeRadials[index].value = manoeuvre.radialAcceleration.toFixed(1); + simplePlaneChangeNormals[index].value = manoeuvre.normalAcceleration.toFixed(1); + simplePlaneChangeTotals[index].value = manoeuvre.totalAcceleration.toFixed(1); + }); +}); + +selectCalculationType(); \ No newline at end of file