From 2b4fb65d4f5ce8017ab93cf3b0fd5cf8b921b1c6 Mon Sep 17 00:00:00 2001 From: Martin Asprusten Date: Mon, 30 Mar 2026 11:42:09 +0200 Subject: [PATCH] Stuff is still working --- index.html | 9 +- src/calculations/orbit-calculations.ts | 193 +++++++++++++--- src/gui/altitude.ts | 61 +++++ src/gui/common.ts | 32 ++- src/gui/intercept.ts | 260 +++++----------------- src/gui/manoeuvres.ts | 117 ++++++++++ src/gui/orbit.ts | 297 +++++++++++++------------ src/gui/planet.ts | 66 +++--- src/gui/simpleplanechange.ts | 269 ++++++---------------- src/gui/targetorbit.ts | 202 ++++------------- src/gui/time.ts | 149 ++++++------- src/gui/worker.ts | 28 +-- src/main.ts | 177 ++++----------- src/storage.ts | 53 +++-- src/style.css | 22 +- 15 files changed, 901 insertions(+), 1034 deletions(-) create mode 100644 src/gui/altitude.ts create mode 100644 src/gui/manoeuvres.ts diff --git a/index.html b/index.html index 4f8e682..abc4a5d 100644 --- a/index.html +++ b/index.html @@ -9,11 +9,8 @@

Kerbal calculations

-

Current time in game:

-
-

Time to periapsis:

-
- +

Earliest allowed manoeuvre time:

+

Planet:

Orbital parameters:

@@ -21,5 +18,7 @@

What to calculate:

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