Stuff is still working
This commit is contained in:
parent
aa45755cfc
commit
2b4fb65d4f
@ -9,11 +9,8 @@
|
||||
</head>
|
||||
<body>
|
||||
<h1>Kerbal calculations</h1>
|
||||
<h3>Current time in game:</h3>
|
||||
<div id="timesDiv"></div>
|
||||
<h3>Time to periapsis:</h3>
|
||||
<div id="periapsisDiv"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
<h3>Earliest allowed manoeuvre time:</h3>
|
||||
<div id="currentTimeDiv"></div>
|
||||
<h3>Planet:</h3>
|
||||
<div id="planetsDiv"></div>
|
||||
<h3>Orbital parameters:</h3>
|
||||
@ -21,5 +18,7 @@
|
||||
<h3>What to calculate:</h3>
|
||||
<div id="calculationChoice"></div>
|
||||
<div id="calculation"></div>
|
||||
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -18,6 +18,7 @@ export interface OrbitalCoordinates {
|
||||
meanAnomaly: number,
|
||||
eccentricAnomaly: number,
|
||||
trueAnomaly: number,
|
||||
meanAngularMotion: number,
|
||||
position: number[][]
|
||||
}
|
||||
|
||||
@ -283,12 +284,11 @@ export function getOrbitFromEccentricity(periapsis: number, eccentricity: number
|
||||
}
|
||||
};
|
||||
|
||||
export function getOrbitalCoordinates(timeToPeriapsis: number, orbit: Orbit, planet: Body) {
|
||||
const meanAnomaly = -Math.sqrt(planet.gravitationalParameter / Math.abs(orbit.semiLatusRectum / (orbit.eccentricity**2 - 1))**3) * timeToPeriapsis;
|
||||
export function getEccentricAndTrueAnomalyFromMeanAnomaly(meanAnomaly: number, eccentricity: number): [number, number] {
|
||||
var eccentricAnomaly;
|
||||
var trueAnomaly;
|
||||
|
||||
if (Math.abs(orbit.eccentricity - 1) < 0.0001) {
|
||||
if (Math.abs(eccentricity - 1) < 0.0001) {
|
||||
// Parabolic trajectory, Barker's equation
|
||||
const A = 3 * meanAnomaly / Math.sqrt(8);
|
||||
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;
|
||||
eccentricAnomaly = meanAnomaly;
|
||||
|
||||
if (orbit.eccentricity < 1) {
|
||||
keplerEquation = (guess: number) => guess - orbit.eccentricity * Math.sin(guess) - meanAnomaly;
|
||||
keplerEquationDerivative = (guess: number) => 1 - orbit.eccentricity * Math.cos(guess);
|
||||
if (eccentricity < 1) {
|
||||
keplerEquation = (guess: number) => guess - eccentricity * Math.sin(guess) - meanAnomaly;
|
||||
keplerEquationDerivative = (guess: number) => 1 - eccentricity * Math.cos(guess);
|
||||
} else {
|
||||
keplerEquation = (guess: number) => orbit.eccentricity * Math.sinh(guess) - guess - meanAnomaly;
|
||||
keplerEquationDerivative = (guess: number) => orbit.eccentricity * Math.cosh(guess) - 1;
|
||||
keplerEquation = (guess: number) => eccentricity * Math.sinh(guess) - guess - meanAnomaly;
|
||||
keplerEquationDerivative = (guess: number) => eccentricity * Math.cosh(guess) - 1;
|
||||
}
|
||||
|
||||
while (Math.abs(keplerEquation(eccentricAnomaly)) > 0.000001) {
|
||||
eccentricAnomaly = eccentricAnomaly - keplerEquation(eccentricAnomaly) / keplerEquationDerivative(eccentricAnomaly);
|
||||
}
|
||||
|
||||
if (orbit.eccentricity < 1) {
|
||||
trueAnomaly = 2*Math.atan(Math.sqrt((1 + orbit.eccentricity) / (1 - orbit.eccentricity)) * Math.tan(eccentricAnomaly / 2));
|
||||
if (eccentricity < 1) {
|
||||
trueAnomaly = 2*Math.atan(Math.sqrt((1 + eccentricity) / (1 - eccentricity)) * Math.tan(eccentricAnomaly / 2));
|
||||
} 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 localX = radius * Math.cos(trueAnomaly);
|
||||
const localY = radius * Math.sin(trueAnomaly);
|
||||
@ -329,10 +338,64 @@ export function getOrbitalCoordinates(timeToPeriapsis: number, orbit: Orbit, pla
|
||||
meanAnomaly: meanAnomaly,
|
||||
eccentricAnomaly: eccentricAnomaly,
|
||||
trueAnomaly: trueAnomaly,
|
||||
meanAngularMotion: meanAngularMotion,
|
||||
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 {
|
||||
let extraTime = 0;
|
||||
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 {
|
||||
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;
|
||||
@ -554,8 +646,8 @@ export function findCheapestTransfer(startingSituation: OrbitalCoordinates, targ
|
||||
}
|
||||
} else {
|
||||
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;
|
||||
for (var i = 1; i < 101; i++) {
|
||||
startingTrueAnomalies.push(startingSituation.trueAnomaly + i * step);
|
||||
}
|
||||
}
|
||||
@ -710,17 +802,42 @@ export function findCheapestIntercept(startingSituation: OrbitalCoordinates, tar
|
||||
}
|
||||
}
|
||||
|
||||
let maxStartTime = 1e99;
|
||||
let maxStartTrueAnomaly = 1e99;
|
||||
let maxEndTime = 1e99;
|
||||
let maxEndTrueAnomaly = 1e99;
|
||||
|
||||
if (interceptOrbitStable && startingOrbitStable) {
|
||||
// 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 targetOrbitPeriod = getTimeBetweenTrueAnomalies(0, 2*Math.PI, targetSituation.orbit, body);
|
||||
|
||||
let maxStartTime = 3 * Math.max(startingOrbitPeriod, targetOrbitPeriod);
|
||||
let maxEndTime = 4 * Math.max(startingOrbitPeriod, targetOrbitPeriod);
|
||||
maxStartTime = 3 * Math.max(startingOrbitPeriod, targetOrbitPeriod);
|
||||
maxEndTime = 4 * Math.max(startingOrbitPeriod, targetOrbitPeriod);
|
||||
} else {
|
||||
maxStartTime = 1e99;
|
||||
maxStartTrueAnomaly = 1e99;
|
||||
maxEndTime = 1e99;
|
||||
maxEndTrueAnomaly = 1e99;
|
||||
|
||||
let anomalyCounter = 0;
|
||||
if (!startingOrbitStable) {
|
||||
maxStartTrueAnomaly = Math.abs(Math.acos((startingSituation.orbit.semiLatusRectum - body.sphereOfInfluence) / (body.sphereOfInfluence * startingSituation.orbit.eccentricity)));
|
||||
maxStartTime = getTimeBetweenTrueAnomalies(startingSituation.trueAnomaly, maxStartTrueAnomaly, startingSituation.orbit, body);
|
||||
maxEndTime = 2*maxStartTime;
|
||||
}
|
||||
|
||||
if (!interceptOrbitStable) {
|
||||
maxEndTrueAnomaly = Math.abs(Math.acos((targetSituation.orbit.semiLatusRectum - body.sphereOfInfluence) / (body.sphereOfInfluence * targetSituation.orbit.eccentricity)));
|
||||
maxEndTime = getTimeBetweenTrueAnomalies(targetSituation.trueAnomaly, maxEndTrueAnomaly, targetSituation.orbit, body);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
@ -733,6 +850,9 @@ export function findCheapestIntercept(startingSituation: OrbitalCoordinates, tar
|
||||
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;
|
||||
|
||||
@ -743,7 +863,6 @@ export function findCheapestIntercept(startingSituation: OrbitalCoordinates, tar
|
||||
intercepts.push([possibleEndTime, possibleEnd]);
|
||||
anomalyCounter += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let pairs: [number, number, number, number][] = [];
|
||||
starts.forEach(([startingTime, startingTrueAnomaly]) => {
|
||||
|
||||
61
src/gui/altitude.ts
Normal file
61
src/gui/altitude.ts
Normal 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));
|
||||
}
|
||||
}
|
||||
@ -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 {
|
||||
periapsis: number;
|
||||
@ -8,6 +9,11 @@ export interface OrbitalParameters {
|
||||
inclination: number;
|
||||
longitudeOfAscendingNode: number;
|
||||
argumentOfPeriapsis: number;
|
||||
|
||||
positionChoice: "timeToPeriapsis" | "altitude";
|
||||
timeToPeriapsis: number,
|
||||
altitude: number,
|
||||
headingInwards: boolean
|
||||
}
|
||||
|
||||
export const DefaultOrbitalParameters: OrbitalParameters = {
|
||||
@ -17,7 +23,11 @@ export const DefaultOrbitalParameters: OrbitalParameters = {
|
||||
eccentricity: 0,
|
||||
inclination: 0,
|
||||
longitudeOfAscendingNode: 0,
|
||||
argumentOfPeriapsis: 0
|
||||
argumentOfPeriapsis: 0,
|
||||
positionChoice: "timeToPeriapsis",
|
||||
timeToPeriapsis: 0,
|
||||
altitude: 100000,
|
||||
headingInwards: true
|
||||
}
|
||||
|
||||
export function encodeOrbitalParameters(orbitalParameters: OrbitalParameters): string {
|
||||
@ -85,3 +95,21 @@ export function getOrbitFromParameters(orbitalParameters: OrbitalParameters, pla
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@ -1,181 +1,63 @@
|
||||
import { getOrbitalCoordinates } from "../calculations/orbit-calculations";
|
||||
import type { Wrapper } from "../storage";
|
||||
import { createDisabledInput, createLabel, createNumberInput, getOrbitFromParameters, type OrbitalParameters } from "./common";
|
||||
import { createLabel, createNumberInput, getCoordinatesFromParameters, type OrbitalParameters } from "./common";
|
||||
import { OrbitalParametersGui } from "./orbit";
|
||||
import { type Body } from "../calculations/constants";
|
||||
import type { FindBestInterceptMessage, FindBestTransferResponse } from "./worker";
|
||||
import { TimeGui } from "./time";
|
||||
import type { FindBestInterceptMessage, ProgressMessage } from "./worker";
|
||||
import type { ChangingStorageValue } from "../storage";
|
||||
import { ManoeuvresGui } from "./manoeuvres";
|
||||
|
||||
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;
|
||||
orbitGui: OrbitalParametersGui;
|
||||
manoeuvresGui: ManoeuvresGui;
|
||||
|
||||
progressParagraph: HTMLParagraphElement;
|
||||
|
||||
firstManoeuvreTime: HTMLInputElement;
|
||||
firstManoeuvrePrograde: HTMLInputElement;
|
||||
firstManoeuvreNormal: HTMLInputElement;
|
||||
firstManoeuvreRadial: HTMLInputElement;
|
||||
firstManoeuvreTotal: HTMLInputElement;
|
||||
|
||||
secondManoeuvreTime: HTMLInputElement;
|
||||
secondManoeuvrePrograde: HTMLInputElement;
|
||||
secondManoeuvreNormal: HTMLInputElement;
|
||||
secondManoeuvreRadial: HTMLInputElement;
|
||||
secondManoeuvreTotal: HTMLInputElement;
|
||||
sourceId: string;
|
||||
|
||||
worker: Worker | null;
|
||||
|
||||
constructor(startingParameters: Wrapper<OrbitalParameters>, goalParameters: Wrapper<OrbitalParameters>, currentTime: Wrapper<number>, timeToPeriapsis: Wrapper<number>, body: Wrapper<Body>, defaultTargetTimeToPeriapsis?: number, defaultAdditionalTrueAnomaly?: number) {
|
||||
this.listeners = [];
|
||||
|
||||
this.startingParameters = startingParameters;
|
||||
this.goalParameters = goalParameters;
|
||||
constructor(currentTime: ChangingStorageValue<number>, body: ChangingStorageValue<Body>, startingOrbitalParameters: ChangingStorageValue<OrbitalParameters>, targetOrbitalParameters: ChangingStorageValue<OrbitalParameters>, additionalTrueAnomaly: ChangingStorageValue<number>) {
|
||||
this.currentTime = currentTime;
|
||||
this.timeToPeriapsis = timeToPeriapsis;
|
||||
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");
|
||||
let parametersHeader = document.createElement("h3");
|
||||
parametersHeader.appendChild(document.createTextNode("Target orbit:"));
|
||||
this.parentDiv.appendChild(parametersHeader);
|
||||
|
||||
let orbitalParameterContainerCreator = () => {
|
||||
let orbitalParameterContainer = document.createElement("div");
|
||||
orbitalParameterContainer.classList.add("orbitalParameter");
|
||||
return orbitalParameterContainer;
|
||||
}
|
||||
this.parentDiv.appendChild(this.orbitGui.parentDiv);
|
||||
|
||||
this.orbitGui = new OrbitalParametersGui(this.parentDiv, goalParameters.value, orbitalParameterContainerCreator);
|
||||
|
||||
let targetTimeToPeriapsisHeader = document.createElement("h3");
|
||||
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 additionalTrueAnomalyContainer = document.createElement("div");
|
||||
additionalTrueAnomalyContainer.classList.add("orbitalParameter");
|
||||
this.parentDiv.appendChild(additionalTrueAnomalyContainer);
|
||||
|
||||
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);
|
||||
[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");
|
||||
searchButton.appendChild(document.createTextNode("Search for cheapest intercept"));
|
||||
this.parentDiv.appendChild(searchButton);
|
||||
|
||||
let searchHeader = document.createElement("h3");
|
||||
searchHeader.appendChild(document.createTextNode("Manoeuvre search"));
|
||||
this.parentDiv.appendChild(searchHeader);
|
||||
let bestInterceptHeader = document.createElement("h3");
|
||||
bestInterceptHeader.appendChild(document.createTextNode("Best intercept:"));
|
||||
this.parentDiv.appendChild(bestInterceptHeader);
|
||||
|
||||
this.progressParagraph = document.createElement("p");
|
||||
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;
|
||||
this.parentDiv.appendChild(this.manoeuvresGui.parentDiv);
|
||||
|
||||
searchButton.addEventListener("click", () => {
|
||||
if (this.worker !== null) {
|
||||
@ -185,86 +67,58 @@ export class InterceptTargetGui {
|
||||
} else {
|
||||
searchButton.innerHTML = "Cancel search";
|
||||
|
||||
let startingOrbit = getOrbitFromParameters(startingParameters.value, this.body.value.radius);
|
||||
let endingOrbit = getOrbitFromParameters(goalParameters.value, this.body.value.radius);
|
||||
let currentCoordinates = getOrbitalCoordinates(timeToPeriapsis.value, startingOrbit, this.body.value);
|
||||
let targetCoordinates = getOrbitalCoordinates(this.targetTimeToPeriapsis, endingOrbit, this.body.value);
|
||||
let currentTime = this.currentTime.getCurrentValue();
|
||||
let body = this.body.getCurrentValue();
|
||||
let startingOrbitalParameters = this.startingOrbitalParameters.getCurrentValue();
|
||||
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.addEventListener("message", event => {
|
||||
let transferResponse = event.data as FindBestTransferResponse;
|
||||
let transferResponse = event.data as ProgressMessage;
|
||||
if (transferResponse) {
|
||||
if (transferResponse.finished) {
|
||||
searchButton.innerHTML = "Search for cheapest intercept";
|
||||
this.worker = null;
|
||||
}
|
||||
|
||||
this.progressParagraph.innerHTML = "";
|
||||
this.progressParagraph.appendChild(document.createTextNode(`Search is ${transferResponse.percentDone.toFixed(2)}% finished`));
|
||||
if (transferResponse.bestDeltaV !== null && transferResponse.bestTransfer != null) {
|
||||
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);
|
||||
if (transferResponse.bestTransfer) {
|
||||
transferResponse.bestTransfer.firstManoeuvre.time += currentTime;
|
||||
transferResponse.bestTransfer.secondManoeuvre.time += currentTime;
|
||||
}
|
||||
|
||||
this.manoeuvresGui.displayProgress(transferResponse);
|
||||
}
|
||||
});
|
||||
|
||||
let workerMessage: FindBestInterceptMessage = {
|
||||
type: "FindBestIntercept",
|
||||
startingSituation: currentCoordinates,
|
||||
startingSituation: startingCoordinates,
|
||||
targetSituation: targetCoordinates,
|
||||
body: body.value,
|
||||
additionalTrueAnomaly: this.additionalTrueAnomaly
|
||||
body: body,
|
||||
additionalTrueAnomaly: additionalTrueAnomaly
|
||||
};
|
||||
|
||||
this.worker.postMessage(workerMessage);
|
||||
}
|
||||
});
|
||||
|
||||
// Add some listeners
|
||||
this.orbitGui.addListener((newValue) => {
|
||||
this.listeners.forEach(listener => {
|
||||
listener(newValue, this.targetTimeToPeriapsis, this.additionalTrueAnomaly);
|
||||
})
|
||||
});
|
||||
this.sourceId = crypto.randomUUID();
|
||||
this.additionalTrueAnomaly.listenToValue((source, value) => {
|
||||
if (source == this.sourceId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.timeGui.addListener((newDate: number) => {
|
||||
this.targetTimeToPeriapsis = newDate;
|
||||
this.listeners.forEach(listener => {
|
||||
listener(goalParameters.value, newDate, this.additionalTrueAnomaly);
|
||||
})
|
||||
additionalTrueAnomalyInput.value = (value * 180 / Math.PI).toString();
|
||||
});
|
||||
|
||||
additionalTrueAnomalyInput.addEventListener("change", () => {
|
||||
this.additionalTrueAnomaly = parseFloat(additionalTrueAnomalyInput.value) * Math.PI / 180.0;
|
||||
this.listeners.forEach(listener => {
|
||||
listener(goalParameters.value, this.targetTimeToPeriapsis, this.additionalTrueAnomaly);
|
||||
})
|
||||
let additionalTrueAnomaly = parseFloat(additionalTrueAnomalyInput.value) * Math.PI / 180.0;
|
||||
this.additionalTrueAnomaly.set(additionalTrueAnomaly, this.sourceId);
|
||||
});
|
||||
}
|
||||
|
||||
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
117
src/gui/manoeuvres.ts
Normal 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) ?? "";
|
||||
};
|
||||
}
|
||||
}
|
||||
287
src/gui/orbit.ts
287
src/gui/orbit.ts
@ -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 {
|
||||
listeners: ((newValue: OrbitalParameters) => void)[];
|
||||
orbitalParameters: OrbitalParameters;
|
||||
parentDiv: HTMLDivElement;
|
||||
orbitalParameters: ChangingStorageValue<OrbitalParameters>;
|
||||
|
||||
periapsisInput: HTMLInputElement;
|
||||
apoapsisChoiceButton: HTMLInputElement;
|
||||
eccentricityChoiceButton: HTMLInputElement;
|
||||
apoapsisInput: HTMLInputElement;
|
||||
eccentricityInput: HTMLInputElement;
|
||||
inclinationInput: HTMLInputElement;
|
||||
lanInput: HTMLInputElement;
|
||||
aopInput: HTMLInputElement;
|
||||
timeToPeriapsis: ChangingStorageValue<number>;
|
||||
timeGui: TimeGui;
|
||||
|
||||
constructor(htmlContainer: HTMLElement, startingValue?: OrbitalParameters, containerCreator?: () => HTMLElement) {
|
||||
this.listeners = [];
|
||||
altitudeData: ChangingStorageValue<AltitudeData>;
|
||||
altitudeGui: AltitudeGui;
|
||||
|
||||
if (startingValue) {
|
||||
this.orbitalParameters = structuredClone(startingValue);
|
||||
} else {
|
||||
this.orbitalParameters = structuredClone(DefaultOrbitalParameters);
|
||||
sourceId: string;
|
||||
|
||||
constructor(orbitalParameters: ChangingStorageValue<OrbitalParameters>, guiType?: GuiType) {
|
||||
this.orbitalParameters = orbitalParameters;
|
||||
|
||||
this.timeToPeriapsis = new ChangingStorageValue(orbitalParameters.getCurrentValue().timeToPeriapsis);
|
||||
this.timeGui = new TimeGui(this.timeToPeriapsis, false);
|
||||
|
||||
this.altitudeData = new ChangingStorageValue({altitude: orbitalParameters.getCurrentValue().altitude, headingInwards: orbitalParameters.getCurrentValue().headingInwards});
|
||||
this.altitudeGui = new AltitudeGui(this.altitudeData);
|
||||
|
||||
this.parentDiv = document.createElement("div");
|
||||
|
||||
if (!guiType) {
|
||||
guiType = "orbitWithPosition";
|
||||
}
|
||||
|
||||
const addToParent = (toWrap: HTMLElement | HTMLElement[]) => {
|
||||
const addToParent = (toWrap: HTMLElement | HTMLElement[], parent: HTMLElement) => {
|
||||
var children = [];
|
||||
if (toWrap instanceof HTMLElement) {
|
||||
children = [toWrap];
|
||||
} else {
|
||||
children = toWrap;
|
||||
}
|
||||
|
||||
let containerToAppendTo = htmlContainer;
|
||||
if (containerCreator !== undefined) {
|
||||
let childContainer = containerCreator();
|
||||
containerToAppendTo.appendChild(childContainer);
|
||||
containerToAppendTo = childContainer;
|
||||
}
|
||||
|
||||
children.forEach(child => containerToAppendTo.appendChild(child));
|
||||
let containerDiv = document.createElement("div");
|
||||
containerDiv.classList.add("orbitalParameter");
|
||||
children.forEach(child => containerDiv.appendChild(child));
|
||||
parent.appendChild(containerDiv);
|
||||
};
|
||||
|
||||
let periapsisId = crypto.randomUUID();
|
||||
this.periapsisInput = createNumberInput(periapsisId, 0);
|
||||
|
||||
this.periapsisInput.addEventListener("change", () => {
|
||||
let newValue = parseFloat(this.periapsisInput.value);
|
||||
this.orbitalParameters.periapsis = newValue;
|
||||
this.informListeners(this.orbitalParameters);
|
||||
});
|
||||
|
||||
addToParent([createLabel(periapsisId, "Periapsis:"), this.periapsisInput]);
|
||||
let periapsisInput = createNumberInput(periapsisId, 0);
|
||||
if (guiType == "orbit" || guiType == "orbitWithPosition") {
|
||||
addToParent([createLabel(periapsisId, "Periapsis:"), periapsisInput], this.parentDiv);
|
||||
}
|
||||
|
||||
let choiceId = crypto.randomUUID();
|
||||
let apoapsisChoiceId = crypto.randomUUID();
|
||||
let eccentricityChoiceId = crypto.randomUUID();
|
||||
|
||||
this.apoapsisChoiceButton = document.createElement("input");
|
||||
this.apoapsisChoiceButton.setAttribute("type", "radio");
|
||||
this.apoapsisChoiceButton.setAttribute("name", choiceId);
|
||||
this.apoapsisChoiceButton.setAttribute("id", apoapsisChoiceId);
|
||||
this.apoapsisChoiceButton.setAttribute("value", "apoapsis");
|
||||
if (this.orbitalParameters.eccentricChoice === "apoapsis") {
|
||||
this.apoapsisChoiceButton.setAttribute("checked", "");
|
||||
let apoapsisChoiceButton = createRadioButton(choiceId, apoapsisChoiceId);
|
||||
let eccentricityChoiceButton = createRadioButton(choiceId, eccentricityChoiceId);
|
||||
|
||||
if (guiType == "orbit" || guiType == "orbitWithPosition") {
|
||||
addToParent([apoapsisChoiceButton, createLabel(apoapsisChoiceId, "Use apoapsis"), eccentricityChoiceButton, createLabel(eccentricityChoiceId, "Use eccentricity")], this.parentDiv);
|
||||
}
|
||||
|
||||
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();
|
||||
this.apoapsisInput = createNumberInput(apoapsisId, 0);
|
||||
if (this.orbitalParameters.eccentricChoice !== "apoapsis") {
|
||||
this.apoapsisInput.setAttribute("disabled", "true");
|
||||
let apoapsisInput = createNumberInput(apoapsisId, 0);
|
||||
if (guiType == "orbit" || guiType == "orbitWithPosition") {
|
||||
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();
|
||||
this.eccentricityInput = createNumberInput(eccentricityId, 0);
|
||||
if (this.orbitalParameters.eccentricChoice !== "eccentricity") {
|
||||
this.eccentricityInput.setAttribute("disabled", "true");
|
||||
let eccentricityInput = createNumberInput(eccentricityId, 0);
|
||||
if (guiType == "orbit" || guiType == "orbitWithPosition") {
|
||||
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();
|
||||
this.inclinationInput = createNumberInput(inclinationId, -360, 360);
|
||||
this.inclinationInput.addEventListener("change", () => {
|
||||
this.orbitalParameters.inclination = parseFloat(this.inclinationInput.value) * Math.PI / 180.0;
|
||||
this.informListeners(this.orbitalParameters);
|
||||
});
|
||||
addToParent([createLabel(inclinationId, "Inclination:"), this.inclinationInput]);
|
||||
let inclinationInput = createNumberInput(inclinationId, -360, 360);
|
||||
addToParent([createLabel(inclinationId, "Inclination:"), inclinationInput], this.parentDiv);
|
||||
|
||||
let lanId = crypto.randomUUID();
|
||||
this.lanInput = createNumberInput(lanId, -360, 360);
|
||||
this.lanInput.addEventListener("change", () => {
|
||||
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 lanInput = createNumberInput(lanId, -360, 360);
|
||||
addToParent([createLabel(lanId, "Longitude of ascending node:"), lanInput], this.parentDiv);
|
||||
|
||||
let aopId = crypto.randomUUID();
|
||||
this.aopInput = createNumberInput(aopId, -360, 360);
|
||||
this.aopInput.addEventListener("change", () => {
|
||||
this.orbitalParameters.argumentOfPeriapsis = parseFloat(this.aopInput.value) * Math.PI / 180.0;
|
||||
this.informListeners(this.orbitalParameters);
|
||||
let aopInput = createNumberInput(aopId, -360, 360);
|
||||
if (guiType == "orbit" || guiType == "orbitWithPosition") {
|
||||
addToParent([createLabel(aopId, "Argument of periapsis:"), aopInput], this.parentDiv);
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
addToParent([createLabel(aopId, "Argument of periapsis:"), this.aopInput]);
|
||||
|
||||
this.populateValues();
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
||||
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", "");
|
||||
this.altitudeData.listenToValue((sourceId, _) => {
|
||||
if (sourceId == this.altitudeGui.sourceId) {
|
||||
onChange();
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -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 {
|
||||
listeners: ((newValue: Body) => void)[];
|
||||
parentDiv: HTMLDivElement;
|
||||
body: ChangingStorageValue<Body>
|
||||
|
||||
constructor(htmlContainer: HTMLElement, startingValue?: Body, containerCreator?: (body: Body) => HTMLElement) {
|
||||
this.listeners = [];
|
||||
constructor(body: ChangingStorageValue<Body>) {
|
||||
this.body = body;
|
||||
this.parentDiv = document.createElement("div");
|
||||
this.parentDiv.classList.add("planetInput")
|
||||
|
||||
if (!startingValue) {
|
||||
startingValue = Kerbol;
|
||||
}
|
||||
|
||||
const addToParent = (toWrap: HTMLElement | HTMLElement[], body: Body) => {
|
||||
const addToParent = (toWrap: HTMLElement | HTMLElement[], parent: HTMLElement) => {
|
||||
var children = [];
|
||||
if (toWrap instanceof HTMLElement) {
|
||||
children = [toWrap];
|
||||
} else {
|
||||
children = toWrap;
|
||||
}
|
||||
|
||||
let containerToAppendTo = htmlContainer;
|
||||
if (containerCreator !== undefined) {
|
||||
let childContainer = containerCreator(body);
|
||||
containerToAppendTo.appendChild(childContainer);
|
||||
containerToAppendTo = childContainer;
|
||||
}
|
||||
|
||||
children.forEach(child => containerToAppendTo.appendChild(child));
|
||||
children.forEach(child => parent.appendChild(child));
|
||||
};
|
||||
|
||||
let planetChoiceName = crypto.randomUUID();
|
||||
let planetToButtonMap = new Map<Body, HTMLInputElement>();
|
||||
|
||||
PlanetList.forEach(body => {
|
||||
let listDiv = document.createElement("div");
|
||||
listDiv.classList.add(body.type);
|
||||
|
||||
let radioButton = document.createElement("input");
|
||||
let buttonId = crypto.randomUUID();
|
||||
radioButton.setAttribute("type", "radio");
|
||||
radioButton.setAttribute("id", buttonId);
|
||||
radioButton.setAttribute("name", planetChoiceName);
|
||||
radioButton.setAttribute("value", body.planetName);
|
||||
if (body === startingValue) {
|
||||
radioButton.setAttribute("checked", "");
|
||||
}
|
||||
|
||||
radioButton.addEventListener("change", () => this.informListeners(body));
|
||||
|
||||
let label = document.createElement("label");
|
||||
label.setAttribute("for", buttonId);
|
||||
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));
|
||||
}
|
||||
}
|
||||
@ -1,226 +1,87 @@
|
||||
import type { Wrapper } from "../storage";
|
||||
import { createDisabledInput, createLabel, createNumberInput, getOrbitFromParameters, type OrbitalParameters } from "./common";
|
||||
import { getCoordinatesFromParameters, type OrbitalParameters } from "./common";
|
||||
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 {
|
||||
listeners: ((targetInclination: number, targetLongitudeOfAscendingNode: number, circularize: boolean) => void)[];
|
||||
parentDiv: HTMLDivElement;
|
||||
|
||||
currentTime: Wrapper<number>;
|
||||
timeToPeriapsis: Wrapper<number>;
|
||||
planet: Wrapper<Body>;
|
||||
startingOrbitalParameters: Wrapper<OrbitalParameters>;
|
||||
goalOrbitalParameters: Wrapper<OrbitalParameters>;
|
||||
circularizeOrbit: boolean;
|
||||
currentTime: ChangingStorageValue<number>;
|
||||
planet: ChangingStorageValue<Body>;
|
||||
startingOrbitalParameters: ChangingStorageValue<OrbitalParameters>;
|
||||
targetOrbitalParameters: ChangingStorageValue<OrbitalParameters>;
|
||||
circularizeOrbit: ChangingStorageValue<boolean>;
|
||||
|
||||
inputHeader: HTMLElement;
|
||||
targetOrbitGui: OrbitalParametersGui;
|
||||
manoeuvresGui: ManoeuvresGui;
|
||||
|
||||
targetInclinationLabel: HTMLLabelElement;
|
||||
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;
|
||||
constructor(currentTime: ChangingStorageValue<number>, planet: ChangingStorageValue<Body>, startingOrbitalParameters: ChangingStorageValue<OrbitalParameters>, targetOrbitalParameters: ChangingStorageValue<OrbitalParameters>, circularizeOrbit: ChangingStorageValue<boolean>) {
|
||||
this.currentTime = currentTime;
|
||||
this.timeToPeriapsis = timeToPeriapsis;
|
||||
this.planet = planet;
|
||||
this.startingOrbitalParameters = startingOrbitalParameters;
|
||||
this.targetOrbitalParameters = targetOrbitalParameters;
|
||||
this.circularizeOrbit = circularizeOrbit;
|
||||
|
||||
if (defaultCircularizeOrbit) {
|
||||
this.circularizeOrbit = defaultCircularizeOrbit;
|
||||
} else {
|
||||
this.circularizeOrbit = false;
|
||||
}
|
||||
this.targetOrbitGui = new OrbitalParametersGui(targetOrbitalParameters, "plane");
|
||||
this.manoeuvresGui = new ManoeuvresGui(false);
|
||||
|
||||
this.inputHeader = document.createElement("h3");
|
||||
this.inputHeader.appendChild(document.createTextNode("Target plane:"));
|
||||
this.parentDiv = document.createElement("div");
|
||||
|
||||
let targetInclinationId = crypto.randomUUID();
|
||||
this.targetInclinationLabel = createLabel(targetInclinationId, "Target inclination:");
|
||||
this.targetInclinationInput = createNumberInput(targetInclinationId, -360, 360);
|
||||
let targetPlaneHeader = document.createElement("h3");
|
||||
targetPlaneHeader.appendChild(document.createTextNode("Target plane:"));
|
||||
|
||||
let targetLongitudeOfAscendingNodeId = crypto.randomUUID();
|
||||
this.targetLongitudeOfAscendingNodeLabel = createLabel(targetLongitudeOfAscendingNodeId, "Target longitude of ascending node:");
|
||||
this.targetLongitudeOfAscendingNodeInput = createNumberInput(targetLongitudeOfAscendingNodeId, -360, 360);
|
||||
this.parentDiv.appendChild(targetPlaneHeader);
|
||||
this.parentDiv.appendChild(this.targetOrbitGui.parentDiv);
|
||||
|
||||
let circularizeOrbitId = crypto.randomUUID();
|
||||
this.circularizeOrbitLabel = createLabel(circularizeOrbitId, "Circularize orbit");
|
||||
this.circularizeOrbitInput = document.createElement("input");
|
||||
this.circularizeOrbitInput.setAttribute("id", circularizeOrbitId);
|
||||
this.circularizeOrbitInput.setAttribute("type", "checkbox");
|
||||
let findPlaneChangeButton = document.createElement("button");
|
||||
findPlaneChangeButton.appendChild(document.createTextNode("Find plane change"));
|
||||
this.parentDiv.appendChild(findPlaneChangeButton);
|
||||
|
||||
this.targetInclinationInput.addEventListener("change", this.informListeners.bind(this));
|
||||
this.targetLongitudeOfAscendingNodeInput.addEventListener("change", this.informListeners.bind(this));
|
||||
this.circularizeOrbitInput.addEventListener("change", this.informListeners.bind(this));
|
||||
let possibleManoeuvresHeader = document.createElement("h3");
|
||||
possibleManoeuvresHeader.appendChild(document.createTextNode("Possible manoeuvres:"));
|
||||
this.parentDiv.appendChild(possibleManoeuvresHeader);
|
||||
this.parentDiv.appendChild(this.manoeuvresGui.parentDiv);
|
||||
|
||||
this.calculateButton = document.createElement("button");
|
||||
this.calculateButton.appendChild(document.createTextNode("Calculate"));
|
||||
this.calculateButton.addEventListener("click", this.performCalculation.bind(this));
|
||||
findPlaneChangeButton.addEventListener("click", () => {
|
||||
let currentTime = this.currentTime.getCurrentValue();
|
||||
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");
|
||||
this.outputHeader.appendChild(document.createTextNode("Possible manoeuvres:"));
|
||||
|
||||
this.manoeuvresContainer = document.createElement("div");
|
||||
this.manoeuvresContainer.classList.add("flexContainer");
|
||||
|
||||
let manoeuvreOneContainer = document.createElement("div");
|
||||
let manoeuvreTwoContainer = document.createElement("div");
|
||||
|
||||
this.manoeuvresContainer.appendChild(manoeuvreOneContainer);
|
||||
this.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.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(
|
||||
let coordinates = getCoordinatesFromParameters(startingOrbitalParameters, body);
|
||||
let planeChange = calculateSimplePlaneChange(
|
||||
coordinates,
|
||||
this.planet.value,
|
||||
this.goalOrbitalParameters.value.inclination,
|
||||
this.goalOrbitalParameters.value.longitudeOfAscendingNode,
|
||||
this.circularizeOrbit
|
||||
body,
|
||||
targetOrbitalParameters.inclination,
|
||||
targetOrbitalParameters.longitudeOfAscendingNode,
|
||||
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));
|
||||
planeChange.firstManoeuvre.time += currentTime;
|
||||
planeChange.secondManoeuvre.time += currentTime;
|
||||
|
||||
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));
|
||||
let bestDeltaV = Math.min(planeChange.firstManoeuvre.totalDeltaV, planeChange.secondManoeuvre.totalDeltaV);
|
||||
let bestTransfer: Transfer = {
|
||||
transferOrbit: {semiLatusRectum: 0, eccentricity: 0, coordinateAxes: [[], [], []]},
|
||||
firstManoeuvre: planeChange.firstManoeuvre,
|
||||
secondManoeuvre: planeChange.secondManoeuvre,
|
||||
closestPointDistance: 0,
|
||||
farthestPointDistance: 0
|
||||
}
|
||||
|
||||
let progressMessage: ProgressMessage = {
|
||||
type: "ProgressMessage",
|
||||
finished: true,
|
||||
percentDone: 100,
|
||||
bestDeltaV: bestDeltaV,
|
||||
bestTransfer: bestTransfer
|
||||
};
|
||||
|
||||
this.manoeuvresGui.displayProgress(progressMessage);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,143 +1,49 @@
|
||||
import { getOrbitalCoordinates } from "../calculations/orbit-calculations";
|
||||
import type { Wrapper } from "../storage";
|
||||
import { createDisabledInput, createLabel, getOrbitFromParameters, type OrbitalParameters } from "./common";
|
||||
import type { Body } from "../calculations/constants";
|
||||
import type { ChangingStorageValue } from "../storage";
|
||||
import { getCoordinatesFromParameters, getOrbitFromParameters, type OrbitalParameters } from "./common";
|
||||
import { ManoeuvresGui } from "./manoeuvres";
|
||||
import { OrbitalParametersGui } from "./orbit";
|
||||
import { type Body } from "../calculations/constants";
|
||||
import type { FindBestTransferMessage, FindBestTransferResponse } from "./worker";
|
||||
import type { FindBestTransferMessage, ProgressMessage } from "./worker";
|
||||
|
||||
export class TargetOrbitGui {
|
||||
startingParameters: Wrapper<OrbitalParameters>;
|
||||
goalParameters: Wrapper<OrbitalParameters>;
|
||||
currentTime: Wrapper<number>;
|
||||
timeToPeriapsis: Wrapper<number>;
|
||||
body: Wrapper<Body>;
|
||||
currentTime: ChangingStorageValue<number>;
|
||||
body: ChangingStorageValue<Body>;
|
||||
startingOrbitalParameters: ChangingStorageValue<OrbitalParameters>;
|
||||
targetOrbitalParameters: ChangingStorageValue<OrbitalParameters>;
|
||||
|
||||
orbitGui: OrbitalParametersGui;
|
||||
parentDiv: HTMLDivElement;
|
||||
|
||||
progressParagraph: HTMLParagraphElement;
|
||||
|
||||
firstManoeuvreTime: HTMLInputElement;
|
||||
firstManoeuvrePrograde: HTMLInputElement;
|
||||
firstManoeuvreNormal: HTMLInputElement;
|
||||
firstManoeuvreRadial: HTMLInputElement;
|
||||
firstManoeuvreTotal: HTMLInputElement;
|
||||
|
||||
secondManoeuvreTime: HTMLInputElement;
|
||||
secondManoeuvrePrograde: HTMLInputElement;
|
||||
secondManoeuvreNormal: HTMLInputElement;
|
||||
secondManoeuvreRadial: HTMLInputElement;
|
||||
secondManoeuvreTotal: HTMLInputElement;
|
||||
orbitGui: OrbitalParametersGui;
|
||||
manoeuvresGui: ManoeuvresGui;
|
||||
|
||||
worker: Worker | null;
|
||||
|
||||
constructor(startingParameters: Wrapper<OrbitalParameters>, goalParameters: Wrapper<OrbitalParameters>, currentTime: Wrapper<number>, timeToPeriapsis: Wrapper<number>, body: Wrapper<Body>) {
|
||||
this.startingParameters = startingParameters;
|
||||
this.goalParameters = goalParameters;
|
||||
constructor(currentTime: ChangingStorageValue<number>, body: ChangingStorageValue<Body>, startingOrbitalParameters: ChangingStorageValue<OrbitalParameters>, targetOrbitalParameters: ChangingStorageValue<OrbitalParameters>) {
|
||||
this.worker = null;
|
||||
|
||||
this.currentTime = currentTime;
|
||||
this.timeToPeriapsis = timeToPeriapsis;
|
||||
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");
|
||||
let parametersHeader = document.createElement("h3");
|
||||
parametersHeader.appendChild(document.createTextNode("Target orbit:"));
|
||||
this.parentDiv.appendChild(parametersHeader);
|
||||
|
||||
let orbitalParameterContainerCreator = () => {
|
||||
let orbitalParameterContainer = document.createElement("div");
|
||||
orbitalParameterContainer.classList.add("orbitalParameter");
|
||||
return orbitalParameterContainer;
|
||||
}
|
||||
|
||||
this.orbitGui = new OrbitalParametersGui(this.parentDiv, goalParameters.value, orbitalParameterContainerCreator);
|
||||
let targetOrbitHeader = document.createElement("h3");
|
||||
targetOrbitHeader.appendChild(document.createTextNode("Target orbit:"));
|
||||
this.parentDiv.appendChild(targetOrbitHeader);
|
||||
this.parentDiv.appendChild(this.orbitGui.parentDiv);
|
||||
|
||||
let searchButton = document.createElement("button");
|
||||
searchButton.appendChild(document.createTextNode("Search for cheapest transfer"));
|
||||
this.parentDiv.appendChild(searchButton);
|
||||
|
||||
let searchHeader = document.createElement("h3");
|
||||
searchHeader.appendChild(document.createTextNode("Manoeuvre search"));
|
||||
this.parentDiv.appendChild(searchHeader);
|
||||
let foundTransferHeader = document.createElement("h3");
|
||||
foundTransferHeader.appendChild(document.createTextNode("Found transfer:"));
|
||||
this.parentDiv.appendChild(foundTransferHeader);
|
||||
|
||||
this.progressParagraph = document.createElement("p");
|
||||
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;
|
||||
this.parentDiv.appendChild(this.manoeuvresGui.parentDiv);
|
||||
|
||||
searchButton.addEventListener("click", () => {
|
||||
if (this.worker !== null) {
|
||||
@ -147,63 +53,41 @@ export class TargetOrbitGui {
|
||||
} else {
|
||||
searchButton.innerHTML = "Cancel search";
|
||||
|
||||
let startingOrbit = getOrbitFromParameters(startingParameters.value, this.body.value.radius);
|
||||
let endingOrbit = getOrbitFromParameters(goalParameters.value, this.body.value.radius);
|
||||
let currentCoordinates = getOrbitalCoordinates(timeToPeriapsis.value, startingOrbit, this.body.value);
|
||||
let currentTime = this.currentTime.getCurrentValue();
|
||||
let startingOrbitalParameters = this.startingOrbitalParameters.getCurrentValue();
|
||||
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.addEventListener("message", event => {
|
||||
let transferResponse = event.data as FindBestTransferResponse;
|
||||
let transferResponse = event.data as ProgressMessage;
|
||||
if (transferResponse) {
|
||||
if (transferResponse.finished) {
|
||||
searchButton.innerHTML = "Search for cheapest transfer";
|
||||
this.worker = null;
|
||||
}
|
||||
|
||||
this.progressParagraph.innerHTML = "";
|
||||
this.progressParagraph.appendChild(document.createTextNode(`Search is ${transferResponse.percentDone.toFixed(2)}% finished`));
|
||||
if (transferResponse.bestDeltaV !== null && transferResponse.bestTransfer != null) {
|
||||
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);
|
||||
if (transferResponse.bestTransfer) {
|
||||
transferResponse.bestTransfer.firstManoeuvre.time += currentTime;
|
||||
transferResponse.bestTransfer.secondManoeuvre.time += currentTime;
|
||||
}
|
||||
|
||||
this.manoeuvresGui.displayProgress(transferResponse);
|
||||
}
|
||||
});
|
||||
|
||||
let workerMessage: FindBestTransferMessage = {
|
||||
type: "FindBestTransfer",
|
||||
startingSituation: currentCoordinates,
|
||||
targetOrbit: endingOrbit,
|
||||
body: body.value
|
||||
startingSituation: startingCoordinates,
|
||||
targetOrbit: targetOrbit,
|
||||
body: body
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
129
src/gui/time.ts
129
src/gui/time.ts
@ -1,20 +1,17 @@
|
||||
import type { ChangingStorageValue } from "../storage";
|
||||
import { createLabel, createNumberInput } from "./common";
|
||||
|
||||
export class TimeGui {
|
||||
dateListeners: ((newDate: number) => void)[];
|
||||
isDate: boolean;
|
||||
yearsInput: HTMLInputElement;
|
||||
daysInput: HTMLInputElement;
|
||||
hoursInput: HTMLInputElement;
|
||||
minutesInput: HTMLInputElement;
|
||||
secondsInput: HTMLInputElement;
|
||||
parentDiv: HTMLDivElement;
|
||||
currentTime: ChangingStorageValue<number>;
|
||||
|
||||
constructor(htmlContainer: HTMLElement, isDate?: boolean, startingValue?: number, containerCreator?: () => HTMLElement) {
|
||||
this.dateListeners = [];
|
||||
if (isDate !== undefined) {
|
||||
this.isDate = isDate;
|
||||
} else {
|
||||
this.isDate = false;
|
||||
sourceId: string;
|
||||
|
||||
constructor(currentTime: ChangingStorageValue<number>, isDate?: boolean) {
|
||||
this.currentTime = currentTime;
|
||||
|
||||
if (isDate === undefined) {
|
||||
isDate = false;
|
||||
}
|
||||
|
||||
var minimumYears = 0;
|
||||
@ -24,7 +21,7 @@ export class TimeGui {
|
||||
minimumDays = 1;
|
||||
}
|
||||
|
||||
const addToParent = (toWrap: HTMLElement | HTMLElement[]) => {
|
||||
const addToParent = (toWrap: HTMLElement | HTMLElement[], parent: HTMLElement) => {
|
||||
var children = [];
|
||||
if (toWrap instanceof HTMLElement) {
|
||||
children = [toWrap];
|
||||
@ -32,81 +29,81 @@ export class TimeGui {
|
||||
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();
|
||||
this.yearsInput = createNumberInput(yearsInputId, minimumYears, undefined, 2);
|
||||
addToParent([createLabel(yearsInputId, "Years:"), this.yearsInput]);
|
||||
let yearsInput = createNumberInput(yearsInputId, minimumYears, undefined, 2);
|
||||
addToParent([createLabel(yearsInputId, "Years:"), yearsInput], this.parentDiv);
|
||||
|
||||
let daysInputId = crypto.randomUUID();
|
||||
this.daysInput = createNumberInput(daysInputId, minimumDays, 424, 1);
|
||||
addToParent([createLabel(daysInputId, "Days:"), this.daysInput]);
|
||||
let daysInput = createNumberInput(daysInputId, minimumDays, 424, 1);
|
||||
addToParent([createLabel(daysInputId, "Days:"), daysInput], this.parentDiv);
|
||||
|
||||
let hoursInputId = crypto.randomUUID();
|
||||
this.hoursInput = createNumberInput(hoursInputId, 0, 5, 1);
|
||||
addToParent([createLabel(hoursInputId, "Hours:"), this.hoursInput]);
|
||||
let hoursInput = createNumberInput(hoursInputId, 0, 5, 1);
|
||||
addToParent([createLabel(hoursInputId, "Hours:"), hoursInput], this.parentDiv);
|
||||
|
||||
let minutesInputId = crypto.randomUUID();
|
||||
this.minutesInput = createNumberInput(minutesInputId, 0, 59, 1);
|
||||
addToParent([createLabel(minutesInputId, "Minutes:"), this.minutesInput]);
|
||||
let minutesInput = createNumberInput(minutesInputId, 0, 59, 1);
|
||||
addToParent([createLabel(minutesInputId, "Minutes:"), minutesInput], this.parentDiv);
|
||||
|
||||
let secondsInputId = crypto.randomUUID();
|
||||
this.secondsInput = createNumberInput(secondsInputId, 0, 59, 1);
|
||||
addToParent([createLabel(secondsInputId, "Seconds:"), this.secondsInput]);
|
||||
let secondsInput = createNumberInput(secondsInputId, 0, 59, 1);
|
||||
addToParent([createLabel(secondsInputId, "Seconds:"), secondsInput], this.parentDiv);
|
||||
|
||||
if (startingValue === undefined) {
|
||||
startingValue = 0;
|
||||
// Set up listeners for these changes and stuff
|
||||
this.sourceId = crypto.randomUUID();
|
||||
|
||||
const currentTimeChangeListener = (source: string, newValue: number) => {
|
||||
if (source == this.sourceId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const startingSeconds = startingValue % 60;
|
||||
startingValue = Math.floor(startingValue / 60);
|
||||
const startingMinutes = startingValue % 60;
|
||||
startingValue = Math.floor(startingValue / 60);
|
||||
const startingHours = (startingValue % 6);
|
||||
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);
|
||||
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);
|
||||
|
||||
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));
|
||||
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));
|
||||
};
|
||||
|
||||
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));
|
||||
}
|
||||
currentTime.listenToValue(currentTimeChangeListener);
|
||||
|
||||
addListener(listenerFunction: (newDate: number) => void) {
|
||||
this.dateListeners.push(listenerFunction);
|
||||
}
|
||||
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);
|
||||
|
||||
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) {
|
||||
if (isDate) {
|
||||
years -= 1;
|
||||
days -= 1;
|
||||
}
|
||||
|
||||
let calculatedTime = (((years * 426 + days) * 6 + hours) * 60 + minutes) * 60 + seconds;
|
||||
this.currentTime.set(calculatedTime, this.sourceId);
|
||||
}
|
||||
|
||||
this.dateListeners.forEach(listener => listener(calculatedTime));
|
||||
yearsInput.addEventListener("change", calculateAndSetTime);
|
||||
daysInput.addEventListener("change", calculateAndSetTime);
|
||||
hoursInput.addEventListener("change", calculateAndSetTime);
|
||||
minutesInput.addEventListener("change", calculateAndSetTime);
|
||||
secondsInput.addEventListener("change", calculateAndSetTime);
|
||||
}
|
||||
}
|
||||
@ -10,14 +10,6 @@ export interface FindBestTransferMessage {
|
||||
body: Body
|
||||
}
|
||||
|
||||
export interface FindBestTransferResponse {
|
||||
type: "FindBestTransferResponse"
|
||||
finished: boolean,
|
||||
percentDone: number,
|
||||
bestDeltaV: number | null,
|
||||
bestTransfer: Transfer | null
|
||||
}
|
||||
|
||||
export interface FindBestInterceptMessage {
|
||||
type: "FindBestIntercept",
|
||||
startingSituation: OrbitalCoordinates,
|
||||
@ -26,12 +18,20 @@ export interface FindBestInterceptMessage {
|
||||
body: Body;
|
||||
}
|
||||
|
||||
export interface ProgressMessage {
|
||||
type: "ProgressMessage"
|
||||
finished: boolean,
|
||||
percentDone: number,
|
||||
bestDeltaV: number | null,
|
||||
bestTransfer: Transfer | null
|
||||
}
|
||||
|
||||
ctx.addEventListener("message", event => {
|
||||
const progressCallback = (numberChecked: number, totalNumber: number, bestDeltaV: number | null, bestTransfer: Transfer | null) => {
|
||||
if (numberChecked % 100 == 0) {
|
||||
let percentDone = numberChecked * 100 / totalNumber;
|
||||
let message: FindBestTransferResponse = {
|
||||
type: "FindBestTransferResponse",
|
||||
let message: ProgressMessage = {
|
||||
type: "ProgressMessage",
|
||||
finished: false,
|
||||
percentDone: percentDone,
|
||||
bestDeltaV: bestDeltaV,
|
||||
@ -51,8 +51,8 @@ ctx.addEventListener("message", event => {
|
||||
bestDeltaV = bestTransfer.firstManoeuvre.totalDeltaV + bestTransfer.secondManoeuvre.totalDeltaV;
|
||||
}
|
||||
|
||||
let finishedMessage: FindBestTransferResponse = {
|
||||
type: "FindBestTransferResponse",
|
||||
let finishedMessage: ProgressMessage = {
|
||||
type: "ProgressMessage",
|
||||
finished: true,
|
||||
percentDone: 100,
|
||||
bestDeltaV: bestDeltaV,
|
||||
@ -69,8 +69,8 @@ ctx.addEventListener("message", event => {
|
||||
bestDeltaV = bestIntercept.firstManoeuvre.totalDeltaV + bestIntercept.secondManoeuvre.totalDeltaV;
|
||||
}
|
||||
|
||||
let finishedMessage: FindBestTransferResponse = {
|
||||
type: "FindBestTransferResponse",
|
||||
let finishedMessage: ProgressMessage = {
|
||||
type: "ProgressMessage",
|
||||
finished: true,
|
||||
percentDone: 100,
|
||||
bestDeltaV: bestDeltaV,
|
||||
|
||||
175
src/main.ts
175
src/main.ts
@ -1,14 +1,12 @@
|
||||
import { getPlanetByName, Kerbol, type Body } from "./calculations/constants";
|
||||
import { DefaultOrbitalParameters } from "./gui/common";
|
||||
import { getPlanetByName, Kerbol } from "./calculations/constants";
|
||||
import { createLabel, createRadioButton, decodeOrbitalParameters, DefaultOrbitalParameters, encodeOrbitalParameters } from "./gui/common";
|
||||
import { InterceptTargetGui } from "./gui/intercept";
|
||||
import { OrbitalParametersGui } from "./gui/orbit";
|
||||
import { decodeOrbitalParameters } from "./gui/common";
|
||||
import { encodeOrbitalParameters } from "./gui/common";
|
||||
import { PlanetGui } from "./gui/planet";
|
||||
import { TimeGui } from "./gui/time";
|
||||
import { createLocalStorageVariable } from "./storage";
|
||||
import { SimplePlaneChangeGui } from "./gui/simpleplanechange";
|
||||
import { TargetOrbitGui } from "./gui/targetorbit";
|
||||
import { InterceptTargetGui } from "./gui/intercept";
|
||||
import { TimeGui } from "./gui/time";
|
||||
import { ChangingStorageValue } from "./storage";
|
||||
|
||||
type CalculationType = "planeChange" | "targetOrbit" | "intercept";
|
||||
function decodeCalculationType(input: string): CalculationType {
|
||||
@ -20,136 +18,59 @@ function decodeCalculationType(input: string): CalculationType {
|
||||
return "planeChange";
|
||||
}
|
||||
|
||||
let [currentTime, setCurrentTime] = createLocalStorageVariable("currentTime", parseInt, n => n.toFixed(0), 0);
|
||||
let [timeToPeriapsis, setTimeToPeriapsis] = createLocalStorageVariable("timeToPeriapsis", parseInt, n => n.toFixed(0), 0);
|
||||
let [planet, setPlanet] = createLocalStorageVariable("planet", getPlanetByName, p => p.planetName, Kerbol);
|
||||
let [orbitalParameters, setOrbitalParameters] = createLocalStorageVariable("orbitalParameters", decodeOrbitalParameters, encodeOrbitalParameters, DefaultOrbitalParameters);
|
||||
let [calculationType, setCalculationType] = createLocalStorageVariable("calculationType", decodeCalculationType, s => s, "planeChange");
|
||||
let [targetOrbitalParameters, setTargetOrbitalParameters] = createLocalStorageVariable("targetOrbitalParameters", decodeOrbitalParameters, encodeOrbitalParameters, DefaultOrbitalParameters);
|
||||
let [circularizeOrbit, setCircularizeOrbit] = createLocalStorageVariable("circularizeOrbit", s => s == "true", bool => bool ? "true" : "false", false)
|
||||
let [targetTimeToPeriapsis, setTargetTimeToPeriapsis] = createLocalStorageVariable("targetTimeToPeriapsis", parseInt, n => n.toFixed(0), 0);
|
||||
let [additionalTrueAnomaly, setAdditionalTrueanomaly] = createLocalStorageVariable("additionalTrueAnomaly", parseFloat, n => n.toString(), 0);
|
||||
let currentTime = new ChangingStorageValue(0, "currentTime", parseInt, n => n.toString());
|
||||
let body = new ChangingStorageValue(Kerbol, "planet", s => getPlanetByName(s), p => p.planetName);
|
||||
let orbitalParameters = new ChangingStorageValue(DefaultOrbitalParameters, "orbitalParameters", decodeOrbitalParameters, encodeOrbitalParameters);
|
||||
let calculationType = new ChangingStorageValue("planeChange", "calculationType", decodeCalculationType, s => s);
|
||||
let targetOrbitalParameters = new ChangingStorageValue(DefaultOrbitalParameters, "targetOrbitalParameters", decodeOrbitalParameters, encodeOrbitalParameters);
|
||||
let circularizeOrbit = new ChangingStorageValue(false, "circularizeOrbit", s => s == "true", b => b ? "true" : "false");
|
||||
let additionalInterceptTrueanomaly = new ChangingStorageValue(0, "additionalTrueAnomaly", parseFloat, s => s.toString());
|
||||
|
||||
let dateInputContainerCreator = () => {
|
||||
let dateInputContainer = document.createElement("span");
|
||||
dateInputContainer.classList.add("dateInputContainer");
|
||||
return dateInputContainer;
|
||||
}
|
||||
let dateGui = new TimeGui(currentTime, true);
|
||||
let currentTimeDiv = document.getElementById("currentTimeDiv");
|
||||
currentTimeDiv?.appendChild(dateGui.parentDiv);
|
||||
|
||||
let planetaryBodyContainerCreator = (body: Body) => {
|
||||
let bodyContainer = document.createElement("div");
|
||||
bodyContainer.classList.add(body.type);
|
||||
return bodyContainer;
|
||||
};
|
||||
let planetGui = new PlanetGui(body);
|
||||
let planetsDiv = document.getElementById("planetsDiv");
|
||||
planetsDiv?.appendChild(planetGui.parentDiv);
|
||||
|
||||
let orbitalParameterContainerCreator = () => {
|
||||
let orbitalParameterContainer = document.createElement("div");
|
||||
orbitalParameterContainer.classList.add("orbitalParameter");
|
||||
return orbitalParameterContainer;
|
||||
}
|
||||
let orbitalParametersGui = new OrbitalParametersGui(orbitalParameters);
|
||||
let orbitalParametersDiv = document.getElementById("orbitalParametersDiv");
|
||||
orbitalParametersDiv?.appendChild(orbitalParametersGui.parentDiv);
|
||||
|
||||
let timesDiv = document.getElementById("timesDiv") as HTMLElement;
|
||||
let dateGui = new TimeGui(timesDiv, true, currentTime.value, dateInputContainerCreator);
|
||||
dateGui.addListener(setCurrentTime);
|
||||
let simplePlaneChangeGui = new SimplePlaneChangeGui(currentTime, body, orbitalParameters, targetOrbitalParameters, circularizeOrbit);
|
||||
let targetOrbitGui = new TargetOrbitGui(currentTime, body, orbitalParameters, targetOrbitalParameters);
|
||||
let interceptTargetGui = new InterceptTargetGui(currentTime, body, orbitalParameters, targetOrbitalParameters, additionalInterceptTrueanomaly);
|
||||
|
||||
let periapsisDiv = document.getElementById("periapsisDiv") as HTMLElement;
|
||||
let periapsisGui = new TimeGui(periapsisDiv, false, timeToPeriapsis.value, dateInputContainerCreator);
|
||||
periapsisGui.addListener(setTimeToPeriapsis);
|
||||
let calculationChoiceDiv = document.getElementById("calculationChoice");
|
||||
|
||||
let planetsDiv = document.getElementById("planetsDiv") as HTMLElement;
|
||||
let planetsGui = new PlanetGui(planetsDiv, planet.value, planetaryBodyContainerCreator);
|
||||
planetsGui.addListener(setPlanet);
|
||||
let simplePlaneChangeButton = createRadioButton("calculationChoice", "simplePlaneChange");
|
||||
simplePlaneChangeButton.addEventListener("change", () => calculationType.set("planeChange", "main"));
|
||||
let simplePlaneChangeLabel = createLabel("simplePlaneChange", "Simple plane change");
|
||||
[simplePlaneChangeButton, simplePlaneChangeLabel].forEach(child => calculationChoiceDiv?.appendChild(child));
|
||||
|
||||
let orbitalParametersDiv = document.getElementById("orbitalParametersDiv") as HTMLElement;
|
||||
let orbitalParametersGui = new OrbitalParametersGui(orbitalParametersDiv, orbitalParameters.value, orbitalParameterContainerCreator);
|
||||
orbitalParametersGui.addListener(setOrbitalParameters);
|
||||
let targetOrbitButton = createRadioButton("calculationChoice", "targetOrbit");
|
||||
targetOrbitButton.addEventListener("change", () => calculationType.set("targetOrbit", "main"));
|
||||
let targetOrbitLabel = createLabel("targetOrbit", "Target orbit");
|
||||
[targetOrbitButton, targetOrbitLabel].forEach(child => calculationChoiceDiv?.appendChild(child));
|
||||
|
||||
let choiceDiv = document.getElementById("calculationChoice") as HTMLElement;
|
||||
let calculationDiv = document.getElementById("calculation") as HTMLElement;
|
||||
let interceptTargetButton = createRadioButton("calculationChoice", "interceptTarget");
|
||||
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);
|
||||
simplePlaneChangeGui.addListener((targetInclination, targetLongitudeOfAscendingNode, circularizeOrbit) => {
|
||||
setCircularizeOrbit(circularizeOrbit);
|
||||
|
||||
let newTargetParameters = structuredClone(targetOrbitalParameters.value);
|
||||
newTargetParameters.inclination = targetInclination;
|
||||
newTargetParameters.longitudeOfAscendingNode = targetLongitudeOfAscendingNode;
|
||||
|
||||
setTargetOrbitalParameters(newTargetParameters);
|
||||
});
|
||||
|
||||
const targetOrbitGui = new TargetOrbitGui(orbitalParameters, targetOrbitalParameters, currentTime, timeToPeriapsis, planet);
|
||||
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") {
|
||||
let calculationsDiv = document.getElementById("calculation") as HTMLDivElement;
|
||||
calculationType.listenToValue((_, value) => {
|
||||
calculationsDiv.innerHTML = "";
|
||||
if (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") {
|
||||
calculationsDiv.appendChild(simplePlaneChangeGui.parentDiv);
|
||||
} else if (value == "targetOrbit") {
|
||||
targetOrbitButton.setAttribute("checked", "");
|
||||
}
|
||||
targetOrbitButton.addEventListener("change", () => {
|
||||
setCalculationType("targetOrbit");
|
||||
populateCalculationDiv();
|
||||
calculationsDiv.appendChild(targetOrbitGui.parentDiv);
|
||||
} else if (value == "intercept") {
|
||||
interceptTargetButton.setAttribute("checked", "");
|
||||
calculationsDiv.appendChild(interceptTargetGui.parentDiv);
|
||||
}
|
||||
});
|
||||
|
||||
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();
|
||||
})
|
||||
@ -1,24 +1,43 @@
|
||||
export interface Wrapper<Type> {
|
||||
export class ChangingStorageValue<Type> {
|
||||
value: Type;
|
||||
}
|
||||
changeListeners: ((changeSource: string, value: Type) => void)[];
|
||||
|
||||
variableName: string | null;
|
||||
encoder: ((value: Type) => string) | null;
|
||||
|
||||
constructor(defaultValue: Type, variableName?: string, decoder?: (a: string) => Type, encoder?: (a: Type) => string) {
|
||||
this.changeListeners = [];
|
||||
this.value = defaultValue;
|
||||
|
||||
if (variableName && decoder && encoder) {
|
||||
this.variableName = variableName;
|
||||
this.encoder = encoder;
|
||||
let localStorageString = localStorage.getItem(variableName);
|
||||
|
||||
export function createLocalStorageVariable<Type>(variableName: string, decoder: (a: string) => Type, encoder: (a: Type) => string, defaultValue: Type): [Wrapper<Type>, (value: Type) => void] {
|
||||
var localStorageString = localStorage.getItem(variableName);
|
||||
var variable: Wrapper<Type>;
|
||||
if (localStorageString) {
|
||||
variable = {
|
||||
value: decoder(localStorageString)
|
||||
};
|
||||
this.value = decoder(localStorageString);
|
||||
}
|
||||
} else {
|
||||
variable = {
|
||||
value: defaultValue
|
||||
};
|
||||
this.variableName = null;
|
||||
this.encoder = null;
|
||||
}
|
||||
}
|
||||
|
||||
const setterFunction = (value: Type) => {
|
||||
variable.value = value;
|
||||
localStorage.setItem(variableName, encoder(value));
|
||||
set(newValue: Type, source: string) {
|
||||
this.value = newValue;
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,10 @@
|
||||
.dateInputContainer {
|
||||
.timeInput {
|
||||
display: inline-block;
|
||||
min-width: 120px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.dateInputContainer label {
|
||||
.timeInput label {
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
@ -29,22 +29,8 @@
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.simplePlaneChange label {
|
||||
display: inline-block;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.simplePlaneChange input[type=number] {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.targetOrbit label {
|
||||
display: inline-block;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.targetOrbit input[type=number] {
|
||||
width: 150px;
|
||||
#calculationChoice label {
|
||||
margin-right: 30px;
|
||||
}
|
||||
|
||||
.flexContainer {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user