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