diff --git a/src/calculations/constants.ts b/src/calculations/constants.ts index d35a6d7..822868c 100644 --- a/src/calculations/constants.ts +++ b/src/calculations/constants.ts @@ -86,6 +86,28 @@ export const Minmus: Body = { initialMeridianLongitude: 4.014486824 }; +export const Duna: Body = { + planetName: "Duna", + type: "planet", + radius: 320000, + gravitationalParameter: 3.0136321e11, + rotationPeriod: 65517.859, + sphereOfInfluence: 47921949, + closestSafeDistance: 50000, + initialMeridianLongitude: 0 // TODO: Fill in later +} + +export const Ike: Body = { + planetName: "Ike", + type: "moon", + radius: 130000, + gravitationalParameter: 1.8568369e10, + rotationPeriod: 65517.852, + sphereOfInfluence: 1049598.9, + closestSafeDistance: 12900, + initialMeridianLongitude: 0 // TODO: Fill in later +} + export const PlanetList = new Map([ [Kerbol.planetName, Kerbol], [Moho.planetName, Moho], @@ -93,7 +115,9 @@ export const PlanetList = new Map([ [Gilly.planetName, Gilly], [Kerbin.planetName, Kerbin], [Mun.planetName, Mun], - [Minmus.planetName, Minmus] + [Minmus.planetName, Minmus], + [Duna.planetName, Duna], + [Ike.planetName, Ike] ]); export function getPlanetByName(name: string): Body { diff --git a/src/calculations/orbit-calculations.ts b/src/calculations/orbit-calculations.ts index 46f6a79..25d9053 100644 --- a/src/calculations/orbit-calculations.ts +++ b/src/calculations/orbit-calculations.ts @@ -115,7 +115,7 @@ export class LambertSolutions { this.goalVelocity = multiplyMatrixWithScalar(goalSpeed, this.goalLocalVectors.prograde); this.extremalGamma = -(this.positionOneMagnitude*this.positionTwoMagnitude - vectorDotProduct(this.positionOne, this.positionTwo)) / vectorDotProduct(this.normalVector, (crossProduct)); - this.parabolaGamma = Math.sqrt(2*this.positionOneMagnitude*this.positionTwoMagnitude - vectorDotProduct(this.positionOne, this.positionTwo)) / getVectorMagnitude(addVector(this.positionOne, this.positionTwo)); + this.parabolaGamma = Math.sqrt(2*(this.positionOneMagnitude*this.positionTwoMagnitude - vectorDotProduct(this.positionOne, this.positionTwo))) / getVectorMagnitude(addVector(this.positionOne, this.positionTwo)); } getTransfer(gamma: number): Transfer { @@ -334,6 +334,7 @@ export function getOrbitalCoordinates(timeToPeriapsis: number, orbit: Orbit, pla } export function getTimeBetweenTrueAnomalies(startingTrueAnomaly: number, endingTrueAnomaly: number, orbit: Orbit, planet: Body): number { + let extraTime = 0; if (Math.abs(orbit.eccentricity - 1) < 0.00001) { // Parabola. Solve using Barker's equation const startingD = Math.tan(startingTrueAnomaly / 2); @@ -364,6 +365,13 @@ export function getTimeBetweenTrueAnomalies(startingTrueAnomaly: number, endingT while (endingMeanAnomaly < startingMeanAnomaly) { endingMeanAnomaly += 2*Math.PI; } + + // Add extra orbits if necessary + if (endingTrueAnomaly > startingTrueAnomaly + 2 * Math.PI) { + let orbitalPeriod = 2 * Math.PI * Math.sqrt(orbit.semiLatusRectum**3 / (planet.gravitationalParameter * (1 - orbit.eccentricity**2)**3)); + let extraOrbits = Math.floor((endingTrueAnomaly - startingTrueAnomaly) / (2 * Math.PI)); + extraTime = extraOrbits * orbitalPeriod; + } } else { const startingEccentricAnomaly = 2*Math.atanh(Math.sqrt((orbit.eccentricity - 1)/(orbit.eccentricity + 1)) * Math.tan(startingTrueAnomaly / 2)); const endingEccentricAnomaly = 2*Math.atanh(Math.sqrt((orbit.eccentricity - 1)/(orbit.eccentricity + 1)) * Math.tan(endingTrueAnomaly / 2)); @@ -375,7 +383,7 @@ export function getTimeBetweenTrueAnomalies(startingTrueAnomaly: number, endingT const startingTime = Math.sqrt(orbit.semiLatusRectum**3 / (planet.gravitationalParameter * Math.abs(1 - orbit.eccentricity**2)**3)) * startingMeanAnomaly; const endingTime = Math.sqrt(orbit.semiLatusRectum**3 / (planet.gravitationalParameter * Math.abs(1 - orbit.eccentricity**2)**3)) * endingMeanAnomaly; - return endingTime - startingTime; + return endingTime - startingTime + extraTime; } } @@ -524,7 +532,9 @@ export function findCheapestLambertSolution(lambertSolutions: LambertSolutions): return bestTransfer; } -export function findCheapestTransfer(startingSituation: OrbitalCoordinates, targetOrbit: Orbit, body: Body, progressCallback?: (anomaliesChecked: number, totalAnomalies: number, currentBestDeltaV: number | null, currentBestTransfer: Transfer | null) => void): Transfer | null { +export type ProgressCallbackFunction = (transfersChecked: number, totalNumberOfTransfers: number, currentBestDeltaV: number | null, currentBestTransfer: Transfer | null) => void; + +export function findCheapestTransfer(startingSituation: OrbitalCoordinates, targetOrbit: Orbit, body: Body, progressCallback?: ProgressCallbackFunction): Transfer | null { // First, create a set of starting true anomalies let startingTrueAnomalies = []; @@ -607,5 +617,167 @@ export function findCheapestTransfer(startingSituation: OrbitalCoordinates, targ }) }); + return bestTransfer; +} + +export function findLambertSolutionsWithCorrectTime(lambertSolutions: LambertSolutions, expectedTime: number): Transfer | null{ + const step = lambertSolutions.parabolaGamma - lambertSolutions.extremalGamma; + + const getGamma = (steps: number) => { + return lambertSolutions.extremalGamma + steps*step; + } + + let lowerSteps = 0; + let lowerTransfer = lambertSolutions.getTransfer(getGamma(lowerSteps)); + let lowerTime = 0; + + let upperSteps = 1; + let upperTransfer = lambertSolutions.getTransfer(getGamma(upperSteps)); + let upperTime = upperTransfer.secondManoeuvre.time; + + let foundWindow = false; + // Find window that contains transfer + for (let windowAttempts = 0; windowAttempts < 100; windowAttempts++) { + if (upperTime < 0 || expectedTime < upperTime) { + foundWindow = true; + break; + } + + lowerSteps = upperSteps; + lowerTransfer = upperTransfer; + lowerTime = upperTime; + + upperSteps += 1; + upperTransfer = lambertSolutions.getTransfer(getGamma(upperSteps)); + upperTime = upperTransfer.secondManoeuvre.time; + } + + if (!foundWindow) { + return null; + } + + const acceptablyClose = 0.5; + const numberOfAttempts = 200; + + for (let attempts = 0; attempts < numberOfAttempts; attempts++) { + if (Math.abs(lowerTime - expectedTime) < acceptablyClose) { + return lowerTransfer; + } + + if (Math.abs(upperTime - expectedTime) < acceptablyClose) { + return upperTransfer; + } + + let middleSteps = (upperSteps + lowerSteps) / 2; + let middleTransfer = lambertSolutions.getTransfer(getGamma(middleSteps)); + let middleTime = middleTransfer.secondManoeuvre.time; + + if (middleTime > 0 && middleTime < expectedTime) { + lowerSteps = middleSteps; + lowerTransfer = middleTransfer; + lowerTime = middleTime; + } else { + upperSteps = middleSteps; + upperTransfer = middleTransfer; + upperTime = middleTime; + } + } + + return null; +} + +export function findCheapestIntercept(startingSituation: OrbitalCoordinates, targetSituation: OrbitalCoordinates, body: Body, extraTrueAnomaly: number, progressCallback?: ProgressCallbackFunction): Transfer | null { + // Find acceptable intercept times + // First number is intercept time, second number is true anomaly + let starts: [number, number][] = []; + let intercepts: [number, number][] = []; + + // Check if the starting orbit is stable0 + let startingOrbitStable = false; + if (startingSituation.orbit.eccentricity < 1) { + let startingApoapsis = startingSituation.orbit.semiLatusRectum / (1 - startingSituation.orbit.eccentricity); + if (startingApoapsis < body.sphereOfInfluence) { + startingOrbitStable = true; + } + } + + // Next, check if intercept orbit is stable + let interceptOrbitStable = false; + if (targetSituation.orbit.eccentricity < 1) { + let targetApoapsis = targetSituation.orbit.semiLatusRectum / (1 - targetSituation.orbit.eccentricity); + if (targetApoapsis < body.sphereOfInfluence) { + interceptOrbitStable = true; + } + } + + 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); + + 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; + } + + 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; + } + } + + let pairs: [number, number, number, number][] = []; + starts.forEach(([startingTime, startingTrueAnomaly]) => { + intercepts.forEach(([endingTime, endingTrueAnomaly]) => { + if (endingTime > startingTime) { + pairs.push([startingTime, startingTrueAnomaly, endingTime, endingTrueAnomaly]); + } + }); + }); + + let bestDeltaV: number | null = null; + let bestTransfer: Transfer | null = null; + + pairs.forEach(([startingTime, startingTrueAnomaly, endingTime, endingTrueAnomaly], index) => { + let transferTime = endingTime - startingTime; + [true, false].forEach(goBackwards => { + let lambertSolutions = new LambertSolutions(startingSituation.orbit, startingTrueAnomaly, targetSituation.orbit, endingTrueAnomaly, body, goBackwards); + let transfer = findLambertSolutionsWithCorrectTime(lambertSolutions, transferTime); + if (transfer !== null && transfer.closestPointDistance > body.closestSafeDistance && transfer.farthestPointDistance < body.sphereOfInfluence) { + let totalDeltaV = transfer.firstManoeuvre.totalDeltaV + transfer.secondManoeuvre.totalDeltaV; + if (bestDeltaV == null || totalDeltaV < bestDeltaV) { + bestDeltaV = totalDeltaV; + bestTransfer = transfer; + + bestTransfer.firstManoeuvre.time += startingTime; + bestTransfer.secondManoeuvre.time += startingTime; + } + } + }); + + if (progressCallback) { + progressCallback(index+1, pairs.length, bestDeltaV, bestTransfer); + } + }); + return bestTransfer; } \ No newline at end of file diff --git a/src/gui/intercept.ts b/src/gui/intercept.ts new file mode 100644 index 0000000..535c0da --- /dev/null +++ b/src/gui/intercept.ts @@ -0,0 +1,270 @@ +import { getOrbitalCoordinates } from "../calculations/orbit-calculations"; +import type { Wrapper } from "../storage"; +import { createDisabledInput, createLabel, createNumberInput, getOrbitFromParameters, type OrbitalParameters } from "./common"; +import { OrbitalParametersGui } from "./orbit"; +import { type Body } from "../calculations/constants"; +import type { FindBestInterceptMessage, FindBestTransferResponse } from "./worker"; +import { TimeGui } from "./time"; + +export class InterceptTargetGui { + listeners: ((newTargetOrbitalParameters: OrbitalParameters, newTargetTimeToPeriapsis: number, newAdditionalTrueAnomaly: number) => void)[]; + + startingParameters: Wrapper; + goalParameters: Wrapper; + currentTime: Wrapper; + timeToPeriapsis: Wrapper; + body: Wrapper; + targetTimeToPeriapsis: number; + additionalTrueAnomaly: number; + + orbitGui: OrbitalParametersGui; + timeGui: TimeGui; + parentDiv: HTMLDivElement; + + progressParagraph: HTMLParagraphElement; + + firstManoeuvreTime: HTMLInputElement; + firstManoeuvrePrograde: HTMLInputElement; + firstManoeuvreNormal: HTMLInputElement; + firstManoeuvreRadial: HTMLInputElement; + firstManoeuvreTotal: HTMLInputElement; + + secondManoeuvreTime: HTMLInputElement; + secondManoeuvrePrograde: HTMLInputElement; + secondManoeuvreNormal: HTMLInputElement; + secondManoeuvreRadial: HTMLInputElement; + secondManoeuvreTotal: HTMLInputElement; + + 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; + this.currentTime = currentTime; + this.timeToPeriapsis = timeToPeriapsis; + this.body = body; + + 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 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 additionalTrueAnomalyId = crypto.randomUUID(); + let additionalTrueAnomalyLabel = createLabel(additionalTrueAnomalyId, "Additional true anomaly (0 for intercept):"); + let additionalTrueAnomalyInput = createNumberInput(additionalTrueAnomalyId, -360, 360); + + 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); + + 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; + + 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 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); + + this.worker = new Worker(new URL('./worker.ts', import.meta.url), {type: 'module'}); + this.worker.addEventListener("message", event => { + let transferResponse = event.data as FindBestTransferResponse; + 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); + } + } + }); + + let workerMessage: FindBestInterceptMessage = { + type: "FindBestIntercept", + startingSituation: currentCoordinates, + targetSituation: targetCoordinates, + body: body.value, + additionalTrueAnomaly: this.additionalTrueAnomaly + }; + + this.worker.postMessage(workerMessage); + } + }); + + // Add some listeners + this.orbitGui.addListener((newValue) => { + this.listeners.forEach(listener => { + listener(newValue, this.targetTimeToPeriapsis, this.additionalTrueAnomaly); + }) + }); + + this.timeGui.addListener((newDate: number) => { + this.targetTimeToPeriapsis = newDate; + this.listeners.forEach(listener => { + listener(goalParameters.value, newDate, this.additionalTrueAnomaly); + }) + }); + + additionalTrueAnomalyInput.addEventListener("change", () => { + this.additionalTrueAnomaly = parseFloat(additionalTrueAnomalyInput.value) * Math.PI / 180.0; + this.listeners.forEach(listener => { + listener(goalParameters.value, this.targetTimeToPeriapsis, this.additionalTrueAnomaly); + }) + }); + } + + addToParentElement(parentElement: HTMLElement) { + this.orbitGui.setValues(this.goalParameters.value); + parentElement.appendChild(this.parentDiv); + } + + addListener(listener: (newValue: OrbitalParameters, newTargetTimeToPeriapsis: number, newAdditionalTrueAnomaly: number) => void) { + this.listeners.push(listener); + } +} \ No newline at end of file diff --git a/src/gui/worker.ts b/src/gui/worker.ts index ec61b99..f98bc6d 100644 --- a/src/gui/worker.ts +++ b/src/gui/worker.ts @@ -1,5 +1,5 @@ import type { Body } from "../calculations/constants"; -import { findCheapestTransfer, type Orbit, type OrbitalCoordinates, type Transfer } from "../calculations/orbit-calculations"; +import { findCheapestIntercept, findCheapestTransfer, type Orbit, type OrbitalCoordinates, type Transfer } from "../calculations/orbit-calculations"; const ctx: Worker = self as any; @@ -18,25 +18,34 @@ export interface FindBestTransferResponse { bestTransfer: Transfer | null } +export interface FindBestInterceptMessage { + type: "FindBestIntercept", + startingSituation: OrbitalCoordinates, + targetSituation: OrbitalCoordinates, + additionalTrueAnomaly: number, + body: Body; +} + ctx.addEventListener("message", event => { - let findBestTransferMessage = event.data as FindBestTransferMessage; - if (findBestTransferMessage) { - 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", - finished: false, - percentDone: percentDone, - bestDeltaV: bestDeltaV, - bestTransfer: bestTransfer - }; + 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", + finished: false, + percentDone: percentDone, + bestDeltaV: bestDeltaV, + bestTransfer: bestTransfer + }; - ctx.postMessage(message); - } + ctx.postMessage(message); } + }; - let bestTransfer = findCheapestTransfer(findBestTransferMessage.startingSituation, findBestTransferMessage.targetOrbit, findBestTransferMessage.body, progressCallback); + let message = event.data as FindBestTransferMessage | FindBestInterceptMessage; + if (message.type == "FindBestTransfer") { + console.log("Finding best transfer"); + let bestTransfer = findCheapestTransfer(message.startingSituation, message.targetOrbit, message.body, progressCallback); let bestDeltaV = null; if (bestTransfer) { bestDeltaV = bestTransfer.firstManoeuvre.totalDeltaV + bestTransfer.secondManoeuvre.totalDeltaV; @@ -51,4 +60,23 @@ ctx.addEventListener("message", event => { }; ctx.postMessage(finishedMessage); } + + if (message.type == "FindBestIntercept") { + console.log("Finding best intercept"); + let bestIntercept = findCheapestIntercept(message.startingSituation, message.targetSituation, message.body, message.additionalTrueAnomaly, progressCallback); + let bestDeltaV = null; + if (bestIntercept) { + bestDeltaV = bestIntercept.firstManoeuvre.totalDeltaV + bestIntercept.secondManoeuvre.totalDeltaV; + } + + let finishedMessage: FindBestTransferResponse = { + type: "FindBestTransferResponse", + finished: true, + percentDone: 100, + bestDeltaV: bestDeltaV, + bestTransfer: bestIntercept + }; + + ctx.postMessage(finishedMessage); + } }); \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index aa679da..942c4ac 100644 --- a/src/main.ts +++ b/src/main.ts @@ -8,8 +8,9 @@ import { TimeGui } from "./gui/time"; import { createLocalStorageVariable } from "./storage"; import { SimplePlaneChangeGui } from "./gui/simpleplanechange"; import { TargetOrbitGui } from "./gui/targetorbit"; +import { InterceptTargetGui } from "./gui/intercept"; -type CalculationType = "planeChange" | "targetOrbit"; +type CalculationType = "planeChange" | "targetOrbit" | "intercept"; function decodeCalculationType(input: string): CalculationType { let calculationType = input as CalculationType; if (calculationType !== undefined) { @@ -24,9 +25,10 @@ let [timeToPeriapsis, setTimeToPeriapsis] = createLocalStorageVariable("timeToPe 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 dateInputContainerCreator = () => { let dateInputContainer = document.createElement("span"); @@ -79,6 +81,13 @@ simplePlaneChangeGui.addListener((targetInclination, targetLongitudeOfAscendingN 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 = ""; @@ -87,8 +96,10 @@ function populateCalculationDiv() { simplePlaneChangeGui.addToParentElement(calculationDiv); } else if (calculationType.value == "targetOrbit") { calculationDiv.classList.add("targetOrbit"); - targetOrbitGui targetOrbitGui.addToParentElement(calculationDiv); + } else if (calculationType.value == "intercept") { + calculationDiv.classList.add("intercept"); + interceptTargetGui.addToParentElement(calculationDiv); } } @@ -130,4 +141,15 @@ if (calculationType.value == "targetOrbit") { targetOrbitButton.addEventListener("change", () => { setCalculationType("targetOrbit"); populateCalculationDiv(); -}); \ No newline at end of file +}); + +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