import type { Body } from "../calculations/constants"; import { extrapolateTrajectory, findOrbitThroughInterpolation, type OrbitalCoordinates } from "../calculations/orbit-calculations"; import type { ChangingStorageValue } from "../storage"; import { createLabel, createNumberInput, createRadioButton, getCoordinatesFromParameters, type OrbitalParameters } from "./common"; import { Renderer } from "./renderer"; export interface InterpolationParameters { targetPeriapsis: number, orbitChoice: "apoapsis" | "speedAndAltitude", targetApoapsis: number, targetSpeed: number, targetAltitude: number, firstOwnAltitude: number, firstTargetAltitude: number, firstDistance: number, firstPhaseAngle: number, secondOwnAltitude: number, secondTargetAltitude: number, secondDistance: number, secondPhaseAngle: number, } export const DefaultInterpolationParameters: InterpolationParameters = { targetPeriapsis: 100000, orbitChoice: "apoapsis", targetApoapsis: 200000, targetSpeed: 0, targetAltitude: 0, firstOwnAltitude: 0, firstTargetAltitude: 0, firstDistance: 0, firstPhaseAngle: 0, secondOwnAltitude: 0, secondTargetAltitude: 0, secondDistance: 0, secondPhaseAngle: 0 } export class InterpolateOrbitGui { parentDiv: HTMLDivElement; startingOrbitalParameters: ChangingStorageValue; body: ChangingStorageValue; interpolationParameters: ChangingStorageValue; interpolatedOrbitCoordinates: ChangingStorageValue; interpolatedOrbitTime: ChangingStorageValue; sourceId: string; renderDiv: HTMLDivElement; constructor(interpolationParameters: ChangingStorageValue, startingOrbitParameters: ChangingStorageValue, interpolatedOrbitCoordinates: ChangingStorageValue, interpolatedOrbitTime: ChangingStorageValue, body: ChangingStorageValue) { this.parentDiv = document.createElement("div"); this.startingOrbitalParameters = startingOrbitParameters; this.interpolationParameters = interpolationParameters; this.interpolatedOrbitCoordinates = interpolatedOrbitCoordinates; this.interpolatedOrbitTime = interpolatedOrbitTime; this.renderDiv = document.createElement("div"); this.body = body; let targetOrbitHeader = document.createElement("h4"); targetOrbitHeader.appendChild(document.createTextNode("Describe target orbit:")); this.parentDiv.appendChild(targetOrbitHeader); let addToParent = (elements: HTMLElement[]) => { let container = document.createElement("div"); container.classList.add("orbitalParameter"); elements.forEach(child => container.appendChild(child)); this.parentDiv.appendChild(container); } let createInputWithLabel = (label: string, minimum: number, maximum?: number, size?: number) => { let inputId = crypto.randomUUID(); let labelElement = createLabel(inputId, label); let numberInput = createNumberInput(inputId, minimum, maximum, size); addToParent([labelElement, numberInput]); return numberInput; } let periapsisInput = createInputWithLabel("Target peripasis:", 0); let descriptorChoiceId = crypto.randomUUID(); let apoapsisButtonId = crypto.randomUUID(); let apoapsisButton = createRadioButton(descriptorChoiceId, apoapsisButtonId); let apoapsisButtonLabel = createLabel(apoapsisButtonId, "Use apoapsis"); let speedAndAltitudeId = crypto.randomUUID(); let speedAndAltitudeButton = createRadioButton(descriptorChoiceId, speedAndAltitudeId); let speedAndAltitudeLabel = createLabel(speedAndAltitudeId, "Use speed and altitude"); addToParent([apoapsisButton, apoapsisButtonLabel, speedAndAltitudeButton, speedAndAltitudeLabel]); let apoapsisInput = createInputWithLabel("Target apopasis:", 0); let speedInput = createInputWithLabel("Target speed:", 0); let altitudeInput = createInputWithLabel("Target altitude:", 0); let instantOneHeader = document.createElement("h4"); instantOneHeader.appendChild(document.createTextNode("Measurement one:")); this.parentDiv.appendChild(instantOneHeader); let firstOwnAltitudeInput = createInputWithLabel("Own altitude:", 0); let firstTargetAltitudeInput = createInputWithLabel("Target altitude:", 0); let firstDistanceInput = createInputWithLabel("Distance to target:", 0); let firstPhaseAngleInput = createInputWithLabel("Phase angle:", -180, 180); let instantTwoHeader = document.createElement("h4"); instantTwoHeader.appendChild(document.createTextNode("Measurement two:")); this.parentDiv.appendChild(instantTwoHeader); let secondOwnAltitudeInput = createInputWithLabel("Own altitude:", 0); let secondTargetAltitudeInput = createInputWithLabel("Target altitude:", 0); let secondDistanceInput = createInputWithLabel("Distance to target:", 0); let secondPhaseAngleInput = createInputWithLabel("Phase angle:", -180, 180); let interpolateOrbitButton = document.createElement("button"); interpolateOrbitButton.appendChild(document.createTextNode("Find interpolated orbit")); this.parentDiv.appendChild(interpolateOrbitButton); this.parentDiv.appendChild(this.renderDiv); this.sourceId = crypto.randomUUID(); const onChange = () => { let periapsis = parseFloat(periapsisInput.value); let apoapsis = parseFloat(apoapsisInput.value); let speed = parseFloat(speedInput.value); let altitude = parseFloat(altitudeInput.value); let firstTargetAltitude = parseFloat(firstTargetAltitudeInput.value); let firstOwnAltitude = parseFloat(firstOwnAltitudeInput.value); let firstDistance = parseFloat(firstDistanceInput.value); let firstPhaseAngle = parseFloat(firstPhaseAngleInput.value) * Math.PI / 180.0; let secondTargetAltitude = parseFloat(secondTargetAltitudeInput.value); let secondOwnAltitude = parseFloat(secondOwnAltitudeInput.value); let secondDistance = parseFloat(secondDistanceInput.value); let secondPhaseAngle = parseFloat(secondPhaseAngleInput.value) * Math.PI / 180.0; let orbitChoice: "apoapsis" | "speedAndAltitude" = "apoapsis"; if (speedAndAltitudeButton.checked) { orbitChoice = "speedAndAltitude" }; interpolationParameters.set({ targetPeriapsis: periapsis, orbitChoice: orbitChoice, targetApoapsis: apoapsis, targetSpeed: speed, targetAltitude: altitude, firstTargetAltitude: firstTargetAltitude, firstOwnAltitude: firstOwnAltitude, firstDistance: firstDistance, firstPhaseAngle: firstPhaseAngle, secondTargetAltitude: secondTargetAltitude, secondOwnAltitude: secondOwnAltitude, secondDistance: secondDistance, secondPhaseAngle: secondPhaseAngle }, this.sourceId) }; [ periapsisInput, apoapsisButton, speedAndAltitudeButton, apoapsisInput, speedInput, altitudeInput, firstOwnAltitudeInput, firstTargetAltitudeInput, firstDistanceInput, firstPhaseAngleInput, secondOwnAltitudeInput, secondTargetAltitudeInput, secondDistanceInput, secondPhaseAngleInput ].forEach(input => input.addEventListener("change", onChange)); interpolationParameters.listenToValue((source, value) => { if (value.orbitChoice == "apoapsis") { apoapsisInput.removeAttribute("disabled"); speedInput.setAttribute("disabled", ""); altitudeInput.setAttribute("disabled", ""); apoapsisButton.setAttribute("checked", ""); } else if (value.orbitChoice == "speedAndAltitude") { apoapsisInput.setAttribute("disabled", ""); speedInput.removeAttribute("disabled"); altitudeInput.removeAttribute("disabled"); speedAndAltitudeButton.setAttribute("checked", ""); } if (source == this.sourceId) { return; } periapsisInput.value = value.targetPeriapsis.toString(); apoapsisInput.value = value.targetApoapsis.toString(); speedInput.value = value.targetSpeed.toString(); altitudeInput.value = value.targetAltitude.toString(); firstOwnAltitudeInput.value = value.firstOwnAltitude.toString(); firstTargetAltitudeInput.value = value.firstTargetAltitude.toString(); firstDistanceInput.value = value.firstDistance.toString(); firstPhaseAngleInput.value = (value.firstPhaseAngle * 180 / Math.PI).toString(); secondOwnAltitudeInput.value = value.secondOwnAltitude.toString(); secondTargetAltitudeInput.value = value.secondTargetAltitude.toString(); secondDistanceInput.value = value.secondDistance.toString(); secondPhaseAngleInput.value = (value.secondPhaseAngle * 180 / Math.PI).toString(); }); // Interpolation action interpolateOrbitButton.addEventListener("click", _ => { this.renderDiv.innerHTML = ""; let startingOrbitalParameters = this.startingOrbitalParameters.getCurrentValue(); let interpolationParameters = structuredClone(this.interpolationParameters.getCurrentValue()); let body = this.body.getCurrentValue(); // All interpolation altitude parameters need to get the planet radius added to them interpolationParameters.firstTargetAltitude += body.radius; interpolationParameters.firstOwnAltitude += body.radius; interpolationParameters.secondTargetAltitude += body.radius; interpolationParameters.secondOwnAltitude += body.radius; interpolationParameters.targetAltitude += body.radius; interpolationParameters.targetApoapsis += body.radius; interpolationParameters.targetPeriapsis += body.radius; let ownCoordinates = getCoordinatesFromParameters(startingOrbitalParameters, body); let results = findOrbitThroughInterpolation(ownCoordinates, interpolationParameters, body); if (results.length == 1) { this.interpolatedOrbitCoordinates.set(results[0][0], "null"); this.interpolatedOrbitTime.set(results[0][1], "null"); } else { let chooseHeader = document.createElement("h3"); chooseHeader.appendChild(document.createTextNode("Choose interpolated orbit:")); this.renderDiv.appendChild(chooseHeader); let interpolationChoiceId = crypto.randomUUID(); results.forEach(([coordinates, time]) => { let buttonId = crypto.randomUUID(); let button = createRadioButton(interpolationChoiceId, buttonId); let label = createLabel(buttonId, "The orbit below fits best"); let renderer = new Renderer(body); let extrapolatedOwnCoordinates = extrapolateTrajectory(time, ownCoordinates, body); let usedCoordinates = ownCoordinates; if (extrapolatedOwnCoordinates) { usedCoordinates = extrapolatedOwnCoordinates; } renderer.addCoordinates(usedCoordinates, 0x00ffff); renderer.addCoordinates(coordinates, 0xff00ff); this.renderDiv.appendChild(button); this.renderDiv.appendChild(label); this.renderDiv.appendChild(renderer.parentDiv); this.renderDiv.appendChild(document.createElement("br")); button.addEventListener("change", _ => { this.interpolatedOrbitCoordinates.set(coordinates, "null"); this.interpolatedOrbitTime.set(startingOrbitalParameters.currentTimeAtReading + time, "null"); }); }); } }); } }