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
+
+
+ Planet:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Orbital parameters:
+
+
+ What to calculate:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Target plane:
+
+
+
+
+
+
Choose one of the following manoeuvres:
+
+
+
+
+
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