import { createLabel, createNumberInput, createRadioButton, getCoordinatesFromParameters, type OrbitalParameters } from "./common"; import { OrbitalParametersGui } from "./orbit"; import { type Body } from "../calculations/constants"; import type { FindBestInterceptMessage, ProgressMessage } from "./worker"; import type { ChangingStorageValue } from "../storage"; import { ManoeuvresGui } from "./manoeuvres"; import { extrapolateTrajectory, findOrbitThroughInterpolation, type OrbitalCoordinates } from "../calculations/orbit-calculations"; import { InterpolateOrbitGui, type InterpolationParameters } from "./interpolate"; export type TargetOrbitChoice = "KnownOrbit" | "InterpolateOrbit"; export function decodeTargetOrbitChoice(s: string): TargetOrbitChoice { if (s == "KnownOrbit" || s == "InterpolateOrbit") { return s; } return "KnownOrbit"; } export class InterceptTargetGui { currentTime: ChangingStorageValue; body: ChangingStorageValue; startingOrbitalParameters: ChangingStorageValue; targetOrbitalParameters: ChangingStorageValue; additionalTrueAnomaly: ChangingStorageValue; targetOrbitChoice: ChangingStorageValue; interpolationParameters: ChangingStorageValue; parentDiv: HTMLDivElement; orbitGui: OrbitalParametersGui; interpolateGui: InterpolateOrbitGui; manoeuvresGui: ManoeuvresGui; sourceId: string; worker: Worker | null; constructor(currentTime: ChangingStorageValue, body: ChangingStorageValue, startingOrbitalParameters: ChangingStorageValue, targetOrbitalParameters: ChangingStorageValue, additionalTrueAnomaly: ChangingStorageValue, targetOrbitChoice: ChangingStorageValue, interpolationParameters: ChangingStorageValue) { this.currentTime = currentTime; this.body = body; this.startingOrbitalParameters = startingOrbitalParameters; this.targetOrbitalParameters = targetOrbitalParameters; this.additionalTrueAnomaly = additionalTrueAnomaly; this.targetOrbitChoice = targetOrbitChoice; this.interpolationParameters = interpolationParameters; this.orbitGui = new OrbitalParametersGui(targetOrbitalParameters, "orbitWithPosition"); this.interpolateGui = new InterpolateOrbitGui(this.interpolationParameters); this.manoeuvresGui = new ManoeuvresGui(true); this.worker = null; this.parentDiv = document.createElement("div"); let choiceHeader = document.createElement("h3") choiceHeader.appendChild(document.createTextNode("Choose target orbit type")); this.parentDiv.appendChild(choiceHeader); let targetChoiceId = crypto.randomUUID(); let knownOrbitId = crypto.randomUUID(); let knownOrbitButton = createRadioButton(targetChoiceId, knownOrbitId); let knownOrbitLabel = createLabel(knownOrbitId, "Known orbit"); this.parentDiv.appendChild(knownOrbitButton); this.parentDiv.appendChild(knownOrbitLabel); let interpolateOrbitId = crypto.randomUUID(); let interpolateOrbitButton = createRadioButton(targetChoiceId, interpolateOrbitId); let interpolateOrbitLabel = createLabel(interpolateOrbitId, "Interpolate orbit"); this.parentDiv.appendChild(interpolateOrbitButton); this.parentDiv.appendChild(interpolateOrbitLabel); let orbitContainer = document.createElement("div"); this.parentDiv.appendChild(orbitContainer); let knownOrbitContainer = document.createElement("div"); let parametersHeader = document.createElement("h3"); parametersHeader.appendChild(document.createTextNode("Target orbit:")); knownOrbitContainer.appendChild(parametersHeader); knownOrbitContainer.appendChild(this.orbitGui.parentDiv); let interpolateOrbitContainer = document.createElement("div"); let interpolateOrbitHeader = document.createElement("h3"); interpolateOrbitHeader.appendChild(document.createTextNode("Interpolate orbit:")); interpolateOrbitContainer.appendChild(interpolateOrbitHeader); interpolateOrbitContainer.appendChild(this.interpolateGui.parentDiv); let offsetHeader = document.createElement("h3"); offsetHeader.appendChild(document.createTextNode("Offset:")); this.parentDiv.appendChild(offsetHeader); let additionalTrueAnomalyContainer = document.createElement("div"); additionalTrueAnomalyContainer.classList.add("orbitalParameter"); this.parentDiv.appendChild(additionalTrueAnomalyContainer); let additionalTrueAnomalyId = crypto.randomUUID(); let additionalTrueanomalyLabel = createLabel(additionalTrueAnomalyId, "Additional true anomaly:"); let additionalTrueAnomalyInput = createNumberInput(additionalTrueAnomalyId, -360, 360); [additionalTrueanomalyLabel, additionalTrueAnomalyInput].forEach(child => additionalTrueAnomalyContainer.appendChild(child)); let searchButton = document.createElement("button"); searchButton.appendChild(document.createTextNode("Search for cheapest intercept")); this.parentDiv.appendChild(searchButton); let bestInterceptHeader = document.createElement("h3"); bestInterceptHeader.appendChild(document.createTextNode("Best intercept:")); this.parentDiv.appendChild(bestInterceptHeader); this.parentDiv.appendChild(this.manoeuvresGui.parentDiv); searchButton.addEventListener("click", () => { if (this.worker !== null) { this.worker.terminate(); this.worker = null; searchButton.innerHTML = "Search for cheapest intercept"; } else { searchButton.innerHTML = "Cancel search"; let currentTime = this.currentTime.getCurrentValue(); let body = this.body.getCurrentValue(); let startingOrbitalParameters = this.startingOrbitalParameters.getCurrentValue(); let targetOrbitalParameters = this.targetOrbitalParameters.getCurrentValue(); let additionalTrueAnomaly = this.additionalTrueAnomaly.getCurrentValue(); let targetOrbitChoice = this.targetOrbitChoice.getCurrentValue(); let interpolationParameters = this.interpolationParameters.getCurrentValue(); interpolationParameters = structuredClone(interpolationParameters); interpolationParameters.firstOwnAltitude += body.radius; interpolationParameters.firstTargetAltitude += body.radius; interpolationParameters.secondOwnAltitude += body.radius; interpolationParameters.secondTargetAltitude += body.radius; interpolationParameters.targetAltitude += body.radius; interpolationParameters.targetPeriapsis += body.radius; interpolationParameters.targetApoapsis += body.radius; let startingCoordinates: OrbitalCoordinates | null = getCoordinatesFromParameters(startingOrbitalParameters, body); let startTime: number | null = null; let targetStartTime = 0; let targetCoordinates: OrbitalCoordinates | null = null; if (targetOrbitChoice == "InterpolateOrbit") { let result = findOrbitThroughInterpolation(startingCoordinates, interpolationParameters, body); if (result) { targetCoordinates = result[0]; targetStartTime = startingOrbitalParameters.currentTimeAtReading + result[1]; } } else if (targetOrbitChoice = "KnownOrbit") { targetCoordinates = getCoordinatesFromParameters(targetOrbitalParameters, body); targetStartTime = targetOrbitalParameters.currentTimeAtReading; } if (startingCoordinates && targetCoordinates) { startTime = Math.max(currentTime, startingOrbitalParameters.currentTimeAtReading, targetStartTime); if (startTime > startingOrbitalParameters.currentTimeAtReading) { let addedTime = startTime - startingOrbitalParameters.currentTimeAtReading; startingCoordinates = extrapolateTrajectory(addedTime, startingCoordinates, body); } if (startTime > targetStartTime) { let addedTime = startTime - targetStartTime; targetCoordinates = extrapolateTrajectory(addedTime, targetCoordinates, body); } } if (startingCoordinates && targetCoordinates && startTime) { this.worker = new Worker(new URL('./worker.ts', import.meta.url), {type: 'module'}); this.worker.addEventListener("message", event => { let transferResponse = event.data as ProgressMessage; if (transferResponse) { if (transferResponse.finished) { searchButton.innerHTML = "Search for cheapest intercept"; this.worker = null; } if (transferResponse.bestTransfer) { transferResponse.bestTransfer.firstManoeuvre.time += startTime; transferResponse.bestTransfer.secondManoeuvre.time += startTime; } this.manoeuvresGui.displayProgress(transferResponse); } }); let workerMessage: FindBestInterceptMessage = { type: "FindBestIntercept", startingSituation: startingCoordinates, targetSituation: targetCoordinates, body: body, additionalTrueAnomaly: additionalTrueAnomaly }; this.worker.postMessage(workerMessage); } else { this.manoeuvresGui.displayProgress({ type: "ProgressMessage", finished: true, percentDone: 100, bestDeltaV: null, bestTransfer: null }); } } }); this.sourceId = crypto.randomUUID(); this.additionalTrueAnomaly.listenToValue((source, value) => { if (source == this.sourceId) { return; } additionalTrueAnomalyInput.value = (value * 180 / Math.PI).toString(); }); additionalTrueAnomalyInput.addEventListener("change", () => { let additionalTrueAnomaly = parseFloat(additionalTrueAnomalyInput.value) * Math.PI / 180.0; this.additionalTrueAnomaly.set(additionalTrueAnomaly, this.sourceId); }); knownOrbitButton.addEventListener("change", () => targetOrbitChoice.set("KnownOrbit", this.sourceId)); interpolateOrbitButton.addEventListener("change", () => targetOrbitChoice.set("InterpolateOrbit", this.sourceId)); targetOrbitChoice.listenToValue((_, value) => { orbitContainer.innerHTML = ""; if (value == "KnownOrbit") { knownOrbitButton.setAttribute("checked", ""); orbitContainer.appendChild(knownOrbitContainer); } else if (value == "InterpolateOrbit") { interpolateOrbitButton.setAttribute("checked", ""); orbitContainer.appendChild(interpolateOrbitContainer); } }); } }