Added intercepts
This commit is contained in:
parent
30ede19096
commit
aa45755cfc
@ -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 {
|
||||
|
||||
@ -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 = [];
|
||||
|
||||
@ -609,3 +619,165 @@ 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
270
src/gui/intercept.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -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,9 +18,15 @@ 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;
|
||||
@ -34,9 +40,12 @@ ctx.addEventListener("message", event => {
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
28
src/main.ts
28
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -131,3 +142,14 @@ 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();
|
||||
})
|
||||
Loading…
x
Reference in New Issue
Block a user