Stuff is still working

This commit is contained in:
Martin Asprusten 2026-03-30 11:42:09 +02:00
parent aa45755cfc
commit 2b4fb65d4f
No known key found for this signature in database
15 changed files with 901 additions and 1034 deletions

View File

@ -9,11 +9,8 @@
</head> </head>
<body> <body>
<h1>Kerbal calculations</h1> <h1>Kerbal calculations</h1>
<h3>Current time in game:</h3> <h3>Earliest allowed manoeuvre time:</h3>
<div id="timesDiv"></div> <div id="currentTimeDiv"></div>
<h3>Time to periapsis:</h3>
<div id="periapsisDiv"></div>
<script type="module" src="/src/main.ts"></script>
<h3>Planet:</h3> <h3>Planet:</h3>
<div id="planetsDiv"></div> <div id="planetsDiv"></div>
<h3>Orbital parameters:</h3> <h3>Orbital parameters:</h3>
@ -21,5 +18,7 @@
<h3>What to calculate:</h3> <h3>What to calculate:</h3>
<div id="calculationChoice"></div> <div id="calculationChoice"></div>
<div id="calculation"></div> <div id="calculation"></div>
<script type="module" src="/src/main.ts"></script>
</body> </body>
</html> </html>

View File

@ -18,6 +18,7 @@ export interface OrbitalCoordinates {
meanAnomaly: number, meanAnomaly: number,
eccentricAnomaly: number, eccentricAnomaly: number,
trueAnomaly: number, trueAnomaly: number,
meanAngularMotion: number,
position: number[][] position: number[][]
} }
@ -283,12 +284,11 @@ export function getOrbitFromEccentricity(periapsis: number, eccentricity: number
} }
}; };
export function getOrbitalCoordinates(timeToPeriapsis: number, orbit: Orbit, planet: Body) { export function getEccentricAndTrueAnomalyFromMeanAnomaly(meanAnomaly: number, eccentricity: number): [number, number] {
const meanAnomaly = -Math.sqrt(planet.gravitationalParameter / Math.abs(orbit.semiLatusRectum / (orbit.eccentricity**2 - 1))**3) * timeToPeriapsis;
var eccentricAnomaly; var eccentricAnomaly;
var trueAnomaly; var trueAnomaly;
if (Math.abs(orbit.eccentricity - 1) < 0.0001) { if (Math.abs(eccentricity - 1) < 0.0001) {
// Parabolic trajectory, Barker's equation // Parabolic trajectory, Barker's equation
const A = 3 * meanAnomaly / Math.sqrt(8); const A = 3 * meanAnomaly / Math.sqrt(8);
const B = Math.pow(A + Math.sqrt(A**2 + 1), 1/3); const B = Math.pow(A + Math.sqrt(A**2 + 1), 1/3);
@ -300,25 +300,34 @@ export function getOrbitalCoordinates(timeToPeriapsis: number, orbit: Orbit, pla
var keplerEquationDerivative; var keplerEquationDerivative;
eccentricAnomaly = meanAnomaly; eccentricAnomaly = meanAnomaly;
if (orbit.eccentricity < 1) { if (eccentricity < 1) {
keplerEquation = (guess: number) => guess - orbit.eccentricity * Math.sin(guess) - meanAnomaly; keplerEquation = (guess: number) => guess - eccentricity * Math.sin(guess) - meanAnomaly;
keplerEquationDerivative = (guess: number) => 1 - orbit.eccentricity * Math.cos(guess); keplerEquationDerivative = (guess: number) => 1 - eccentricity * Math.cos(guess);
} else { } else {
keplerEquation = (guess: number) => orbit.eccentricity * Math.sinh(guess) - guess - meanAnomaly; keplerEquation = (guess: number) => eccentricity * Math.sinh(guess) - guess - meanAnomaly;
keplerEquationDerivative = (guess: number) => orbit.eccentricity * Math.cosh(guess) - 1; keplerEquationDerivative = (guess: number) => eccentricity * Math.cosh(guess) - 1;
} }
while (Math.abs(keplerEquation(eccentricAnomaly)) > 0.000001) { while (Math.abs(keplerEquation(eccentricAnomaly)) > 0.000001) {
eccentricAnomaly = eccentricAnomaly - keplerEquation(eccentricAnomaly) / keplerEquationDerivative(eccentricAnomaly); eccentricAnomaly = eccentricAnomaly - keplerEquation(eccentricAnomaly) / keplerEquationDerivative(eccentricAnomaly);
} }
if (orbit.eccentricity < 1) { if (eccentricity < 1) {
trueAnomaly = 2*Math.atan(Math.sqrt((1 + orbit.eccentricity) / (1 - orbit.eccentricity)) * Math.tan(eccentricAnomaly / 2)); trueAnomaly = 2*Math.atan(Math.sqrt((1 + eccentricity) / (1 - eccentricity)) * Math.tan(eccentricAnomaly / 2));
} else { } else {
trueAnomaly = 2*Math.atan(Math.sqrt((orbit.eccentricity + 1) / (orbit.eccentricity - 1)) * Math.tanh(eccentricAnomaly / 2)); trueAnomaly = 2*Math.atan(Math.sqrt((eccentricity + 1) / (eccentricity - 1)) * Math.tanh(eccentricAnomaly / 2));
} }
} }
return [eccentricAnomaly, trueAnomaly];
}
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);
const meanAnomaly = meanAngularMotion * -timeToPeriapsis;
const [eccentricAnomaly, trueAnomaly] = getEccentricAndTrueAnomalyFromMeanAnomaly(meanAnomaly, orbit.eccentricity);
const radius = orbit.semiLatusRectum / (1 + orbit.eccentricity * Math.cos(trueAnomaly)); const radius = orbit.semiLatusRectum / (1 + orbit.eccentricity * Math.cos(trueAnomaly));
const localX = radius * Math.cos(trueAnomaly); const localX = radius * Math.cos(trueAnomaly);
const localY = radius * Math.sin(trueAnomaly); const localY = radius * Math.sin(trueAnomaly);
@ -329,10 +338,64 @@ export function getOrbitalCoordinates(timeToPeriapsis: number, orbit: Orbit, pla
meanAnomaly: meanAnomaly, meanAnomaly: meanAnomaly,
eccentricAnomaly: eccentricAnomaly, eccentricAnomaly: eccentricAnomaly,
trueAnomaly: trueAnomaly, trueAnomaly: trueAnomaly,
meanAngularMotion: meanAngularMotion,
position: globalPosition, position: globalPosition,
} }
} }
export function getOrbitalCoordinatesFromAltitude(altitude: number, headingInwards: boolean, orbit: Orbit, planet: Body): OrbitalCoordinates {
// If the eccentricity is zero, this will not work, and we may as well just return assume we're at the periapsis
if (orbit.eccentricity < 0.0001) {
return getOrbitalCoordinates(0, orbit, planet);
}
let cosineOfTrueAnomaly = (orbit.semiLatusRectum - altitude) / (orbit.eccentricity * altitude);
if (cosineOfTrueAnomaly < -1 || cosineOfTrueAnomaly > 1) {
// We're outside the range of this function. Return NAN
return {
orbit: orbit,
meanAnomaly: NaN,
eccentricAnomaly: NaN,
trueAnomaly: NaN,
meanAngularMotion: NaN,
position: [[NaN], [NaN], [NaN]]
};
}
let trueAnomaly = Math.acos(cosineOfTrueAnomaly) * (headingInwards ? -1 : 1);
let localX = altitude * Math.cos(trueAnomaly);
let localY = altitude * Math.sin(trueAnomaly);
let globalPosition = addVector(
multiplyMatrixWithScalar(localX, orbit.coordinateAxes[0]),
multiplyMatrixWithScalar(localY, orbit.coordinateAxes[1])
);
let eccentricAnomaly;
let meanAnomaly;
if (Math.abs(orbit.eccentricity - 1) < 0.0001) {
eccentricAnomaly = trueAnomaly;
meanAnomaly = eccentricAnomaly;
} else if (orbit.eccentricity < 1) {
eccentricAnomaly = Math.atan2(Math.sqrt(1 - orbit.eccentricity**2)*Math.sin(trueAnomaly), orbit.eccentricity + Math.cos(trueAnomaly));
meanAnomaly = eccentricAnomaly - eccentricAnomaly * Math.sin(eccentricAnomaly);
} else {
eccentricAnomaly = 2 * Math.atanh(Math.sqrt((orbit.eccentricity - 1) / (orbit.eccentricity + 1)) * Math.tan(trueAnomaly / 2));
meanAnomaly = orbit.eccentricity * Math.sinh(eccentricAnomaly) - eccentricAnomaly;
}
const meanAngularMotion = Math.sqrt(planet.gravitationalParameter * Math.abs(1 - orbit.eccentricity**2)**3 / orbit.semiLatusRectum**3);
return {
orbit: orbit,
meanAnomaly: meanAnomaly,
eccentricAnomaly: eccentricAnomaly,
trueAnomaly: trueAnomaly,
meanAngularMotion: meanAngularMotion,
position: globalPosition
};
}
export function getTimeBetweenTrueAnomalies(startingTrueAnomaly: number, endingTrueAnomaly: number, orbit: Orbit, planet: Body): number { export function getTimeBetweenTrueAnomalies(startingTrueAnomaly: number, endingTrueAnomaly: number, orbit: Orbit, planet: Body): number {
let extraTime = 0; let extraTime = 0;
if (Math.abs(orbit.eccentricity - 1) < 0.00001) { if (Math.abs(orbit.eccentricity - 1) < 0.00001) {
@ -387,6 +450,35 @@ export function getTimeBetweenTrueAnomalies(startingTrueAnomaly: number, endingT
} }
} }
export function extrapolateTrajectory(addedTime: number, orbitalCoordinates: OrbitalCoordinates, body: Body): OrbitalCoordinates | null {
const newMeanAnomaly = orbitalCoordinates.meanAnomaly + orbitalCoordinates.meanAngularMotion * addedTime;
const [newEccentricAnomaly, newTrueAnomaly] = getEccentricAndTrueAnomalyFromMeanAnomaly(newMeanAnomaly, orbitalCoordinates.orbit.eccentricity);
// Calculate new position
const newRadius = orbitalCoordinates.orbit.semiLatusRectum / (1 + orbitalCoordinates.orbit.eccentricity * Math.cos(newTrueAnomaly));
// If we are outside our planet's sphere of influence, return nothing
if (newRadius >= body.sphereOfInfluence) {
return null;
}
const localX = newRadius * Math.cos(newTrueAnomaly);
const localY = newRadius * Math.sin(newTrueAnomaly);
const newPosition = addVector(
multiplyMatrixWithScalar(localX, orbitalCoordinates.orbit.coordinateAxes[0]),
multiplyMatrixWithScalar(localY, orbitalCoordinates.orbit.coordinateAxes[1])
);
return {
orbit: orbitalCoordinates.orbit,
meanAnomaly: newMeanAnomaly,
eccentricAnomaly: newEccentricAnomaly,
trueAnomaly: newTrueAnomaly,
meanAngularMotion: orbitalCoordinates.meanAngularMotion,
position: newPosition
};
}
export function getLocalVectors(trueAnomaly: number, orbit: Orbit): LocalVectors { export function getLocalVectors(trueAnomaly: number, orbit: Orbit): LocalVectors {
const changeInX = -orbit.semiLatusRectum * Math.sin(trueAnomaly) / (1 + orbit.eccentricity * Math.cos(trueAnomaly))**2; const changeInX = -orbit.semiLatusRectum * Math.sin(trueAnomaly) / (1 + orbit.eccentricity * Math.cos(trueAnomaly))**2;
const changeInY = orbit.semiLatusRectum * (orbit.eccentricity + Math.cos(trueAnomaly)) / (1 + orbit.eccentricity * Math.cos(trueAnomaly))**2; const changeInY = orbit.semiLatusRectum * (orbit.eccentricity + Math.cos(trueAnomaly)) / (1 + orbit.eccentricity * Math.cos(trueAnomaly))**2;
@ -554,8 +646,8 @@ export function findCheapestTransfer(startingSituation: OrbitalCoordinates, targ
} }
} else { } else {
let finalAnomaly = Math.abs(Math.acos((startingSituation.orbit.semiLatusRectum - body.sphereOfInfluence) / (body.sphereOfInfluence * startingSituation.orbit.eccentricity))); let finalAnomaly = Math.abs(Math.acos((startingSituation.orbit.semiLatusRectum - body.sphereOfInfluence) / (body.sphereOfInfluence * startingSituation.orbit.eccentricity)));
for (var i = 0; i < 100; i++) { let step = (finalAnomaly - startingSituation.trueAnomaly) / 100;
let step = (finalAnomaly - startingSituation.trueAnomaly) / 100; for (var i = 1; i < 101; i++) {
startingTrueAnomalies.push(startingSituation.trueAnomaly + i * step); startingTrueAnomalies.push(startingSituation.trueAnomaly + i * step);
} }
} }
@ -710,41 +802,68 @@ export function findCheapestIntercept(startingSituation: OrbitalCoordinates, tar
} }
} }
let maxStartTime = 1e99;
let maxStartTrueAnomaly = 1e99;
let maxEndTime = 1e99;
let maxEndTrueAnomaly = 1e99;
if (interceptOrbitStable && startingOrbitStable) { if (interceptOrbitStable && startingOrbitStable) {
// If both orbits are stable, we'll check for three whole orbits of the largest orbit // If both orbits are stable, we'll check for three whole orbits of the largest orbit
let startingOrbitPeriod = getTimeBetweenTrueAnomalies(0, 2*Math.PI, startingSituation.orbit, body); let startingOrbitPeriod = getTimeBetweenTrueAnomalies(0, 2*Math.PI, startingSituation.orbit, body);
let targetOrbitPeriod = getTimeBetweenTrueAnomalies(0, 2*Math.PI, targetSituation.orbit, body); let targetOrbitPeriod = getTimeBetweenTrueAnomalies(0, 2*Math.PI, targetSituation.orbit, body);
let maxStartTime = 3 * Math.max(startingOrbitPeriod, targetOrbitPeriod); maxStartTime = 3 * Math.max(startingOrbitPeriod, targetOrbitPeriod);
let maxEndTime = 4 * Math.max(startingOrbitPeriod, targetOrbitPeriod); maxEndTime = 4 * Math.max(startingOrbitPeriod, targetOrbitPeriod);
} else {
maxStartTime = 1e99;
maxStartTrueAnomaly = 1e99;
maxEndTime = 1e99;
maxEndTrueAnomaly = 1e99;
let anomalyCounter = 0; if (!startingOrbitStable) {
while (true) { maxStartTrueAnomaly = Math.abs(Math.acos((startingSituation.orbit.semiLatusRectum - body.sphereOfInfluence) / (body.sphereOfInfluence * startingSituation.orbit.eccentricity)));
let possibleStart = startingSituation.trueAnomaly + anomalyCounter * 2 * Math.PI / 100; maxStartTime = getTimeBetweenTrueAnomalies(startingSituation.trueAnomaly, maxStartTrueAnomaly, startingSituation.orbit, body);
let possibleStartTime = getTimeBetweenTrueAnomalies(startingSituation.trueAnomaly, possibleStart, startingSituation.orbit, body); maxEndTime = 2*maxStartTime;
if (possibleStartTime > maxStartTime) {
break;
}
starts.push([possibleStartTime, possibleStart]);
anomalyCounter += 1;
} }
anomalyCounter = 0; if (!interceptOrbitStable) {
while (true) { maxEndTrueAnomaly = Math.abs(Math.acos((targetSituation.orbit.semiLatusRectum - body.sphereOfInfluence) / (body.sphereOfInfluence * targetSituation.orbit.eccentricity)));
let possibleEnd = targetSituation.trueAnomaly + anomalyCounter * 2 * Math.PI / 100.0; maxEndTime = getTimeBetweenTrueAnomalies(targetSituation.trueAnomaly, maxEndTrueAnomaly, targetSituation.orbit, body);
let possibleEndTime = getTimeBetweenTrueAnomalies(targetSituation.trueAnomaly, possibleEnd, targetSituation.orbit, body);
possibleEnd += extraTrueAnomaly;
if (possibleEndTime > maxEndTime) {
break;
}
intercepts.push([possibleEndTime, possibleEnd]);
anomalyCounter += 1;
} }
} }
let anomalyCounter = 1;
while (true) {
let possibleStart = startingSituation.trueAnomaly + anomalyCounter * 2 * Math.PI / 100;
if (possibleStart > maxStartTrueAnomaly) {
break;
}
let possibleStartTime = getTimeBetweenTrueAnomalies(startingSituation.trueAnomaly, possibleStart, startingSituation.orbit, body);
if (possibleStartTime > maxStartTime) {
break;
}
starts.push([possibleStartTime, possibleStart]);
anomalyCounter += 1;
}
anomalyCounter = 0;
while (true) {
let possibleEnd = targetSituation.trueAnomaly + anomalyCounter * 2 * Math.PI / 100.0;
if (possibleEnd > maxEndTrueAnomaly) {
break;
}
let possibleEndTime = getTimeBetweenTrueAnomalies(targetSituation.trueAnomaly, possibleEnd, targetSituation.orbit, body);
possibleEnd += extraTrueAnomaly;
if (possibleEndTime > maxEndTime) {
break;
}
intercepts.push([possibleEndTime, possibleEnd]);
anomalyCounter += 1;
}
let pairs: [number, number, number, number][] = []; let pairs: [number, number, number, number][] = [];
starts.forEach(([startingTime, startingTrueAnomaly]) => { starts.forEach(([startingTime, startingTrueAnomaly]) => {
intercepts.forEach(([endingTime, endingTrueAnomaly]) => { intercepts.forEach(([endingTime, endingTrueAnomaly]) => {

61
src/gui/altitude.ts Normal file
View File

@ -0,0 +1,61 @@
import type { ChangingStorageValue } from "../storage";
import { createLabel, createNumberInput } from "./common";
export interface AltitudeData {
altitude: number,
headingInwards: boolean
}
export class AltitudeGui {
parentDiv: HTMLDivElement;
altitude: ChangingStorageValue<AltitudeData>;
sourceId: string;
constructor(altitude: ChangingStorageValue<AltitudeData>) {
this.parentDiv = document.createElement("div");
this.altitude = altitude;
let altitudeDiv = document.createElement("div");
altitudeDiv.classList.add("orbitalParameter");
let altitudeId = crypto.randomUUID();
let altitudeLabel = createLabel(altitudeId, "Altitude:")
let altitudeInput = createNumberInput(altitudeId, 0);
[altitudeLabel, altitudeInput].forEach(child => altitudeDiv.appendChild(child));
this.parentDiv.appendChild(altitudeDiv);
let headingDiv = document.createElement("div");
let headingId = crypto.randomUUID();
let headingCheckbox = document.createElement("input");
headingCheckbox.setAttribute("type", "checkbox");
headingCheckbox.setAttribute("id", headingId);
let headingLabel = createLabel(headingId, "Heading inwards");
[headingCheckbox, headingLabel].forEach(child => headingDiv.appendChild(child));
this.parentDiv.appendChild(headingDiv);
this.sourceId = crypto.randomUUID();
altitude.listenToValue((changeSource, value) => {
if (changeSource == this.sourceId) {
return;
}
altitudeInput.value = value.altitude.toString();
if (value.headingInwards) {
headingCheckbox.setAttribute("checked", "");
} else {
headingCheckbox.removeAttribute("checked")
}
});
const onChange = () => {
let altitude = parseFloat(altitudeInput.value);
let headingInwards = headingCheckbox.checked;
this.altitude.set({altitude: altitude, headingInwards: headingInwards}, this.sourceId);
};
[altitudeInput, headingCheckbox].forEach(input => input.addEventListener("change", onChange));
}
}

View File

@ -1,4 +1,5 @@
import { getOrbit, getOrbitFromEccentricity, type Orbit } from "../calculations/orbit-calculations"; import type { Body } from "../calculations/constants";
import { getOrbit, getOrbitalCoordinates, getOrbitalCoordinatesFromAltitude, getOrbitFromEccentricity, type Orbit, type OrbitalCoordinates } from "../calculations/orbit-calculations";
export interface OrbitalParameters { export interface OrbitalParameters {
periapsis: number; periapsis: number;
@ -8,6 +9,11 @@ export interface OrbitalParameters {
inclination: number; inclination: number;
longitudeOfAscendingNode: number; longitudeOfAscendingNode: number;
argumentOfPeriapsis: number; argumentOfPeriapsis: number;
positionChoice: "timeToPeriapsis" | "altitude";
timeToPeriapsis: number,
altitude: number,
headingInwards: boolean
} }
export const DefaultOrbitalParameters: OrbitalParameters = { export const DefaultOrbitalParameters: OrbitalParameters = {
@ -17,7 +23,11 @@ export const DefaultOrbitalParameters: OrbitalParameters = {
eccentricity: 0, eccentricity: 0,
inclination: 0, inclination: 0,
longitudeOfAscendingNode: 0, longitudeOfAscendingNode: 0,
argumentOfPeriapsis: 0 argumentOfPeriapsis: 0,
positionChoice: "timeToPeriapsis",
timeToPeriapsis: 0,
altitude: 100000,
headingInwards: true
} }
export function encodeOrbitalParameters(orbitalParameters: OrbitalParameters): string { export function encodeOrbitalParameters(orbitalParameters: OrbitalParameters): string {
@ -84,4 +94,22 @@ export function getOrbitFromParameters(orbitalParameters: OrbitalParameters, pla
orbitalParameters.argumentOfPeriapsis orbitalParameters.argumentOfPeriapsis
); );
} }
}
export function getCoordinatesFromParameters(orbitalParameters: OrbitalParameters, body: Body): OrbitalCoordinates {
let orbit = getOrbitFromParameters(orbitalParameters, body.radius);
if (orbitalParameters.positionChoice == "altitude") {
return getOrbitalCoordinatesFromAltitude(orbitalParameters.altitude + body.radius, orbitalParameters.headingInwards, orbit, body);
} else {
return getOrbitalCoordinates(orbitalParameters.timeToPeriapsis, orbit, body);
}
}
export function createRadioButton(choiceId: string, valueId: string): HTMLInputElement {
let button = document.createElement("input");
button.setAttribute("type", "radio");
button.setAttribute("name", choiceId);
button.setAttribute("id", valueId);
button.setAttribute("value", valueId);
return button;
} }

View File

@ -1,181 +1,63 @@
import { getOrbitalCoordinates } from "../calculations/orbit-calculations"; import { createLabel, createNumberInput, getCoordinatesFromParameters, type OrbitalParameters } from "./common";
import type { Wrapper } from "../storage";
import { createDisabledInput, createLabel, createNumberInput, getOrbitFromParameters, type OrbitalParameters } from "./common";
import { OrbitalParametersGui } from "./orbit"; import { OrbitalParametersGui } from "./orbit";
import { type Body } from "../calculations/constants"; import { type Body } from "../calculations/constants";
import type { FindBestInterceptMessage, FindBestTransferResponse } from "./worker"; import type { FindBestInterceptMessage, ProgressMessage } from "./worker";
import { TimeGui } from "./time"; import type { ChangingStorageValue } from "../storage";
import { ManoeuvresGui } from "./manoeuvres";
export class InterceptTargetGui { export class InterceptTargetGui {
listeners: ((newTargetOrbitalParameters: OrbitalParameters, newTargetTimeToPeriapsis: number, newAdditionalTrueAnomaly: number) => void)[]; currentTime: ChangingStorageValue<number>;
body: ChangingStorageValue<Body>;
startingOrbitalParameters: ChangingStorageValue<OrbitalParameters>;
targetOrbitalParameters: ChangingStorageValue<OrbitalParameters>;
additionalTrueAnomaly: ChangingStorageValue<number>;
startingParameters: Wrapper<OrbitalParameters>;
goalParameters: Wrapper<OrbitalParameters>;
currentTime: Wrapper<number>;
timeToPeriapsis: Wrapper<number>;
body: Wrapper<Body>;
targetTimeToPeriapsis: number;
additionalTrueAnomaly: number;
orbitGui: OrbitalParametersGui;
timeGui: TimeGui;
parentDiv: HTMLDivElement; parentDiv: HTMLDivElement;
orbitGui: OrbitalParametersGui;
manoeuvresGui: ManoeuvresGui;
progressParagraph: HTMLParagraphElement; sourceId: string;
firstManoeuvreTime: HTMLInputElement;
firstManoeuvrePrograde: HTMLInputElement;
firstManoeuvreNormal: HTMLInputElement;
firstManoeuvreRadial: HTMLInputElement;
firstManoeuvreTotal: HTMLInputElement;
secondManoeuvreTime: HTMLInputElement;
secondManoeuvrePrograde: HTMLInputElement;
secondManoeuvreNormal: HTMLInputElement;
secondManoeuvreRadial: HTMLInputElement;
secondManoeuvreTotal: HTMLInputElement;
worker: Worker | null; worker: Worker | null;
constructor(startingParameters: Wrapper<OrbitalParameters>, goalParameters: Wrapper<OrbitalParameters>, currentTime: Wrapper<number>, timeToPeriapsis: Wrapper<number>, body: Wrapper<Body>, defaultTargetTimeToPeriapsis?: number, defaultAdditionalTrueAnomaly?: number) { constructor(currentTime: ChangingStorageValue<number>, body: ChangingStorageValue<Body>, startingOrbitalParameters: ChangingStorageValue<OrbitalParameters>, targetOrbitalParameters: ChangingStorageValue<OrbitalParameters>, additionalTrueAnomaly: ChangingStorageValue<number>) {
this.listeners = [];
this.startingParameters = startingParameters;
this.goalParameters = goalParameters;
this.currentTime = currentTime; this.currentTime = currentTime;
this.timeToPeriapsis = timeToPeriapsis;
this.body = body; this.body = body;
this.startingOrbitalParameters = startingOrbitalParameters;
this.targetOrbitalParameters = targetOrbitalParameters;
this.additionalTrueAnomaly = additionalTrueAnomaly;
this.orbitGui = new OrbitalParametersGui(targetOrbitalParameters, "orbitWithPosition");
this.manoeuvresGui = new ManoeuvresGui(true);
this.worker = null;
this.parentDiv = document.createElement("div"); this.parentDiv = document.createElement("div");
let parametersHeader = document.createElement("h3"); let parametersHeader = document.createElement("h3");
parametersHeader.appendChild(document.createTextNode("Target orbit:")); parametersHeader.appendChild(document.createTextNode("Target orbit:"));
this.parentDiv.appendChild(parametersHeader); this.parentDiv.appendChild(parametersHeader);
let orbitalParameterContainerCreator = () => { this.parentDiv.appendChild(this.orbitGui.parentDiv);
let orbitalParameterContainer = document.createElement("div");
orbitalParameterContainer.classList.add("orbitalParameter");
return orbitalParameterContainer;
}
this.orbitGui = new OrbitalParametersGui(this.parentDiv, goalParameters.value, orbitalParameterContainerCreator); let additionalTrueAnomalyContainer = document.createElement("div");
additionalTrueAnomalyContainer.classList.add("orbitalParameter");
let targetTimeToPeriapsisHeader = document.createElement("h3"); this.parentDiv.appendChild(additionalTrueAnomalyContainer);
targetTimeToPeriapsisHeader.appendChild(document.createTextNode("Target time to periapsis:"));
this.parentDiv.appendChild(targetTimeToPeriapsisHeader);
if (!defaultTargetTimeToPeriapsis) {
defaultTargetTimeToPeriapsis = 0;
}
this.targetTimeToPeriapsis = defaultTargetTimeToPeriapsis;
this.timeGui = new TimeGui(this.parentDiv, false, defaultTargetTimeToPeriapsis);
this.parentDiv.appendChild(document.createElement("br"));
let additionalTrueAnomalyId = crypto.randomUUID(); let additionalTrueAnomalyId = crypto.randomUUID();
let additionalTrueAnomalyLabel = createLabel(additionalTrueAnomalyId, "Additional true anomaly (0 for intercept):"); let additionalTrueanomalyLabel = createLabel(additionalTrueAnomalyId, "Additional true anomaly:");
let additionalTrueAnomalyInput = createNumberInput(additionalTrueAnomalyId, -360, 360); let additionalTrueAnomalyInput = createNumberInput(additionalTrueAnomalyId, -360, 360);
[additionalTrueanomalyLabel, additionalTrueAnomalyInput].forEach(child => additionalTrueAnomalyContainer.appendChild(child));
if (!defaultAdditionalTrueAnomaly) {
this.additionalTrueAnomaly = 0;
} else {
this.additionalTrueAnomaly = defaultAdditionalTrueAnomaly;
}
additionalTrueAnomalyInput.setAttribute("value", (this.additionalTrueAnomaly * 180 / Math.PI).toString());
this.parentDiv.appendChild(additionalTrueAnomalyLabel);
this.parentDiv.appendChild(additionalTrueAnomalyInput);
this.parentDiv.appendChild(document.createElement("br"));
this.parentDiv.appendChild(document.createElement("br"));
let searchButton = document.createElement("button"); let searchButton = document.createElement("button");
searchButton.appendChild(document.createTextNode("Search for cheapest intercept")); searchButton.appendChild(document.createTextNode("Search for cheapest intercept"));
this.parentDiv.appendChild(searchButton); this.parentDiv.appendChild(searchButton);
let searchHeader = document.createElement("h3"); let bestInterceptHeader = document.createElement("h3");
searchHeader.appendChild(document.createTextNode("Manoeuvre search")); bestInterceptHeader.appendChild(document.createTextNode("Best intercept:"));
this.parentDiv.appendChild(searchHeader); this.parentDiv.appendChild(bestInterceptHeader);
this.progressParagraph = document.createElement("p"); this.parentDiv.appendChild(this.manoeuvresGui.parentDiv);
this.parentDiv.appendChild(this.progressParagraph);
let manoeuvresContainer = document.createElement("div");
manoeuvresContainer.classList.add("flexContainer");
this.parentDiv.appendChild(manoeuvresContainer)
let manoeuvreOneContainer = document.createElement("div");
let manoeuvreTwoContainer = document.createElement("div");
manoeuvresContainer.appendChild(manoeuvreOneContainer);
manoeuvresContainer.appendChild(manoeuvreTwoContainer);
let manoeuvreOneHeader = document.createElement("h4");
manoeuvreOneHeader.appendChild(document.createTextNode("First manoeuvre:"));
manoeuvreOneContainer.appendChild(manoeuvreOneHeader);
let manoeuvreTwoHeader = document.createElement("h4");
manoeuvreTwoHeader.appendChild(document.createTextNode("Second manoeuvre:"));
manoeuvreTwoContainer.appendChild(manoeuvreTwoHeader);
const addTo = (container: HTMLElement, children: HTMLElement[]) => {
children.forEach(child => {
container.appendChild(child);
})
container.appendChild(document.createElement("br"));
};
let firstTimeId = crypto.randomUUID();
let firstTimeLabel = createLabel(firstTimeId, "Time:");
this.firstManoeuvreTime = createDisabledInput(firstTimeId);
addTo(manoeuvreOneContainer, [firstTimeLabel, this.firstManoeuvreTime]);
let firstProgradeId = crypto.randomUUID();
let firstProgradeLabel = createLabel(firstProgradeId, "Prograde delta-v:");
this.firstManoeuvrePrograde = createDisabledInput(firstProgradeId);
addTo(manoeuvreOneContainer, [firstProgradeLabel, this.firstManoeuvrePrograde]);
let firstNormalId = crypto.randomUUID();
let firstNormalLabel = createLabel(firstNormalId, "Normal delta-v:");
this.firstManoeuvreNormal = createDisabledInput(firstNormalId);
addTo(manoeuvreOneContainer, [firstNormalLabel, this.firstManoeuvreNormal]);
let firstRadialId = crypto.randomUUID();
let firstRadialLabel = createLabel(firstRadialId, "Radial delta-v:");
this.firstManoeuvreRadial = createDisabledInput(firstRadialId);
addTo(manoeuvreOneContainer, [firstRadialLabel, this.firstManoeuvreRadial]);
let firstTotalId = crypto.randomUUID();
let firstTotalLabel = createLabel(firstTotalId, "Total delta-v:");
this.firstManoeuvreTotal = createDisabledInput(firstTotalId);
addTo(manoeuvreOneContainer, [firstTotalLabel, this.firstManoeuvreTotal]);
let secondTimeId = crypto.randomUUID();
let secondTimeLabel = createLabel(secondTimeId, "Time:");
this.secondManoeuvreTime = createDisabledInput(secondTimeId);
addTo(manoeuvreTwoContainer, [secondTimeLabel, this.secondManoeuvreTime]);
let secondProgradeId = crypto.randomUUID();
let secondProgradeLabel = createLabel(secondProgradeId, "Prograde delta-v:");
this.secondManoeuvrePrograde = createDisabledInput(secondProgradeId);
addTo(manoeuvreTwoContainer, [secondProgradeLabel, this.secondManoeuvrePrograde]);
let secondNormalId = crypto.randomUUID();
let secondNormalLabel = createLabel(secondNormalId, "Normal delta-v:");
this.secondManoeuvreNormal = createDisabledInput(secondNormalId);
addTo(manoeuvreTwoContainer, [secondNormalLabel, this.secondManoeuvreNormal]);
let secondRadialId = crypto.randomUUID();
let secondRadialLabel = createLabel(secondRadialId, "Radial delta-v:");
this.secondManoeuvreRadial = createDisabledInput(secondRadialId);
addTo(manoeuvreTwoContainer, [secondRadialLabel, this.secondManoeuvreRadial]);
let secondTotalId = crypto.randomUUID();
let secondTotalLabel = createLabel(secondTotalId, "Total delta-v:");
this.secondManoeuvreTotal = createDisabledInput(secondTotalId);
addTo(manoeuvreTwoContainer, [secondTotalLabel, this.secondManoeuvreTotal]);
this.worker = null;
searchButton.addEventListener("click", () => { searchButton.addEventListener("click", () => {
if (this.worker !== null) { if (this.worker !== null) {
@ -185,86 +67,58 @@ export class InterceptTargetGui {
} else { } else {
searchButton.innerHTML = "Cancel search"; searchButton.innerHTML = "Cancel search";
let startingOrbit = getOrbitFromParameters(startingParameters.value, this.body.value.radius); let currentTime = this.currentTime.getCurrentValue();
let endingOrbit = getOrbitFromParameters(goalParameters.value, this.body.value.radius); let body = this.body.getCurrentValue();
let currentCoordinates = getOrbitalCoordinates(timeToPeriapsis.value, startingOrbit, this.body.value); let startingOrbitalParameters = this.startingOrbitalParameters.getCurrentValue();
let targetCoordinates = getOrbitalCoordinates(this.targetTimeToPeriapsis, endingOrbit, this.body.value); let targetOrbitalParameters = this.targetOrbitalParameters.getCurrentValue();
let startingCoordinates = getCoordinatesFromParameters(startingOrbitalParameters, body);
let targetCoordinates = getCoordinatesFromParameters(targetOrbitalParameters, body);
let additionalTrueAnomaly = this.additionalTrueAnomaly.getCurrentValue();
this.worker = new Worker(new URL('./worker.ts', import.meta.url), {type: 'module'}); this.worker = new Worker(new URL('./worker.ts', import.meta.url), {type: 'module'});
this.worker.addEventListener("message", event => { this.worker.addEventListener("message", event => {
let transferResponse = event.data as FindBestTransferResponse; let transferResponse = event.data as ProgressMessage;
if (transferResponse) { if (transferResponse) {
if (transferResponse.finished) { if (transferResponse.finished) {
searchButton.innerHTML = "Search for cheapest intercept"; searchButton.innerHTML = "Search for cheapest intercept";
this.worker = null; this.worker = null;
} }
this.progressParagraph.innerHTML = ""; if (transferResponse.bestTransfer) {
this.progressParagraph.appendChild(document.createTextNode(`Search is ${transferResponse.percentDone.toFixed(2)}% finished`)); transferResponse.bestTransfer.firstManoeuvre.time += currentTime;
if (transferResponse.bestDeltaV !== null && transferResponse.bestTransfer != null) { transferResponse.bestTransfer.secondManoeuvre.time += currentTime;
this.progressParagraph.appendChild(document.createElement("br"));
this.progressParagraph.appendChild(document.createTextNode(`Best transfer costs ${transferResponse.bestDeltaV.toFixed(3)} m/s`));
let transfer = transferResponse.bestTransfer;
transfer.firstManoeuvre.time += currentTime.value;
transfer.secondManoeuvre.time += currentTime.value;
this.firstManoeuvreTime.value = transfer.firstManoeuvre.time.toFixed(0);
this.firstManoeuvrePrograde.value = transfer.firstManoeuvre.progradeDeltaV.toFixed(2);
this.firstManoeuvreNormal.value = transfer.firstManoeuvre.normalDeltaV.toFixed(2);
this.firstManoeuvreRadial.value = transfer.firstManoeuvre.radialDeltaV.toFixed(2);
this.firstManoeuvreTotal.value = transfer.firstManoeuvre.totalDeltaV.toFixed(2);
this.secondManoeuvreTime.value = transfer.secondManoeuvre.time.toFixed(0);
this.secondManoeuvrePrograde.value = transfer.secondManoeuvre.progradeDeltaV.toFixed(2);
this.secondManoeuvreNormal.value = transfer.secondManoeuvre.normalDeltaV.toFixed(2);
this.secondManoeuvreRadial.value = transfer.secondManoeuvre.radialDeltaV.toFixed(2);
this.secondManoeuvreTotal.value = transfer.secondManoeuvre.totalDeltaV.toFixed(2);
} }
this.manoeuvresGui.displayProgress(transferResponse);
} }
}); });
let workerMessage: FindBestInterceptMessage = { let workerMessage: FindBestInterceptMessage = {
type: "FindBestIntercept", type: "FindBestIntercept",
startingSituation: currentCoordinates, startingSituation: startingCoordinates,
targetSituation: targetCoordinates, targetSituation: targetCoordinates,
body: body.value, body: body,
additionalTrueAnomaly: this.additionalTrueAnomaly additionalTrueAnomaly: additionalTrueAnomaly
}; };
this.worker.postMessage(workerMessage); this.worker.postMessage(workerMessage);
} }
}); });
// Add some listeners this.sourceId = crypto.randomUUID();
this.orbitGui.addListener((newValue) => { this.additionalTrueAnomaly.listenToValue((source, value) => {
this.listeners.forEach(listener => { if (source == this.sourceId) {
listener(newValue, this.targetTimeToPeriapsis, this.additionalTrueAnomaly); return;
}) }
});
this.timeGui.addListener((newDate: number) => { additionalTrueAnomalyInput.value = (value * 180 / Math.PI).toString();
this.targetTimeToPeriapsis = newDate;
this.listeners.forEach(listener => {
listener(goalParameters.value, newDate, this.additionalTrueAnomaly);
})
}); });
additionalTrueAnomalyInput.addEventListener("change", () => { additionalTrueAnomalyInput.addEventListener("change", () => {
this.additionalTrueAnomaly = parseFloat(additionalTrueAnomalyInput.value) * Math.PI / 180.0; let additionalTrueAnomaly = parseFloat(additionalTrueAnomalyInput.value) * Math.PI / 180.0;
this.listeners.forEach(listener => { this.additionalTrueAnomaly.set(additionalTrueAnomaly, this.sourceId);
listener(goalParameters.value, this.targetTimeToPeriapsis, this.additionalTrueAnomaly);
})
}); });
} }
addToParentElement(parentElement: HTMLElement) {
this.orbitGui.setValues(this.goalParameters.value);
parentElement.appendChild(this.parentDiv);
}
addListener(listener: (newValue: OrbitalParameters, newTargetTimeToPeriapsis: number, newAdditionalTrueAnomaly: number) => void) {
this.listeners.push(listener);
}
} }

117
src/gui/manoeuvres.ts Normal file
View File

@ -0,0 +1,117 @@
import { createDisabledInput, createLabel } from "./common";
import type { ProgressMessage } from "./worker";
export class ManoeuvresGui {
parentDiv: HTMLElement;
displayProgress: (progressMessage: ProgressMessage) => void;
constructor(displayProgress?: boolean) {
if (displayProgress === undefined) {
displayProgress = true;
}
this.parentDiv = document.createElement("div");
let explanationParagraph = document.createElement("p");
this.parentDiv.appendChild(explanationParagraph);
let manoeuvresContainer = document.createElement("div");
manoeuvresContainer.classList.add("flexContainer");
this.parentDiv.appendChild(manoeuvresContainer);
let manoeuvreOneContainer = document.createElement("div");
let manoeuvreTwoContainer = document.createElement("div");
manoeuvreOneContainer.classList.add("orbitalParameter");
manoeuvreTwoContainer.classList.add("orbitalParameter");
manoeuvresContainer.appendChild(manoeuvreOneContainer);
manoeuvresContainer.appendChild(manoeuvreTwoContainer);
let manoeuvreOneHeader = document.createElement("h4");
manoeuvreOneHeader.appendChild(document.createTextNode("First manoeuvre:"));
manoeuvreOneContainer.appendChild(manoeuvreOneHeader);
const addTo = (container: HTMLElement, children: HTMLElement[]) => {
children.forEach(child => {
container.appendChild(child);
})
container.appendChild(document.createElement("br"));
};
let firstTimeId = crypto.randomUUID();
let firstTimeLabel = createLabel(firstTimeId, "Time:");
let firstManoeuvreTime = createDisabledInput(firstTimeId);
addTo(manoeuvreOneContainer, [firstTimeLabel, firstManoeuvreTime]);
let firstProgradeId = crypto.randomUUID();
let firstProgradeLabel = createLabel(firstProgradeId, "Prograde delta-v:");
let firstManoeuvrePrograde = createDisabledInput(firstProgradeId);
addTo(manoeuvreOneContainer, [firstProgradeLabel, firstManoeuvrePrograde]);
let firstNormalId = crypto.randomUUID();
let firstNormalLabel = createLabel(firstNormalId, "Normal delta-v:");
let firstManoeuvreNormal = createDisabledInput(firstNormalId);
addTo(manoeuvreOneContainer, [firstNormalLabel, firstManoeuvreNormal]);
let firstRadialId = crypto.randomUUID();
let firstRadialLabel = createLabel(firstRadialId, "Radial delta-v:");
let firstManoeuvreRadial = createDisabledInput(firstRadialId);
addTo(manoeuvreOneContainer, [firstRadialLabel, firstManoeuvreRadial]);
let firstTotalId = crypto.randomUUID();
let firstTotalLabel = createLabel(firstTotalId, "Total delta-v:");
let firstManoeuvreTotal = createDisabledInput(firstTotalId);
addTo(manoeuvreOneContainer, [firstTotalLabel, firstManoeuvreTotal]);
let manoeuvreTwoHeader = document.createElement("h4");
manoeuvreTwoHeader.appendChild(document.createTextNode("Second manoeuvre"));
manoeuvreTwoContainer.appendChild(manoeuvreTwoHeader);
let secondTimeId = crypto.randomUUID();
let secondTimeLabel = createLabel(secondTimeId, "Time:");
let secondManoeuvreTime = createDisabledInput(secondTimeId);
addTo(manoeuvreTwoContainer, [secondTimeLabel, secondManoeuvreTime]);
let secondProgradeId = crypto.randomUUID();
let secondProgradeLabel = createLabel(secondProgradeId, "Prograde delta-v:");
let secondManoeuvrePrograde = createDisabledInput(secondProgradeId);
addTo(manoeuvreTwoContainer, [secondProgradeLabel, secondManoeuvrePrograde]);
let secondNormalId = crypto.randomUUID();
let secondNormalLabel = createLabel(secondNormalId, "Normal delta-v:");
let secondManoeuvreNormal = createDisabledInput(secondNormalId);
addTo(manoeuvreTwoContainer, [secondNormalLabel, secondManoeuvreNormal]);
let secondRadialId = crypto.randomUUID();
let secondRadialLabel = createLabel(secondRadialId, "Radial delta-v:");
let secondManoeuvreRadial = createDisabledInput(secondRadialId);
addTo(manoeuvreTwoContainer, [secondRadialLabel, secondManoeuvreRadial]);
let secondTotalId = crypto.randomUUID();
let secondTotalLabel = createLabel(secondTotalId, "Total delta-v:");
let secondManoeuvreTotal = createDisabledInput(secondTotalId);
addTo(manoeuvreTwoContainer, [secondTotalLabel, secondManoeuvreTotal]);
this.displayProgress = (progressMessage) => {
explanationParagraph.innerHTML = "";
if (displayProgress) {
explanationParagraph.appendChild(document.createTextNode(`The search is ${progressMessage.percentDone.toFixed(2)}% complete.`));
if (progressMessage.bestDeltaV) {
explanationParagraph.appendChild(document.createElement("br"));
explanationParagraph.appendChild(document.createTextNode(`The best transfer costs ${progressMessage.bestDeltaV.toFixed(3)} m/s`));
}
}
firstManoeuvreTime.value = progressMessage.bestTransfer?.firstManoeuvre.time.toFixed(0) ?? "";
firstManoeuvrePrograde.value = progressMessage.bestTransfer?.firstManoeuvre.progradeDeltaV.toFixed(3) ?? "";
firstManoeuvreNormal.value = progressMessage.bestTransfer?.firstManoeuvre.normalDeltaV.toFixed(3) ?? "";
firstManoeuvreRadial.value = progressMessage.bestTransfer?.firstManoeuvre.radialDeltaV.toFixed(3) ?? "";
firstManoeuvreTotal.value = progressMessage.bestTransfer?.firstManoeuvre.totalDeltaV.toFixed(3) ?? "";
secondManoeuvreTime.value = progressMessage.bestTransfer?.secondManoeuvre.time.toFixed(0) ?? "";
secondManoeuvrePrograde.value = progressMessage.bestTransfer?.secondManoeuvre.progradeDeltaV.toFixed(3) ?? "";
secondManoeuvreNormal.value = progressMessage.bestTransfer?.secondManoeuvre.normalDeltaV.toFixed(3) ?? "";
secondManoeuvreRadial.value = progressMessage.bestTransfer?.secondManoeuvre.radialDeltaV.toFixed(3) ?? "";
secondManoeuvreTotal.value = progressMessage.bestTransfer?.secondManoeuvre.totalDeltaV.toFixed(3) ?? "";
};
}
}

View File

@ -1,174 +1,195 @@
import { createLabel, createNumberInput, type OrbitalParameters, DefaultOrbitalParameters } from "./common"; import { ChangingStorageValue } from "../storage";
import { AltitudeGui, type AltitudeData } from "./altitude";
import { createLabel, createNumberInput, createRadioButton, type OrbitalParameters } from "./common";
import { TimeGui } from "./time";
type GuiType = "plane" | "orbit" | "orbitWithPosition";
export class OrbitalParametersGui { export class OrbitalParametersGui {
listeners: ((newValue: OrbitalParameters) => void)[]; parentDiv: HTMLDivElement;
orbitalParameters: OrbitalParameters; orbitalParameters: ChangingStorageValue<OrbitalParameters>;
timeToPeriapsis: ChangingStorageValue<number>;
timeGui: TimeGui;
altitudeData: ChangingStorageValue<AltitudeData>;
altitudeGui: AltitudeGui;
periapsisInput: HTMLInputElement; sourceId: string;
apoapsisChoiceButton: HTMLInputElement;
eccentricityChoiceButton: HTMLInputElement;
apoapsisInput: HTMLInputElement;
eccentricityInput: HTMLInputElement;
inclinationInput: HTMLInputElement;
lanInput: HTMLInputElement;
aopInput: HTMLInputElement;
constructor(htmlContainer: HTMLElement, startingValue?: OrbitalParameters, containerCreator?: () => HTMLElement) { constructor(orbitalParameters: ChangingStorageValue<OrbitalParameters>, guiType?: GuiType) {
this.listeners = []; this.orbitalParameters = orbitalParameters;
this.timeToPeriapsis = new ChangingStorageValue(orbitalParameters.getCurrentValue().timeToPeriapsis);
this.timeGui = new TimeGui(this.timeToPeriapsis, false);
if (startingValue) { this.altitudeData = new ChangingStorageValue({altitude: orbitalParameters.getCurrentValue().altitude, headingInwards: orbitalParameters.getCurrentValue().headingInwards});
this.orbitalParameters = structuredClone(startingValue); this.altitudeGui = new AltitudeGui(this.altitudeData);
} else {
this.orbitalParameters = structuredClone(DefaultOrbitalParameters); this.parentDiv = document.createElement("div");
if (!guiType) {
guiType = "orbitWithPosition";
} }
const addToParent = (toWrap: HTMLElement | HTMLElement[]) => { const addToParent = (toWrap: HTMLElement | HTMLElement[], parent: HTMLElement) => {
var children = []; var children = [];
if (toWrap instanceof HTMLElement) { if (toWrap instanceof HTMLElement) {
children = [toWrap]; children = [toWrap];
} else { } else {
children = toWrap; children = toWrap;
} }
let containerDiv = document.createElement("div");
let containerToAppendTo = htmlContainer; containerDiv.classList.add("orbitalParameter");
if (containerCreator !== undefined) { children.forEach(child => containerDiv.appendChild(child));
let childContainer = containerCreator(); parent.appendChild(containerDiv);
containerToAppendTo.appendChild(childContainer);
containerToAppendTo = childContainer;
}
children.forEach(child => containerToAppendTo.appendChild(child));
}; };
let periapsisId = crypto.randomUUID(); let periapsisId = crypto.randomUUID();
this.periapsisInput = createNumberInput(periapsisId, 0); let periapsisInput = createNumberInput(periapsisId, 0);
if (guiType == "orbit" || guiType == "orbitWithPosition") {
this.periapsisInput.addEventListener("change", () => { addToParent([createLabel(periapsisId, "Periapsis:"), periapsisInput], this.parentDiv);
let newValue = parseFloat(this.periapsisInput.value); }
this.orbitalParameters.periapsis = newValue;
this.informListeners(this.orbitalParameters);
});
addToParent([createLabel(periapsisId, "Periapsis:"), this.periapsisInput]);
let choiceId = crypto.randomUUID(); let choiceId = crypto.randomUUID();
let apoapsisChoiceId = crypto.randomUUID(); let apoapsisChoiceId = crypto.randomUUID();
let eccentricityChoiceId = crypto.randomUUID(); let eccentricityChoiceId = crypto.randomUUID();
this.apoapsisChoiceButton = document.createElement("input"); let apoapsisChoiceButton = createRadioButton(choiceId, apoapsisChoiceId);
this.apoapsisChoiceButton.setAttribute("type", "radio"); let eccentricityChoiceButton = createRadioButton(choiceId, eccentricityChoiceId);
this.apoapsisChoiceButton.setAttribute("name", choiceId);
this.apoapsisChoiceButton.setAttribute("id", apoapsisChoiceId); if (guiType == "orbit" || guiType == "orbitWithPosition") {
this.apoapsisChoiceButton.setAttribute("value", "apoapsis"); addToParent([apoapsisChoiceButton, createLabel(apoapsisChoiceId, "Use apoapsis"), eccentricityChoiceButton, createLabel(eccentricityChoiceId, "Use eccentricity")], this.parentDiv);
if (this.orbitalParameters.eccentricChoice === "apoapsis") {
this.apoapsisChoiceButton.setAttribute("checked", "");
} }
this.eccentricityChoiceButton = document.createElement("input");
this.eccentricityChoiceButton.setAttribute("type", "radio");
this.eccentricityChoiceButton.setAttribute("name", choiceId);
this.eccentricityChoiceButton.setAttribute("id", eccentricityChoiceId);
this.eccentricityChoiceButton.setAttribute("value", "eccentricity");
if (this.orbitalParameters.eccentricChoice == "eccentricity") {
this.eccentricityChoiceButton.setAttribute("checked", "");
};
addToParent([this.apoapsisChoiceButton, createLabel(apoapsisChoiceId, "Use apoapsis"), this.eccentricityChoiceButton, createLabel(eccentricityChoiceId, "Use eccentricity")]);
let apoapsisId = crypto.randomUUID(); let apoapsisId = crypto.randomUUID();
this.apoapsisInput = createNumberInput(apoapsisId, 0); let apoapsisInput = createNumberInput(apoapsisId, 0);
if (this.orbitalParameters.eccentricChoice !== "apoapsis") { if (guiType == "orbit" || guiType == "orbitWithPosition") {
this.apoapsisInput.setAttribute("disabled", "true"); addToParent([createLabel(apoapsisId, "Apoapsis:"), apoapsisInput], this.parentDiv);
} }
this.apoapsisInput.addEventListener("change", () => {
this.orbitalParameters.apoapsis = parseFloat(this.apoapsisInput.value);
this.informListeners(this.orbitalParameters);
})
addToParent([createLabel(apoapsisId, "Apoapsis:"), this.apoapsisInput]);
let eccentricityId = crypto.randomUUID(); let eccentricityId = crypto.randomUUID();
this.eccentricityInput = createNumberInput(eccentricityId, 0); let eccentricityInput = createNumberInput(eccentricityId, 0);
if (this.orbitalParameters.eccentricChoice !== "eccentricity") { if (guiType == "orbit" || guiType == "orbitWithPosition") {
this.eccentricityInput.setAttribute("disabled", "true"); addToParent([createLabel(eccentricityId, "Eccentricity:"), eccentricityInput], this.parentDiv);
} }
this.eccentricityInput.addEventListener("change", () => {
this.orbitalParameters.eccentricity = parseFloat(this.eccentricityInput.value);
this.informListeners(this.orbitalParameters);
});
addToParent([createLabel(eccentricityId, "Eccentricity:"), this.eccentricityInput]);
this.apoapsisChoiceButton.addEventListener("change", () => {
this.orbitalParameters.eccentricChoice = "apoapsis";
this.informListeners(this.orbitalParameters);
this.apoapsisInput.removeAttribute("disabled");
this.eccentricityInput.setAttribute("disabled", "true");
})
this.eccentricityChoiceButton.addEventListener("change", () => {
this.orbitalParameters.eccentricChoice = "eccentricity";
this.informListeners(this.orbitalParameters);
this.apoapsisInput.setAttribute("disabled", "true");
this.eccentricityInput.removeAttribute("disabled");
});
let inclinationId = crypto.randomUUID(); let inclinationId = crypto.randomUUID();
this.inclinationInput = createNumberInput(inclinationId, -360, 360); let inclinationInput = createNumberInput(inclinationId, -360, 360);
this.inclinationInput.addEventListener("change", () => { addToParent([createLabel(inclinationId, "Inclination:"), inclinationInput], this.parentDiv);
this.orbitalParameters.inclination = parseFloat(this.inclinationInput.value) * Math.PI / 180.0;
this.informListeners(this.orbitalParameters);
});
addToParent([createLabel(inclinationId, "Inclination:"), this.inclinationInput]);
let lanId = crypto.randomUUID(); let lanId = crypto.randomUUID();
this.lanInput = createNumberInput(lanId, -360, 360); let lanInput = createNumberInput(lanId, -360, 360);
this.lanInput.addEventListener("change", () => { addToParent([createLabel(lanId, "Longitude of ascending node:"), lanInput], this.parentDiv);
this.orbitalParameters.longitudeOfAscendingNode = parseFloat(this.lanInput.value) * Math.PI / 180.0;
this.informListeners(this.orbitalParameters);
});
addToParent([createLabel(lanId, "Longitude of ascending node:"), this.lanInput]);
let aopId = crypto.randomUUID(); let aopId = crypto.randomUUID();
this.aopInput = createNumberInput(aopId, -360, 360); let aopInput = createNumberInput(aopId, -360, 360);
this.aopInput.addEventListener("change", () => { if (guiType == "orbit" || guiType == "orbitWithPosition") {
this.orbitalParameters.argumentOfPeriapsis = parseFloat(this.aopInput.value) * Math.PI / 180.0; addToParent([createLabel(aopId, "Argument of periapsis:"), aopInput], this.parentDiv);
this.informListeners(this.orbitalParameters);
});
addToParent([createLabel(aopId, "Argument of periapsis:"), this.aopInput]);
this.populateValues();
}
addListener(listener: (newValue: OrbitalParameters) => void) {
this.listeners.push(listener);
}
informListeners(newValue: OrbitalParameters) {
this.listeners.forEach(listener => listener(newValue));
}
setValues(values: OrbitalParameters) {
this.orbitalParameters = values;
this.populateValues();
}
populateValues() {
this.periapsisInput.setAttribute("value", this.orbitalParameters.periapsis.toString());
this.apoapsisInput.setAttribute("value", this.orbitalParameters.apoapsis.toString());
this.eccentricityInput.setAttribute("value", this.orbitalParameters.eccentricity.toString());
this.inclinationInput.setAttribute("value", (this.orbitalParameters.inclination * 180 / Math.PI).toString());
this.lanInput.setAttribute("value", (this.orbitalParameters.longitudeOfAscendingNode * 180 / Math.PI).toString());
this.aopInput.setAttribute("value", (this.orbitalParameters.argumentOfPeriapsis * 180 / Math.PI).toString());
if (this.orbitalParameters.eccentricChoice == "apoapsis") {
this.apoapsisInput.removeAttribute("disabled");
this.eccentricityInput.setAttribute("disabled", "");
this.eccentricityChoiceButton.removeAttribute("checked");
this.apoapsisChoiceButton.setAttribute("checked", "");
} else if (this.orbitalParameters.eccentricChoice == "eccentricity") {
this.apoapsisInput.setAttribute("disabled", "");
this.eccentricityInput.removeAttribute("disabled");
this.apoapsisChoiceButton.removeAttribute("checked");
this.eccentricityChoiceButton.setAttribute("checked", "");
} }
let positionChoiceId = crypto.randomUUID();
let timeToPeriapsisChoiceId = crypto.randomUUID();
let altitudeChoiceId = crypto.randomUUID();
let timeToPeriapsisButton = createRadioButton(positionChoiceId, timeToPeriapsisChoiceId);
let altitudeButton = createRadioButton(positionChoiceId, altitudeChoiceId);
if (guiType == "orbitWithPosition") {
addToParent([timeToPeriapsisButton, createLabel(timeToPeriapsisChoiceId, "Use time to periapsis"), altitudeButton, createLabel(altitudeChoiceId, "Use altitude")], this.parentDiv);
}
let positionDiv = document.createElement("div");
if (guiType == "orbitWithPosition") {
this.parentDiv.appendChild(positionDiv);
}
this.sourceId = crypto.randomUUID();
orbitalParameters.listenToValue((changeSource, value) => {
if (value.eccentricChoice == "apoapsis") {
apoapsisChoiceButton.setAttribute("checked", "");
apoapsisInput.removeAttribute("disabled");
eccentricityInput.setAttribute("disabled", "");
} else if (value.eccentricChoice == "eccentricity") {
eccentricityChoiceButton.setAttribute("checked", "")
eccentricityInput.removeAttribute("disabled");
apoapsisInput.setAttribute("disabled", "");
};
positionDiv.innerHTML = "";
if (value.positionChoice == "timeToPeriapsis") {
timeToPeriapsisButton.setAttribute("checked", "");
positionDiv.appendChild(this.timeGui.parentDiv);
} else {
altitudeButton.setAttribute("checked", "");
positionDiv.appendChild(this.altitudeGui.parentDiv);
}
if (changeSource == this.sourceId) {
return;
}
periapsisInput.value = value.periapsis.toString();
apoapsisInput.value = value.apoapsis.toString();
eccentricityInput.value = value.eccentricity.toString();
inclinationInput.value = (value.inclination * 180 / Math.PI).toString();
lanInput.value = (value.longitudeOfAscendingNode * 180 / Math.PI).toString();
aopInput.value = (value.argumentOfPeriapsis * 180 / Math.PI).toString();
this.timeToPeriapsis.set(value.timeToPeriapsis, this.sourceId);
this.altitudeData.set({
altitude: value.altitude,
headingInwards: value.headingInwards
}, this.sourceId);
});
const onChange = () => {
let periapsis = parseFloat(periapsisInput.value);
let apoapsis = parseFloat(apoapsisInput.value);
let eccentricity = parseFloat(eccentricityInput.value);
let inclination = parseFloat(inclinationInput.value) * Math.PI / 180.0;
let lan = parseFloat(lanInput.value) * Math.PI / 180.0;
let aop = parseFloat(aopInput.value) * Math.PI / 180.0;
let newOrbitalParameters: OrbitalParameters = {
periapsis: periapsis,
eccentricChoice: apoapsisChoiceButton.checked ? "apoapsis" : "eccentricity",
apoapsis: apoapsis,
eccentricity: eccentricity,
inclination: inclination,
longitudeOfAscendingNode: lan,
argumentOfPeriapsis: aop,
positionChoice: timeToPeriapsisButton.checked ? "timeToPeriapsis" : "altitude",
timeToPeriapsis: this.timeToPeriapsis.getCurrentValue(),
altitude: this.altitudeData.getCurrentValue().altitude,
headingInwards: this.altitudeData.getCurrentValue().headingInwards
};
this.orbitalParameters.set(newOrbitalParameters, this.sourceId);
};
[
periapsisInput,
apoapsisChoiceButton,
eccentricityChoiceButton,
apoapsisInput,
eccentricityInput,
inclinationInput,
lanInput,
aopInput,
timeToPeriapsisButton,
altitudeButton
].forEach(input => input.addEventListener("change", onChange));
this.timeToPeriapsis.listenToValue((sourceId, _) => {
if (sourceId == this.timeGui.sourceId) {
onChange();
}
});
this.altitudeData.listenToValue((sourceId, _) => {
if (sourceId == this.altitudeGui.sourceId) {
onChange();
}
})
} }
} }

View File

@ -1,61 +1,61 @@
import { type Body, Kerbol, PlanetList } from "../calculations/constants"; import { type Body, PlanetList } from "../calculations/constants";
import { ChangingStorageValue } from "../storage";
export class PlanetGui { export class PlanetGui {
listeners: ((newValue: Body) => void)[]; parentDiv: HTMLDivElement;
body: ChangingStorageValue<Body>
constructor(htmlContainer: HTMLElement, startingValue?: Body, containerCreator?: (body: Body) => HTMLElement) { constructor(body: ChangingStorageValue<Body>) {
this.listeners = []; this.body = body;
this.parentDiv = document.createElement("div");
this.parentDiv.classList.add("planetInput")
if (!startingValue) { const addToParent = (toWrap: HTMLElement | HTMLElement[], parent: HTMLElement) => {
startingValue = Kerbol;
}
const addToParent = (toWrap: HTMLElement | HTMLElement[], body: Body) => {
var children = []; var children = [];
if (toWrap instanceof HTMLElement) { if (toWrap instanceof HTMLElement) {
children = [toWrap]; children = [toWrap];
} else { } else {
children = toWrap; children = toWrap;
} }
children.forEach(child => parent.appendChild(child));
let containerToAppendTo = htmlContainer;
if (containerCreator !== undefined) {
let childContainer = containerCreator(body);
containerToAppendTo.appendChild(childContainer);
containerToAppendTo = childContainer;
}
children.forEach(child => containerToAppendTo.appendChild(child));
}; };
let planetChoiceName = crypto.randomUUID(); let planetChoiceName = crypto.randomUUID();
let planetToButtonMap = new Map<Body, HTMLInputElement>();
PlanetList.forEach(body => { PlanetList.forEach(body => {
let listDiv = document.createElement("div");
listDiv.classList.add(body.type);
let radioButton = document.createElement("input"); let radioButton = document.createElement("input");
let buttonId = crypto.randomUUID(); let buttonId = crypto.randomUUID();
radioButton.setAttribute("type", "radio"); radioButton.setAttribute("type", "radio");
radioButton.setAttribute("id", buttonId); radioButton.setAttribute("id", buttonId);
radioButton.setAttribute("name", planetChoiceName); radioButton.setAttribute("name", planetChoiceName);
radioButton.setAttribute("value", body.planetName); radioButton.setAttribute("value", body.planetName);
if (body === startingValue) {
radioButton.setAttribute("checked", "");
}
radioButton.addEventListener("change", () => this.informListeners(body));
let label = document.createElement("label"); let label = document.createElement("label");
label.setAttribute("for", buttonId); label.setAttribute("for", buttonId);
label.appendChild(document.createTextNode(body.planetName)); label.appendChild(document.createTextNode(body.planetName));
addToParent([radioButton, label], body); addToParent([radioButton, label], listDiv);
this.parentDiv.appendChild(listDiv);
planetToButtonMap.set(body, radioButton);
});
let thisSourceId = crypto.randomUUID();
body.listenToValue((changeSource: string, value: Body) => {
if (changeSource == thisSourceId) {
return;
}
let button = planetToButtonMap.get(value);
button?.setAttribute("checked", "");
});
planetToButtonMap.forEach((button, body) => {
button.addEventListener("change", () => this.body.set(body, thisSourceId));
}); });
} }
addListener(listener: (newValue: Body) => void) {
this.listeners.push(listener);
}
informListeners(newValue: Body) {
this.listeners.forEach(listener => listener(newValue));
}
} }

View File

@ -1,226 +1,87 @@
import type { Wrapper } from "../storage"; import { getCoordinatesFromParameters, type OrbitalParameters } from "./common";
import { createDisabledInput, createLabel, createNumberInput, getOrbitFromParameters, type OrbitalParameters } from "./common";
import { type Body } from "../calculations/constants"; import { type Body } from "../calculations/constants";
import { calculateSimplePlaneChange, getOrbitalCoordinates} from "../calculations/orbit-calculations"; import type { ChangingStorageValue } from "../storage";
import { OrbitalParametersGui } from "./orbit";
import { ManoeuvresGui } from "./manoeuvres";
import { calculateSimplePlaneChange, type Transfer } from "../calculations/orbit-calculations";
import { type ProgressMessage } from "./worker";
export class SimplePlaneChangeGui { export class SimplePlaneChangeGui {
listeners: ((targetInclination: number, targetLongitudeOfAscendingNode: number, circularize: boolean) => void)[]; parentDiv: HTMLDivElement;
currentTime: Wrapper<number>; currentTime: ChangingStorageValue<number>;
timeToPeriapsis: Wrapper<number>; planet: ChangingStorageValue<Body>;
planet: Wrapper<Body>; startingOrbitalParameters: ChangingStorageValue<OrbitalParameters>;
startingOrbitalParameters: Wrapper<OrbitalParameters>; targetOrbitalParameters: ChangingStorageValue<OrbitalParameters>;
goalOrbitalParameters: Wrapper<OrbitalParameters>; circularizeOrbit: ChangingStorageValue<boolean>;
circularizeOrbit: boolean;
inputHeader: HTMLElement; targetOrbitGui: OrbitalParametersGui;
manoeuvresGui: ManoeuvresGui;
targetInclinationLabel: HTMLLabelElement; constructor(currentTime: ChangingStorageValue<number>, planet: ChangingStorageValue<Body>, startingOrbitalParameters: ChangingStorageValue<OrbitalParameters>, targetOrbitalParameters: ChangingStorageValue<OrbitalParameters>, circularizeOrbit: ChangingStorageValue<boolean>) {
targetInclinationInput: HTMLInputElement;
targetLongitudeOfAscendingNodeLabel: HTMLLabelElement;
targetLongitudeOfAscendingNodeInput: HTMLInputElement;
circularizeOrbitLabel: HTMLLabelElement;
circularizeOrbitInput: HTMLInputElement;
calculateButton: HTMLButtonElement;
outputHeader: HTMLElement;
manoeuvresContainer: HTMLDivElement;
firstManoeuvreTime: HTMLInputElement;
firstManoeuvrePrograde: HTMLInputElement;
firstManoeuvreRadial: HTMLElement;
firstManoeuvreNormal: HTMLElement;
firstManoeuvreTotal: HTMLElement;
secondManoeuvreTime: HTMLInputElement;
secondManoeuvrePrograde: HTMLInputElement;
secondManoeuvreRadial: HTMLElement;
secondManoeuvreNormal: HTMLElement;
secondManoeuvreTotal: HTMLElement;
constructor(startingParameters: Wrapper<OrbitalParameters>, goalParameters: Wrapper<OrbitalParameters>, currentTime: Wrapper<number>, timeToPeriapsis: Wrapper<number>, planet: Wrapper<Body>, defaultCircularizeOrbit?: boolean) {
this.listeners = [];
this.startingOrbitalParameters = startingParameters;
this.goalOrbitalParameters = goalParameters;
this.currentTime = currentTime; this.currentTime = currentTime;
this.timeToPeriapsis = timeToPeriapsis;
this.planet = planet; this.planet = planet;
this.startingOrbitalParameters = startingOrbitalParameters;
this.targetOrbitalParameters = targetOrbitalParameters;
this.circularizeOrbit = circularizeOrbit;
if (defaultCircularizeOrbit) { this.targetOrbitGui = new OrbitalParametersGui(targetOrbitalParameters, "plane");
this.circularizeOrbit = defaultCircularizeOrbit; this.manoeuvresGui = new ManoeuvresGui(false);
} else {
this.circularizeOrbit = false;
}
this.inputHeader = document.createElement("h3"); this.parentDiv = document.createElement("div");
this.inputHeader.appendChild(document.createTextNode("Target plane:"));
let targetInclinationId = crypto.randomUUID(); let targetPlaneHeader = document.createElement("h3");
this.targetInclinationLabel = createLabel(targetInclinationId, "Target inclination:"); targetPlaneHeader.appendChild(document.createTextNode("Target plane:"));
this.targetInclinationInput = createNumberInput(targetInclinationId, -360, 360);
let targetLongitudeOfAscendingNodeId = crypto.randomUUID(); this.parentDiv.appendChild(targetPlaneHeader);
this.targetLongitudeOfAscendingNodeLabel = createLabel(targetLongitudeOfAscendingNodeId, "Target longitude of ascending node:"); this.parentDiv.appendChild(this.targetOrbitGui.parentDiv);
this.targetLongitudeOfAscendingNodeInput = createNumberInput(targetLongitudeOfAscendingNodeId, -360, 360);
let circularizeOrbitId = crypto.randomUUID(); let findPlaneChangeButton = document.createElement("button");
this.circularizeOrbitLabel = createLabel(circularizeOrbitId, "Circularize orbit"); findPlaneChangeButton.appendChild(document.createTextNode("Find plane change"));
this.circularizeOrbitInput = document.createElement("input"); this.parentDiv.appendChild(findPlaneChangeButton);
this.circularizeOrbitInput.setAttribute("id", circularizeOrbitId);
this.circularizeOrbitInput.setAttribute("type", "checkbox");
this.targetInclinationInput.addEventListener("change", this.informListeners.bind(this)); let possibleManoeuvresHeader = document.createElement("h3");
this.targetLongitudeOfAscendingNodeInput.addEventListener("change", this.informListeners.bind(this)); possibleManoeuvresHeader.appendChild(document.createTextNode("Possible manoeuvres:"));
this.circularizeOrbitInput.addEventListener("change", this.informListeners.bind(this)); this.parentDiv.appendChild(possibleManoeuvresHeader);
this.parentDiv.appendChild(this.manoeuvresGui.parentDiv);
this.calculateButton = document.createElement("button"); findPlaneChangeButton.addEventListener("click", () => {
this.calculateButton.appendChild(document.createTextNode("Calculate")); let currentTime = this.currentTime.getCurrentValue();
this.calculateButton.addEventListener("click", this.performCalculation.bind(this)); let body = this.planet.getCurrentValue();
let startingOrbitalParameters = this.startingOrbitalParameters.getCurrentValue();
let targetOrbitalParameters = this.targetOrbitalParameters.getCurrentValue();
let circularizeOrbit = this.circularizeOrbit.getCurrentValue();
this.outputHeader = document.createElement("h3"); let coordinates = getCoordinatesFromParameters(startingOrbitalParameters, body);
this.outputHeader.appendChild(document.createTextNode("Possible manoeuvres:")); let planeChange = calculateSimplePlaneChange(
coordinates,
body,
targetOrbitalParameters.inclination,
targetOrbitalParameters.longitudeOfAscendingNode,
circularizeOrbit
);
this.manoeuvresContainer = document.createElement("div"); planeChange.firstManoeuvre.time += currentTime;
this.manoeuvresContainer.classList.add("flexContainer"); planeChange.secondManoeuvre.time += currentTime;
let manoeuvreOneContainer = document.createElement("div"); let bestDeltaV = Math.min(planeChange.firstManoeuvre.totalDeltaV, planeChange.secondManoeuvre.totalDeltaV);
let manoeuvreTwoContainer = document.createElement("div"); let bestTransfer: Transfer = {
transferOrbit: {semiLatusRectum: 0, eccentricity: 0, coordinateAxes: [[], [], []]},
firstManoeuvre: planeChange.firstManoeuvre,
secondManoeuvre: planeChange.secondManoeuvre,
closestPointDistance: 0,
farthestPointDistance: 0
}
this.manoeuvresContainer.appendChild(manoeuvreOneContainer); let progressMessage: ProgressMessage = {
this.manoeuvresContainer.appendChild(manoeuvreTwoContainer); type: "ProgressMessage",
finished: true,
percentDone: 100,
bestDeltaV: bestDeltaV,
bestTransfer: bestTransfer
};
let manoeuvreOneHeader = document.createElement("h4"); this.manoeuvresGui.displayProgress(progressMessage);
manoeuvreOneHeader.appendChild(document.createTextNode("First manoeuvre:")); });
manoeuvreOneContainer.appendChild(manoeuvreOneHeader);
let manoeuvreTwoHeader = document.createElement("h4");
manoeuvreTwoHeader.appendChild(document.createTextNode("Second manoeuvre:"));
manoeuvreTwoContainer.appendChild(manoeuvreTwoHeader);
const addTo = (container: HTMLElement, children: HTMLElement[]) => {
children.forEach(child => {
container.appendChild(child);
})
container.appendChild(document.createElement("br"));
};
let firstTimeId = crypto.randomUUID();
let firstTimeLabel = createLabel(firstTimeId, "Time:");
this.firstManoeuvreTime = createDisabledInput(firstTimeId);
addTo(manoeuvreOneContainer, [firstTimeLabel, this.firstManoeuvreTime]);
let firstProgradeId = crypto.randomUUID();
let firstProgradeLabel = createLabel(firstProgradeId, "Prograde delta-v:");
this.firstManoeuvrePrograde = createDisabledInput(firstProgradeId);
addTo(manoeuvreOneContainer, [firstProgradeLabel, this.firstManoeuvrePrograde]);
let firstNormalId = crypto.randomUUID();
let firstNormalLabel = createLabel(firstNormalId, "Normal delta-v:");
this.firstManoeuvreNormal = createDisabledInput(firstNormalId);
addTo(manoeuvreOneContainer, [firstNormalLabel, this.firstManoeuvreNormal]);
let firstRadialId = crypto.randomUUID();
let firstRadialLabel = createLabel(firstRadialId, "Radial delta-v:");
this.firstManoeuvreRadial = createDisabledInput(firstRadialId);
addTo(manoeuvreOneContainer, [firstRadialLabel, this.firstManoeuvreRadial]);
let firstTotalId = crypto.randomUUID();
let firstTotalLabel = createLabel(firstTotalId, "Total delta-v:");
this.firstManoeuvreTotal = createDisabledInput(firstTotalId);
addTo(manoeuvreOneContainer, [firstTotalLabel, this.firstManoeuvreTotal]);
let secondTimeId = crypto.randomUUID();
let secondTimeLabel = createLabel(secondTimeId, "Time:");
this.secondManoeuvreTime = createDisabledInput(secondTimeId);
addTo(manoeuvreTwoContainer, [secondTimeLabel, this.secondManoeuvreTime]);
let secondProgradeId = crypto.randomUUID();
let secondProgradeLabel = createLabel(secondProgradeId, "Prograde delta-v:");
this.secondManoeuvrePrograde = createDisabledInput(secondProgradeId);
addTo(manoeuvreTwoContainer, [secondProgradeLabel, this.secondManoeuvrePrograde]);
let secondNormalId = crypto.randomUUID();
let secondNormalLabel = createLabel(secondNormalId, "Normal delta-v:");
this.secondManoeuvreNormal = createDisabledInput(secondNormalId);
addTo(manoeuvreTwoContainer, [secondNormalLabel, this.secondManoeuvreNormal]);
let secondRadialId = crypto.randomUUID();
let secondRadialLabel = createLabel(secondRadialId, "Radial delta-v:");
this.secondManoeuvreRadial = createDisabledInput(secondRadialId);
addTo(manoeuvreTwoContainer, [secondRadialLabel, this.secondManoeuvreRadial]);
let secondTotalId = crypto.randomUUID();
let secondTotalLabel = createLabel(secondTotalId, "Total delta-v:");
this.secondManoeuvreTotal = createDisabledInput(secondTotalId);
addTo(manoeuvreTwoContainer, [secondTotalLabel, this.secondManoeuvreTotal]);
this.populateValues();
}
addToParentElement(parentElement: HTMLElement) {
parentElement.appendChild(this.inputHeader);
parentElement.appendChild(this.targetInclinationLabel);
parentElement.appendChild(this.targetInclinationInput);
parentElement.appendChild(document.createElement("br"));
parentElement.appendChild(this.targetLongitudeOfAscendingNodeLabel);
parentElement.appendChild(this.targetLongitudeOfAscendingNodeInput);
parentElement.appendChild(document.createElement("br"));
parentElement.appendChild(this.circularizeOrbitInput);
parentElement.appendChild(this.circularizeOrbitLabel);
parentElement.appendChild(document.createElement("br"));
parentElement.appendChild(this.calculateButton);
parentElement.appendChild(this.outputHeader);
parentElement.appendChild(this.manoeuvresContainer);
if (this.circularizeOrbit) {
this.circularizeOrbitInput.setAttribute("checked", "");
} else {
this.circularizeOrbitInput.removeAttribute("checked");
}
this.populateValues();
}
addListener(listener: (targetInclination: number, targetLongitudeOfAscendingNode: number, circularize: boolean) => void) {
this.listeners.push(listener);
}
informListeners() {
let targetInclination = parseFloat(this.targetInclinationInput.value) * Math.PI / 180.0;
let targetLAN = parseFloat(this.targetLongitudeOfAscendingNodeInput.value) * Math.PI / 180.0;
this.circularizeOrbit = this.circularizeOrbitInput.checked;
this.listeners.forEach(listener => listener(targetInclination, targetLAN, this.circularizeOrbit));
}
populateValues() {
this.targetInclinationInput.setAttribute("value", (this.goalOrbitalParameters.value.inclination * 180 / Math.PI).toString());
this.targetLongitudeOfAscendingNodeInput.setAttribute("value", (this.goalOrbitalParameters.value.longitudeOfAscendingNode * 180 / Math.PI).toString());
}
performCalculation() {
let orbit = getOrbitFromParameters(this.startingOrbitalParameters.value, this.planet.value.radius);
let coordinates = getOrbitalCoordinates(this.timeToPeriapsis.value, orbit, this.planet.value);
let simplePlaneChange = calculateSimplePlaneChange(
coordinates,
this.planet.value,
this.goalOrbitalParameters.value.inclination,
this.goalOrbitalParameters.value.longitudeOfAscendingNode,
this.circularizeOrbit
);
this.firstManoeuvreTime.setAttribute("value", (simplePlaneChange.firstManoeuvre.time + this.currentTime.value).toFixed(0));
this.firstManoeuvrePrograde.setAttribute("value", simplePlaneChange.firstManoeuvre.progradeDeltaV.toFixed(1));
this.firstManoeuvreRadial.setAttribute("value", simplePlaneChange.firstManoeuvre.radialDeltaV.toFixed(1));
this.firstManoeuvreNormal.setAttribute("value", simplePlaneChange.firstManoeuvre.normalDeltaV.toFixed(1));
this.firstManoeuvreTotal.setAttribute("value", simplePlaneChange.firstManoeuvre.totalDeltaV.toFixed(1));
this.secondManoeuvreTime.setAttribute("value", (simplePlaneChange.secondManoeuvre.time + this.currentTime.value).toFixed(0));
this.secondManoeuvrePrograde.setAttribute("value", simplePlaneChange.secondManoeuvre.progradeDeltaV.toFixed(1));
this.secondManoeuvreRadial.setAttribute("value", simplePlaneChange.secondManoeuvre.radialDeltaV.toFixed(1));
this.secondManoeuvreNormal.setAttribute("value", simplePlaneChange.secondManoeuvre.normalDeltaV.toFixed(1));
this.secondManoeuvreTotal.setAttribute("value", simplePlaneChange.secondManoeuvre.totalDeltaV.toFixed(1));
} }
} }

View File

@ -1,143 +1,49 @@
import { getOrbitalCoordinates } from "../calculations/orbit-calculations"; import type { Body } from "../calculations/constants";
import type { Wrapper } from "../storage"; import type { ChangingStorageValue } from "../storage";
import { createDisabledInput, createLabel, getOrbitFromParameters, type OrbitalParameters } from "./common"; import { getCoordinatesFromParameters, getOrbitFromParameters, type OrbitalParameters } from "./common";
import { ManoeuvresGui } from "./manoeuvres";
import { OrbitalParametersGui } from "./orbit"; import { OrbitalParametersGui } from "./orbit";
import { type Body } from "../calculations/constants"; import type { FindBestTransferMessage, ProgressMessage } from "./worker";
import type { FindBestTransferMessage, FindBestTransferResponse } from "./worker";
export class TargetOrbitGui { export class TargetOrbitGui {
startingParameters: Wrapper<OrbitalParameters>; currentTime: ChangingStorageValue<number>;
goalParameters: Wrapper<OrbitalParameters>; body: ChangingStorageValue<Body>;
currentTime: Wrapper<number>; startingOrbitalParameters: ChangingStorageValue<OrbitalParameters>;
timeToPeriapsis: Wrapper<number>; targetOrbitalParameters: ChangingStorageValue<OrbitalParameters>;
body: Wrapper<Body>;
orbitGui: OrbitalParametersGui;
parentDiv: HTMLDivElement; parentDiv: HTMLDivElement;
orbitGui: OrbitalParametersGui;
progressParagraph: HTMLParagraphElement; manoeuvresGui: ManoeuvresGui;
firstManoeuvreTime: HTMLInputElement;
firstManoeuvrePrograde: HTMLInputElement;
firstManoeuvreNormal: HTMLInputElement;
firstManoeuvreRadial: HTMLInputElement;
firstManoeuvreTotal: HTMLInputElement;
secondManoeuvreTime: HTMLInputElement;
secondManoeuvrePrograde: HTMLInputElement;
secondManoeuvreNormal: HTMLInputElement;
secondManoeuvreRadial: HTMLInputElement;
secondManoeuvreTotal: HTMLInputElement;
worker: Worker | null; worker: Worker | null;
constructor(startingParameters: Wrapper<OrbitalParameters>, goalParameters: Wrapper<OrbitalParameters>, currentTime: Wrapper<number>, timeToPeriapsis: Wrapper<number>, body: Wrapper<Body>) { constructor(currentTime: ChangingStorageValue<number>, body: ChangingStorageValue<Body>, startingOrbitalParameters: ChangingStorageValue<OrbitalParameters>, targetOrbitalParameters: ChangingStorageValue<OrbitalParameters>) {
this.startingParameters = startingParameters; this.worker = null;
this.goalParameters = goalParameters;
this.currentTime = currentTime; this.currentTime = currentTime;
this.timeToPeriapsis = timeToPeriapsis;
this.body = body; this.body = body;
this.startingOrbitalParameters = startingOrbitalParameters;
this.targetOrbitalParameters = targetOrbitalParameters;
this.orbitGui = new OrbitalParametersGui(this.targetOrbitalParameters, "orbit");
this.manoeuvresGui = new ManoeuvresGui(true);
this.parentDiv = document.createElement("div"); this.parentDiv = document.createElement("div");
let parametersHeader = document.createElement("h3");
parametersHeader.appendChild(document.createTextNode("Target orbit:"));
this.parentDiv.appendChild(parametersHeader);
let orbitalParameterContainerCreator = () => { let targetOrbitHeader = document.createElement("h3");
let orbitalParameterContainer = document.createElement("div"); targetOrbitHeader.appendChild(document.createTextNode("Target orbit:"));
orbitalParameterContainer.classList.add("orbitalParameter"); this.parentDiv.appendChild(targetOrbitHeader);
return orbitalParameterContainer; this.parentDiv.appendChild(this.orbitGui.parentDiv);
}
this.orbitGui = new OrbitalParametersGui(this.parentDiv, goalParameters.value, orbitalParameterContainerCreator);
let searchButton = document.createElement("button"); let searchButton = document.createElement("button");
searchButton.appendChild(document.createTextNode("Search for cheapest transfer")); searchButton.appendChild(document.createTextNode("Search for cheapest transfer"));
this.parentDiv.appendChild(searchButton); this.parentDiv.appendChild(searchButton);
let searchHeader = document.createElement("h3"); let foundTransferHeader = document.createElement("h3");
searchHeader.appendChild(document.createTextNode("Manoeuvre search")); foundTransferHeader.appendChild(document.createTextNode("Found transfer:"));
this.parentDiv.appendChild(searchHeader); this.parentDiv.appendChild(foundTransferHeader);
this.progressParagraph = document.createElement("p"); this.parentDiv.appendChild(this.manoeuvresGui.parentDiv);
this.parentDiv.appendChild(this.progressParagraph);
let manoeuvresContainer = document.createElement("div");
manoeuvresContainer.classList.add("flexContainer");
this.parentDiv.appendChild(manoeuvresContainer)
let manoeuvreOneContainer = document.createElement("div");
let manoeuvreTwoContainer = document.createElement("div");
manoeuvresContainer.appendChild(manoeuvreOneContainer);
manoeuvresContainer.appendChild(manoeuvreTwoContainer);
let manoeuvreOneHeader = document.createElement("h4");
manoeuvreOneHeader.appendChild(document.createTextNode("First manoeuvre:"));
manoeuvreOneContainer.appendChild(manoeuvreOneHeader);
let manoeuvreTwoHeader = document.createElement("h4");
manoeuvreTwoHeader.appendChild(document.createTextNode("Second manoeuvre:"));
manoeuvreTwoContainer.appendChild(manoeuvreTwoHeader);
const addTo = (container: HTMLElement, children: HTMLElement[]) => {
children.forEach(child => {
container.appendChild(child);
})
container.appendChild(document.createElement("br"));
};
let firstTimeId = crypto.randomUUID();
let firstTimeLabel = createLabel(firstTimeId, "Time:");
this.firstManoeuvreTime = createDisabledInput(firstTimeId);
addTo(manoeuvreOneContainer, [firstTimeLabel, this.firstManoeuvreTime]);
let firstProgradeId = crypto.randomUUID();
let firstProgradeLabel = createLabel(firstProgradeId, "Prograde delta-v:");
this.firstManoeuvrePrograde = createDisabledInput(firstProgradeId);
addTo(manoeuvreOneContainer, [firstProgradeLabel, this.firstManoeuvrePrograde]);
let firstNormalId = crypto.randomUUID();
let firstNormalLabel = createLabel(firstNormalId, "Normal delta-v:");
this.firstManoeuvreNormal = createDisabledInput(firstNormalId);
addTo(manoeuvreOneContainer, [firstNormalLabel, this.firstManoeuvreNormal]);
let firstRadialId = crypto.randomUUID();
let firstRadialLabel = createLabel(firstRadialId, "Radial delta-v:");
this.firstManoeuvreRadial = createDisabledInput(firstRadialId);
addTo(manoeuvreOneContainer, [firstRadialLabel, this.firstManoeuvreRadial]);
let firstTotalId = crypto.randomUUID();
let firstTotalLabel = createLabel(firstTotalId, "Total delta-v:");
this.firstManoeuvreTotal = createDisabledInput(firstTotalId);
addTo(manoeuvreOneContainer, [firstTotalLabel, this.firstManoeuvreTotal]);
let secondTimeId = crypto.randomUUID();
let secondTimeLabel = createLabel(secondTimeId, "Time:");
this.secondManoeuvreTime = createDisabledInput(secondTimeId);
addTo(manoeuvreTwoContainer, [secondTimeLabel, this.secondManoeuvreTime]);
let secondProgradeId = crypto.randomUUID();
let secondProgradeLabel = createLabel(secondProgradeId, "Prograde delta-v:");
this.secondManoeuvrePrograde = createDisabledInput(secondProgradeId);
addTo(manoeuvreTwoContainer, [secondProgradeLabel, this.secondManoeuvrePrograde]);
let secondNormalId = crypto.randomUUID();
let secondNormalLabel = createLabel(secondNormalId, "Normal delta-v:");
this.secondManoeuvreNormal = createDisabledInput(secondNormalId);
addTo(manoeuvreTwoContainer, [secondNormalLabel, this.secondManoeuvreNormal]);
let secondRadialId = crypto.randomUUID();
let secondRadialLabel = createLabel(secondRadialId, "Radial delta-v:");
this.secondManoeuvreRadial = createDisabledInput(secondRadialId);
addTo(manoeuvreTwoContainer, [secondRadialLabel, this.secondManoeuvreRadial]);
let secondTotalId = crypto.randomUUID();
let secondTotalLabel = createLabel(secondTotalId, "Total delta-v:");
this.secondManoeuvreTotal = createDisabledInput(secondTotalId);
addTo(manoeuvreTwoContainer, [secondTotalLabel, this.secondManoeuvreTotal]);
this.worker = null;
searchButton.addEventListener("click", () => { searchButton.addEventListener("click", () => {
if (this.worker !== null) { if (this.worker !== null) {
@ -147,63 +53,41 @@ export class TargetOrbitGui {
} else { } else {
searchButton.innerHTML = "Cancel search"; searchButton.innerHTML = "Cancel search";
let startingOrbit = getOrbitFromParameters(startingParameters.value, this.body.value.radius); let currentTime = this.currentTime.getCurrentValue();
let endingOrbit = getOrbitFromParameters(goalParameters.value, this.body.value.radius); let startingOrbitalParameters = this.startingOrbitalParameters.getCurrentValue();
let currentCoordinates = getOrbitalCoordinates(timeToPeriapsis.value, startingOrbit, this.body.value); let targetOrbitalParameters = this.targetOrbitalParameters.getCurrentValue();
let body = this.body.getCurrentValue();
let startingCoordinates = getCoordinatesFromParameters(startingOrbitalParameters, body);
let targetOrbit = getOrbitFromParameters(targetOrbitalParameters, body.radius);
this.worker = new Worker(new URL('./worker.ts', import.meta.url), {type: 'module'}); this.worker = new Worker(new URL('./worker.ts', import.meta.url), {type: 'module'});
this.worker.addEventListener("message", event => { this.worker.addEventListener("message", event => {
let transferResponse = event.data as FindBestTransferResponse; let transferResponse = event.data as ProgressMessage;
if (transferResponse) { if (transferResponse) {
if (transferResponse.finished) { if (transferResponse.finished) {
searchButton.innerHTML = "Search for cheapest transfer"; searchButton.innerHTML = "Search for cheapest transfer";
this.worker = null; this.worker = null;
} }
this.progressParagraph.innerHTML = ""; if (transferResponse.bestTransfer) {
this.progressParagraph.appendChild(document.createTextNode(`Search is ${transferResponse.percentDone.toFixed(2)}% finished`)); transferResponse.bestTransfer.firstManoeuvre.time += currentTime;
if (transferResponse.bestDeltaV !== null && transferResponse.bestTransfer != null) { transferResponse.bestTransfer.secondManoeuvre.time += currentTime;
this.progressParagraph.appendChild(document.createElement("br"));
this.progressParagraph.appendChild(document.createTextNode(`Best transfer costs ${transferResponse.bestDeltaV.toFixed(3)} m/s`));
let transfer = transferResponse.bestTransfer;
transfer.firstManoeuvre.time += currentTime.value;
transfer.secondManoeuvre.time += currentTime.value;
this.firstManoeuvreTime.value = transfer.firstManoeuvre.time.toFixed(0);
this.firstManoeuvrePrograde.value = transfer.firstManoeuvre.progradeDeltaV.toFixed(2);
this.firstManoeuvreNormal.value = transfer.firstManoeuvre.normalDeltaV.toFixed(2);
this.firstManoeuvreRadial.value = transfer.firstManoeuvre.radialDeltaV.toFixed(2);
this.firstManoeuvreTotal.value = transfer.firstManoeuvre.totalDeltaV.toFixed(2);
this.secondManoeuvreTime.value = transfer.secondManoeuvre.time.toFixed(0);
this.secondManoeuvrePrograde.value = transfer.secondManoeuvre.progradeDeltaV.toFixed(2);
this.secondManoeuvreNormal.value = transfer.secondManoeuvre.normalDeltaV.toFixed(2);
this.secondManoeuvreRadial.value = transfer.secondManoeuvre.radialDeltaV.toFixed(2);
this.secondManoeuvreTotal.value = transfer.secondManoeuvre.totalDeltaV.toFixed(2);
} }
this.manoeuvresGui.displayProgress(transferResponse);
} }
}); });
let workerMessage: FindBestTransferMessage = { let workerMessage: FindBestTransferMessage = {
type: "FindBestTransfer", type: "FindBestTransfer",
startingSituation: currentCoordinates, startingSituation: startingCoordinates,
targetOrbit: endingOrbit, targetOrbit: targetOrbit,
body: body.value body: body
}; };
this.worker.postMessage(workerMessage); this.worker.postMessage(workerMessage);
} }
}); });
} }
addToParentElement(parentElement: HTMLElement) {
this.orbitGui.setValues(this.goalParameters.value);
parentElement.appendChild(this.parentDiv);
}
addListener(listener: (newValue: OrbitalParameters) => void) {
this.orbitGui.addListener(listener);
}
} }

View File

@ -1,20 +1,17 @@
import type { ChangingStorageValue } from "../storage";
import { createLabel, createNumberInput } from "./common"; import { createLabel, createNumberInput } from "./common";
export class TimeGui { export class TimeGui {
dateListeners: ((newDate: number) => void)[]; parentDiv: HTMLDivElement;
isDate: boolean; currentTime: ChangingStorageValue<number>;
yearsInput: HTMLInputElement;
daysInput: HTMLInputElement;
hoursInput: HTMLInputElement;
minutesInput: HTMLInputElement;
secondsInput: HTMLInputElement;
constructor(htmlContainer: HTMLElement, isDate?: boolean, startingValue?: number, containerCreator?: () => HTMLElement) { sourceId: string;
this.dateListeners = [];
if (isDate !== undefined) { constructor(currentTime: ChangingStorageValue<number>, isDate?: boolean) {
this.isDate = isDate; this.currentTime = currentTime;
} else {
this.isDate = false; if (isDate === undefined) {
isDate = false;
} }
var minimumYears = 0; var minimumYears = 0;
@ -24,89 +21,89 @@ export class TimeGui {
minimumDays = 1; minimumDays = 1;
} }
const addToParent = (toWrap: HTMLElement | HTMLElement[]) => { const addToParent = (toWrap: HTMLElement | HTMLElement[], parent: HTMLElement) => {
var children = []; var children = [];
if (toWrap instanceof HTMLElement) { if (toWrap instanceof HTMLElement) {
children = [toWrap]; children = [toWrap];
} else { } else {
children = toWrap; children = toWrap;
} }
let containerToAppendTo = htmlContainer;
if (containerCreator !== undefined) {
let childContainer = containerCreator();
containerToAppendTo.appendChild(childContainer);
containerToAppendTo = childContainer;
}
children.forEach(child => containerToAppendTo.appendChild(child)); let timeInput = document.createElement("span");
timeInput.classList.add("timeInput");
children.forEach(child => timeInput.appendChild(child));
parent.appendChild(timeInput);
}; };
this.parentDiv = document.createElement("div");
let yearsInputId = crypto.randomUUID(); let yearsInputId = crypto.randomUUID();
this.yearsInput = createNumberInput(yearsInputId, minimumYears, undefined, 2); let yearsInput = createNumberInput(yearsInputId, minimumYears, undefined, 2);
addToParent([createLabel(yearsInputId, "Years:"), this.yearsInput]); addToParent([createLabel(yearsInputId, "Years:"), yearsInput], this.parentDiv);
let daysInputId = crypto.randomUUID(); let daysInputId = crypto.randomUUID();
this.daysInput = createNumberInput(daysInputId, minimumDays, 424, 1); let daysInput = createNumberInput(daysInputId, minimumDays, 424, 1);
addToParent([createLabel(daysInputId, "Days:"), this.daysInput]); addToParent([createLabel(daysInputId, "Days:"), daysInput], this.parentDiv);
let hoursInputId = crypto.randomUUID(); let hoursInputId = crypto.randomUUID();
this.hoursInput = createNumberInput(hoursInputId, 0, 5, 1); let hoursInput = createNumberInput(hoursInputId, 0, 5, 1);
addToParent([createLabel(hoursInputId, "Hours:"), this.hoursInput]); addToParent([createLabel(hoursInputId, "Hours:"), hoursInput], this.parentDiv);
let minutesInputId = crypto.randomUUID(); let minutesInputId = crypto.randomUUID();
this.minutesInput = createNumberInput(minutesInputId, 0, 59, 1); let minutesInput = createNumberInput(minutesInputId, 0, 59, 1);
addToParent([createLabel(minutesInputId, "Minutes:"), this.minutesInput]); addToParent([createLabel(minutesInputId, "Minutes:"), minutesInput], this.parentDiv);
let secondsInputId = crypto.randomUUID(); let secondsInputId = crypto.randomUUID();
this.secondsInput = createNumberInput(secondsInputId, 0, 59, 1); let secondsInput = createNumberInput(secondsInputId, 0, 59, 1);
addToParent([createLabel(secondsInputId, "Seconds:"), this.secondsInput]); addToParent([createLabel(secondsInputId, "Seconds:"), secondsInput], this.parentDiv);
if (startingValue === undefined) { // Set up listeners for these changes and stuff
startingValue = 0; this.sourceId = crypto.randomUUID();
const currentTimeChangeListener = (source: string, newValue: number) => {
if (source == this.sourceId) {
return;
}
const seconds = newValue % 60;
newValue = Math.floor(newValue / 60);
const minutes = newValue % 60;
newValue = Math.floor(newValue / 60);
const hours = (newValue % 6);
newValue = Math.floor(newValue / 6);
const days = (newValue % 426) + (isDate ? 1 : 0);
newValue = Math.floor(newValue / 426);
const years = newValue + (isDate ? 1 : 0);
yearsInput.setAttribute("value", years.toFixed(0));
daysInput.setAttribute("value", days.toFixed(0));
hoursInput.setAttribute("value", hours.toFixed(0));
minutesInput.setAttribute("value", minutes.toFixed(0));
secondsInput.setAttribute("value", seconds.toFixed(0));
};
currentTime.listenToValue(currentTimeChangeListener);
const calculateAndSetTime = () => {
let years = parseInt(yearsInput.value);
let days = parseInt(daysInput.value);
let hours = parseInt(hoursInput.value);
let minutes = parseInt(minutesInput.value);
let seconds = parseInt(secondsInput.value);
if (isDate) {
years -= 1;
days -= 1;
}
let calculatedTime = (((years * 426 + days) * 6 + hours) * 60 + minutes) * 60 + seconds;
this.currentTime.set(calculatedTime, this.sourceId);
} }
const startingSeconds = startingValue % 60; yearsInput.addEventListener("change", calculateAndSetTime);
startingValue = Math.floor(startingValue / 60); daysInput.addEventListener("change", calculateAndSetTime);
const startingMinutes = startingValue % 60; hoursInput.addEventListener("change", calculateAndSetTime);
startingValue = Math.floor(startingValue / 60); minutesInput.addEventListener("change", calculateAndSetTime);
const startingHours = (startingValue % 6); secondsInput.addEventListener("change", calculateAndSetTime);
startingValue = Math.floor(startingValue / 6);
const startingDays = (startingValue % 426) + (this.isDate ? 1 : 0);
startingValue = Math.floor(startingValue / 426);
const startingYears = startingValue + (this.isDate ? 1 : 0);
this.yearsInput.setAttribute("value", startingYears.toFixed(0));
this.daysInput.setAttribute("value", startingDays.toFixed(0));
this.hoursInput.setAttribute("value", startingHours.toFixed(0));
this.minutesInput.setAttribute("value", startingMinutes.toFixed(0));
this.secondsInput.setAttribute("value", startingSeconds.toFixed(0));
this.yearsInput.addEventListener("change", this.calculateTime.bind(this));
this.daysInput.addEventListener("change", this.calculateTime.bind(this));
this.hoursInput.addEventListener("change", this.calculateTime.bind(this));
this.minutesInput.addEventListener("change", this.calculateTime.bind(this));
this.secondsInput.addEventListener("change", this.calculateTime.bind(this));
}
addListener(listenerFunction: (newDate: number) => void) {
this.dateListeners.push(listenerFunction);
}
calculateTime() {
let years = parseInt(this.yearsInput.value);
let days = parseInt(this.daysInput.value);
let hours = parseInt(this.hoursInput.value);
let minutes = parseInt(this.minutesInput.value);
let seconds = parseInt(this.secondsInput.value);
if (this.isDate) {
years -= 1;
days -= 1;
}
let calculatedTime = (((years * 426 + days) * 6 + hours) * 60 + minutes) * 60 + seconds;
this.dateListeners.forEach(listener => listener(calculatedTime));
} }
} }

View File

@ -10,14 +10,6 @@ export interface FindBestTransferMessage {
body: Body body: Body
} }
export interface FindBestTransferResponse {
type: "FindBestTransferResponse"
finished: boolean,
percentDone: number,
bestDeltaV: number | null,
bestTransfer: Transfer | null
}
export interface FindBestInterceptMessage { export interface FindBestInterceptMessage {
type: "FindBestIntercept", type: "FindBestIntercept",
startingSituation: OrbitalCoordinates, startingSituation: OrbitalCoordinates,
@ -26,12 +18,20 @@ export interface FindBestInterceptMessage {
body: Body; body: Body;
} }
export interface ProgressMessage {
type: "ProgressMessage"
finished: boolean,
percentDone: number,
bestDeltaV: number | null,
bestTransfer: Transfer | null
}
ctx.addEventListener("message", event => { ctx.addEventListener("message", event => {
const progressCallback = (numberChecked: number, totalNumber: number, bestDeltaV: number | null, bestTransfer: Transfer | null) => { const progressCallback = (numberChecked: number, totalNumber: number, bestDeltaV: number | null, bestTransfer: Transfer | null) => {
if (numberChecked % 100 == 0) { if (numberChecked % 100 == 0) {
let percentDone = numberChecked * 100 / totalNumber; let percentDone = numberChecked * 100 / totalNumber;
let message: FindBestTransferResponse = { let message: ProgressMessage = {
type: "FindBestTransferResponse", type: "ProgressMessage",
finished: false, finished: false,
percentDone: percentDone, percentDone: percentDone,
bestDeltaV: bestDeltaV, bestDeltaV: bestDeltaV,
@ -51,8 +51,8 @@ ctx.addEventListener("message", event => {
bestDeltaV = bestTransfer.firstManoeuvre.totalDeltaV + bestTransfer.secondManoeuvre.totalDeltaV; bestDeltaV = bestTransfer.firstManoeuvre.totalDeltaV + bestTransfer.secondManoeuvre.totalDeltaV;
} }
let finishedMessage: FindBestTransferResponse = { let finishedMessage: ProgressMessage = {
type: "FindBestTransferResponse", type: "ProgressMessage",
finished: true, finished: true,
percentDone: 100, percentDone: 100,
bestDeltaV: bestDeltaV, bestDeltaV: bestDeltaV,
@ -69,8 +69,8 @@ ctx.addEventListener("message", event => {
bestDeltaV = bestIntercept.firstManoeuvre.totalDeltaV + bestIntercept.secondManoeuvre.totalDeltaV; bestDeltaV = bestIntercept.firstManoeuvre.totalDeltaV + bestIntercept.secondManoeuvre.totalDeltaV;
} }
let finishedMessage: FindBestTransferResponse = { let finishedMessage: ProgressMessage = {
type: "FindBestTransferResponse", type: "ProgressMessage",
finished: true, finished: true,
percentDone: 100, percentDone: 100,
bestDeltaV: bestDeltaV, bestDeltaV: bestDeltaV,

View File

@ -1,14 +1,12 @@
import { getPlanetByName, Kerbol, type Body } from "./calculations/constants"; import { getPlanetByName, Kerbol } from "./calculations/constants";
import { DefaultOrbitalParameters } from "./gui/common"; import { createLabel, createRadioButton, decodeOrbitalParameters, DefaultOrbitalParameters, encodeOrbitalParameters } from "./gui/common";
import { InterceptTargetGui } from "./gui/intercept";
import { OrbitalParametersGui } from "./gui/orbit"; import { OrbitalParametersGui } from "./gui/orbit";
import { decodeOrbitalParameters } from "./gui/common";
import { encodeOrbitalParameters } from "./gui/common";
import { PlanetGui } from "./gui/planet"; import { PlanetGui } from "./gui/planet";
import { TimeGui } from "./gui/time";
import { createLocalStorageVariable } from "./storage";
import { SimplePlaneChangeGui } from "./gui/simpleplanechange"; import { SimplePlaneChangeGui } from "./gui/simpleplanechange";
import { TargetOrbitGui } from "./gui/targetorbit"; import { TargetOrbitGui } from "./gui/targetorbit";
import { InterceptTargetGui } from "./gui/intercept"; import { TimeGui } from "./gui/time";
import { ChangingStorageValue } from "./storage";
type CalculationType = "planeChange" | "targetOrbit" | "intercept"; type CalculationType = "planeChange" | "targetOrbit" | "intercept";
function decodeCalculationType(input: string): CalculationType { function decodeCalculationType(input: string): CalculationType {
@ -20,136 +18,59 @@ function decodeCalculationType(input: string): CalculationType {
return "planeChange"; return "planeChange";
} }
let [currentTime, setCurrentTime] = createLocalStorageVariable("currentTime", parseInt, n => n.toFixed(0), 0); let currentTime = new ChangingStorageValue(0, "currentTime", parseInt, n => n.toString());
let [timeToPeriapsis, setTimeToPeriapsis] = createLocalStorageVariable("timeToPeriapsis", parseInt, n => n.toFixed(0), 0); let body = new ChangingStorageValue(Kerbol, "planet", s => getPlanetByName(s), p => p.planetName);
let [planet, setPlanet] = createLocalStorageVariable("planet", getPlanetByName, p => p.planetName, Kerbol); let orbitalParameters = new ChangingStorageValue(DefaultOrbitalParameters, "orbitalParameters", decodeOrbitalParameters, encodeOrbitalParameters);
let [orbitalParameters, setOrbitalParameters] = createLocalStorageVariable("orbitalParameters", decodeOrbitalParameters, encodeOrbitalParameters, DefaultOrbitalParameters); let calculationType = new ChangingStorageValue("planeChange", "calculationType", decodeCalculationType, s => s);
let [calculationType, setCalculationType] = createLocalStorageVariable("calculationType", decodeCalculationType, s => s, "planeChange"); let targetOrbitalParameters = new ChangingStorageValue(DefaultOrbitalParameters, "targetOrbitalParameters", decodeOrbitalParameters, encodeOrbitalParameters);
let [targetOrbitalParameters, setTargetOrbitalParameters] = createLocalStorageVariable("targetOrbitalParameters", decodeOrbitalParameters, encodeOrbitalParameters, DefaultOrbitalParameters); let circularizeOrbit = new ChangingStorageValue(false, "circularizeOrbit", s => s == "true", b => b ? "true" : "false");
let [circularizeOrbit, setCircularizeOrbit] = createLocalStorageVariable("circularizeOrbit", s => s == "true", bool => bool ? "true" : "false", false) let additionalInterceptTrueanomaly = new ChangingStorageValue(0, "additionalTrueAnomaly", parseFloat, s => s.toString());
let [targetTimeToPeriapsis, setTargetTimeToPeriapsis] = createLocalStorageVariable("targetTimeToPeriapsis", parseInt, n => n.toFixed(0), 0);
let [additionalTrueAnomaly, setAdditionalTrueanomaly] = createLocalStorageVariable("additionalTrueAnomaly", parseFloat, n => n.toString(), 0);
let dateInputContainerCreator = () => { let dateGui = new TimeGui(currentTime, true);
let dateInputContainer = document.createElement("span"); let currentTimeDiv = document.getElementById("currentTimeDiv");
dateInputContainer.classList.add("dateInputContainer"); currentTimeDiv?.appendChild(dateGui.parentDiv);
return dateInputContainer;
}
let planetaryBodyContainerCreator = (body: Body) => { let planetGui = new PlanetGui(body);
let bodyContainer = document.createElement("div"); let planetsDiv = document.getElementById("planetsDiv");
bodyContainer.classList.add(body.type); planetsDiv?.appendChild(planetGui.parentDiv);
return bodyContainer;
};
let orbitalParameterContainerCreator = () => { let orbitalParametersGui = new OrbitalParametersGui(orbitalParameters);
let orbitalParameterContainer = document.createElement("div"); let orbitalParametersDiv = document.getElementById("orbitalParametersDiv");
orbitalParameterContainer.classList.add("orbitalParameter"); orbitalParametersDiv?.appendChild(orbitalParametersGui.parentDiv);
return orbitalParameterContainer;
}
let timesDiv = document.getElementById("timesDiv") as HTMLElement; let simplePlaneChangeGui = new SimplePlaneChangeGui(currentTime, body, orbitalParameters, targetOrbitalParameters, circularizeOrbit);
let dateGui = new TimeGui(timesDiv, true, currentTime.value, dateInputContainerCreator); let targetOrbitGui = new TargetOrbitGui(currentTime, body, orbitalParameters, targetOrbitalParameters);
dateGui.addListener(setCurrentTime); let interceptTargetGui = new InterceptTargetGui(currentTime, body, orbitalParameters, targetOrbitalParameters, additionalInterceptTrueanomaly);
let periapsisDiv = document.getElementById("periapsisDiv") as HTMLElement; let calculationChoiceDiv = document.getElementById("calculationChoice");
let periapsisGui = new TimeGui(periapsisDiv, false, timeToPeriapsis.value, dateInputContainerCreator);
periapsisGui.addListener(setTimeToPeriapsis);
let planetsDiv = document.getElementById("planetsDiv") as HTMLElement; let simplePlaneChangeButton = createRadioButton("calculationChoice", "simplePlaneChange");
let planetsGui = new PlanetGui(planetsDiv, planet.value, planetaryBodyContainerCreator); simplePlaneChangeButton.addEventListener("change", () => calculationType.set("planeChange", "main"));
planetsGui.addListener(setPlanet); let simplePlaneChangeLabel = createLabel("simplePlaneChange", "Simple plane change");
[simplePlaneChangeButton, simplePlaneChangeLabel].forEach(child => calculationChoiceDiv?.appendChild(child));
let orbitalParametersDiv = document.getElementById("orbitalParametersDiv") as HTMLElement; let targetOrbitButton = createRadioButton("calculationChoice", "targetOrbit");
let orbitalParametersGui = new OrbitalParametersGui(orbitalParametersDiv, orbitalParameters.value, orbitalParameterContainerCreator); targetOrbitButton.addEventListener("change", () => calculationType.set("targetOrbit", "main"));
orbitalParametersGui.addListener(setOrbitalParameters); let targetOrbitLabel = createLabel("targetOrbit", "Target orbit");
[targetOrbitButton, targetOrbitLabel].forEach(child => calculationChoiceDiv?.appendChild(child));
let choiceDiv = document.getElementById("calculationChoice") as HTMLElement; let interceptTargetButton = createRadioButton("calculationChoice", "interceptTarget");
let calculationDiv = document.getElementById("calculation") as HTMLElement; interceptTargetButton.addEventListener("change", () => calculationType.set("intercept", "main"));
let interceptTargetLabel = createLabel("interceptTarget", "Intercept target");
[interceptTargetButton, interceptTargetLabel].forEach(child => calculationChoiceDiv?.appendChild(child));
const simplePlaneChangeGui = new SimplePlaneChangeGui(orbitalParameters, targetOrbitalParameters, currentTime, timeToPeriapsis, planet, circularizeOrbit.value); let calculationsDiv = document.getElementById("calculation") as HTMLDivElement;
simplePlaneChangeGui.addListener((targetInclination, targetLongitudeOfAscendingNode, circularizeOrbit) => { calculationType.listenToValue((_, value) => {
setCircularizeOrbit(circularizeOrbit); calculationsDiv.innerHTML = "";
if (value == "planeChange") {
let newTargetParameters = structuredClone(targetOrbitalParameters.value); simplePlaneChangeButton.setAttribute("checked", "");
newTargetParameters.inclination = targetInclination; calculationsDiv.appendChild(simplePlaneChangeGui.parentDiv);
newTargetParameters.longitudeOfAscendingNode = targetLongitudeOfAscendingNode; } else if (value == "targetOrbit") {
targetOrbitButton.setAttribute("checked", "");
setTargetOrbitalParameters(newTargetParameters); calculationsDiv.appendChild(targetOrbitGui.parentDiv);
}); } else if (value == "intercept") {
interceptTargetButton.setAttribute("checked", "");
const targetOrbitGui = new TargetOrbitGui(orbitalParameters, targetOrbitalParameters, currentTime, timeToPeriapsis, planet); calculationsDiv.appendChild(interceptTargetGui.parentDiv);
targetOrbitGui.addListener(setTargetOrbitalParameters);
const interceptTargetGui: InterceptTargetGui = new InterceptTargetGui(orbitalParameters, targetOrbitalParameters, currentTime, timeToPeriapsis, planet, targetTimeToPeriapsis.value, additionalTrueAnomaly.value);
interceptTargetGui.addListener((newTargetParameters, newTargetTimeToPeriapsis, newAdditionalTrueAnomaly) => {
setTargetOrbitalParameters(newTargetParameters);
setTargetTimeToPeriapsis(newTargetTimeToPeriapsis);
setAdditionalTrueanomaly(newAdditionalTrueAnomaly);
});
function populateCalculationDiv() {
calculationDiv.innerHTML = "";
calculationDiv.className = "";
if (calculationType.value == "planeChange") {
calculationDiv.classList.add("simplePlaneChange");
simplePlaneChangeGui.addToParentElement(calculationDiv);
} else if (calculationType.value == "targetOrbit") {
calculationDiv.classList.add("targetOrbit");
targetOrbitGui.addToParentElement(calculationDiv);
} else if (calculationType.value == "intercept") {
calculationDiv.classList.add("intercept");
interceptTargetGui.addToParentElement(calculationDiv);
} }
}
// Run this when creating the document
populateCalculationDiv();
// Create the choices
const createRadioButton = (name: string, id: string, value: string) => {
let radioButton = document.createElement("input");
radioButton.setAttribute("type", "radio");
radioButton.setAttribute("id", id);
radioButton.setAttribute("name", name);
radioButton.setAttribute("value", value);
let label = document.createElement("label");
label.setAttribute("for", id);
label.appendChild(document.createTextNode(value));
return [radioButton, label];
}
let [simplePlaneChangeButton, planeChangeLabel] = createRadioButton("calculationType", "spc", "Simple Plane Change");
choiceDiv.appendChild(simplePlaneChangeButton);
choiceDiv.appendChild(planeChangeLabel);
if (calculationType.value === "planeChange") {
simplePlaneChangeButton.setAttribute("checked", "");
}
simplePlaneChangeButton.addEventListener("change", () => {
setCalculationType("planeChange");
populateCalculationDiv();
}); });
let [targetOrbitButton, targetOrbitLabel] = createRadioButton("calculationType", "to", "Target Orbit");
choiceDiv.appendChild(targetOrbitButton);
choiceDiv.appendChild(targetOrbitLabel);
if (calculationType.value == "targetOrbit") {
targetOrbitButton.setAttribute("checked", "");
}
targetOrbitButton.addEventListener("change", () => {
setCalculationType("targetOrbit");
populateCalculationDiv();
});
let [interceptTargetButton, interceptTargetLabel] = createRadioButton("calculationType", "it", "Intercept target");
choiceDiv.appendChild(interceptTargetButton);
choiceDiv.appendChild(interceptTargetLabel);
if (calculationType.value == "intercept") {
interceptTargetButton.setAttribute("checked", "");
}
interceptTargetButton.addEventListener("change", () => {
setCalculationType("intercept");
populateCalculationDiv();
})

View File

@ -1,24 +1,43 @@
export interface Wrapper<Type> { export class ChangingStorageValue<Type> {
value: Type; value: Type;
} changeListeners: ((changeSource: string, value: Type) => void)[];
export function createLocalStorageVariable<Type>(variableName: string, decoder: (a: string) => Type, encoder: (a: Type) => string, defaultValue: Type): [Wrapper<Type>, (value: Type) => void] { variableName: string | null;
var localStorageString = localStorage.getItem(variableName); encoder: ((value: Type) => string) | null;
var variable: Wrapper<Type>;
if (localStorageString) { constructor(defaultValue: Type, variableName?: string, decoder?: (a: string) => Type, encoder?: (a: Type) => string) {
variable = { this.changeListeners = [];
value: decoder(localStorageString) this.value = defaultValue;
};
} else { if (variableName && decoder && encoder) {
variable = { this.variableName = variableName;
value: defaultValue this.encoder = encoder;
}; let localStorageString = localStorage.getItem(variableName);
if (localStorageString) {
this.value = decoder(localStorageString);
}
} else {
this.variableName = null;
this.encoder = null;
}
} }
const setterFunction = (value: Type) => { set(newValue: Type, source: string) {
variable.value = value; this.value = newValue;
localStorage.setItem(variableName, encoder(value)); this.changeListeners.forEach(listener => listener(source, this.value));
if (this.variableName != null && this.encoder != null) {
localStorage.setItem(this.variableName, this.encoder(newValue));
}
} }
return [variable, setterFunction]; listenToValue(listener: (changeSource: string, value: Type) => void): void {
this.changeListeners.push(listener);
listener("null", this.value);
}
getCurrentValue(): Type {
return this.value;
}
} }

View File

@ -1,10 +1,10 @@
.dateInputContainer { .timeInput {
display: inline-block; display: inline-block;
min-width: 120px; min-width: 120px;
padding-right: 10px; padding-right: 10px;
} }
.dateInputContainer label { .timeInput label {
padding-right: 5px; padding-right: 5px;
} }
@ -29,22 +29,8 @@
width: 150px; width: 150px;
} }
.simplePlaneChange label { #calculationChoice label {
display: inline-block; margin-right: 30px;
width: 300px;
}
.simplePlaneChange input[type=number] {
width: 150px;
}
.targetOrbit label {
display: inline-block;
width: 300px;
}
.targetOrbit input[type=number] {
width: 150px;
} }
.flexContainer { .flexContainer {