Added intercepts

This commit is contained in:
Martin Asprusten 2026-03-29 22:47:14 +02:00
parent 30ede19096
commit aa45755cfc
No known key found for this signature in database
5 changed files with 540 additions and 24 deletions

View File

@ -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<string, Body>([
[Kerbol.planetName, Kerbol],
[Moho.planetName, Moho],
@ -93,7 +115,9 @@ export const PlanetList = new Map<string, Body>([
[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 {

View File

@ -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;
}

270
src/gui/intercept.ts Normal file
View File

@ -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<OrbitalParameters>;
goalParameters: Wrapper<OrbitalParameters>;
currentTime: Wrapper<number>;
timeToPeriapsis: Wrapper<number>;
body: Wrapper<Body>;
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<OrbitalParameters>, goalParameters: Wrapper<OrbitalParameters>, currentTime: Wrapper<number>, timeToPeriapsis: Wrapper<number>, body: Wrapper<Body>, 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);
}
}

View File

@ -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);
}
});

View File

@ -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();
});
});
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();
})