Added intercepts
This commit is contained in:
parent
30ede19096
commit
aa45755cfc
@ -86,6 +86,28 @@ export const Minmus: Body = {
|
|||||||
initialMeridianLongitude: 4.014486824
|
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>([
|
export const PlanetList = new Map<string, Body>([
|
||||||
[Kerbol.planetName, Kerbol],
|
[Kerbol.planetName, Kerbol],
|
||||||
[Moho.planetName, Moho],
|
[Moho.planetName, Moho],
|
||||||
@ -93,7 +115,9 @@ export const PlanetList = new Map<string, Body>([
|
|||||||
[Gilly.planetName, Gilly],
|
[Gilly.planetName, Gilly],
|
||||||
[Kerbin.planetName, Kerbin],
|
[Kerbin.planetName, Kerbin],
|
||||||
[Mun.planetName, Mun],
|
[Mun.planetName, Mun],
|
||||||
[Minmus.planetName, Minmus]
|
[Minmus.planetName, Minmus],
|
||||||
|
[Duna.planetName, Duna],
|
||||||
|
[Ike.planetName, Ike]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export function getPlanetByName(name: string): Body {
|
export function getPlanetByName(name: string): Body {
|
||||||
|
|||||||
@ -115,7 +115,7 @@ export class LambertSolutions {
|
|||||||
this.goalVelocity = multiplyMatrixWithScalar(goalSpeed, this.goalLocalVectors.prograde);
|
this.goalVelocity = multiplyMatrixWithScalar(goalSpeed, this.goalLocalVectors.prograde);
|
||||||
|
|
||||||
this.extremalGamma = -(this.positionOneMagnitude*this.positionTwoMagnitude - vectorDotProduct(this.positionOne, this.positionTwo)) / vectorDotProduct(this.normalVector, (crossProduct));
|
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 {
|
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 {
|
export function getTimeBetweenTrueAnomalies(startingTrueAnomaly: number, endingTrueAnomaly: number, orbit: Orbit, planet: Body): number {
|
||||||
|
let extraTime = 0;
|
||||||
if (Math.abs(orbit.eccentricity - 1) < 0.00001) {
|
if (Math.abs(orbit.eccentricity - 1) < 0.00001) {
|
||||||
// Parabola. Solve using Barker's equation
|
// Parabola. Solve using Barker's equation
|
||||||
const startingD = Math.tan(startingTrueAnomaly / 2);
|
const startingD = Math.tan(startingTrueAnomaly / 2);
|
||||||
@ -364,6 +365,13 @@ export function getTimeBetweenTrueAnomalies(startingTrueAnomaly: number, endingT
|
|||||||
while (endingMeanAnomaly < startingMeanAnomaly) {
|
while (endingMeanAnomaly < startingMeanAnomaly) {
|
||||||
endingMeanAnomaly += 2*Math.PI;
|
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 {
|
} else {
|
||||||
const startingEccentricAnomaly = 2*Math.atanh(Math.sqrt((orbit.eccentricity - 1)/(orbit.eccentricity + 1)) * Math.tan(startingTrueAnomaly / 2));
|
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));
|
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 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;
|
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;
|
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
|
// First, create a set of starting true anomalies
|
||||||
let startingTrueAnomalies = [];
|
let startingTrueAnomalies = [];
|
||||||
|
|
||||||
@ -609,3 +619,165 @@ export function findCheapestTransfer(startingSituation: OrbitalCoordinates, targ
|
|||||||
|
|
||||||
return bestTransfer;
|
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 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;
|
const ctx: Worker = self as any;
|
||||||
|
|
||||||
@ -18,25 +18,34 @@ export interface FindBestTransferResponse {
|
|||||||
bestTransfer: Transfer | null
|
bestTransfer: Transfer | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FindBestInterceptMessage {
|
||||||
|
type: "FindBestIntercept",
|
||||||
|
startingSituation: OrbitalCoordinates,
|
||||||
|
targetSituation: OrbitalCoordinates,
|
||||||
|
additionalTrueAnomaly: number,
|
||||||
|
body: Body;
|
||||||
|
}
|
||||||
|
|
||||||
ctx.addEventListener("message", event => {
|
ctx.addEventListener("message", event => {
|
||||||
let findBestTransferMessage = event.data as FindBestTransferMessage;
|
const progressCallback = (numberChecked: number, totalNumber: number, bestDeltaV: number | null, bestTransfer: Transfer | null) => {
|
||||||
if (findBestTransferMessage) {
|
if (numberChecked % 100 == 0) {
|
||||||
const progressCallback = (numberChecked: number, totalNumber: number, bestDeltaV: number | null, bestTransfer: Transfer | null) => {
|
let percentDone = numberChecked * 100 / totalNumber;
|
||||||
if (numberChecked % 100 == 0) {
|
let message: FindBestTransferResponse = {
|
||||||
let percentDone = numberChecked * 100 / totalNumber;
|
type: "FindBestTransferResponse",
|
||||||
let message: FindBestTransferResponse = {
|
finished: false,
|
||||||
type: "FindBestTransferResponse",
|
percentDone: percentDone,
|
||||||
finished: false,
|
bestDeltaV: bestDeltaV,
|
||||||
percentDone: percentDone,
|
bestTransfer: bestTransfer
|
||||||
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;
|
let bestDeltaV = null;
|
||||||
if (bestTransfer) {
|
if (bestTransfer) {
|
||||||
bestDeltaV = bestTransfer.firstManoeuvre.totalDeltaV + bestTransfer.secondManoeuvre.totalDeltaV;
|
bestDeltaV = bestTransfer.firstManoeuvre.totalDeltaV + bestTransfer.secondManoeuvre.totalDeltaV;
|
||||||
@ -51,4 +60,23 @@ ctx.addEventListener("message", event => {
|
|||||||
};
|
};
|
||||||
ctx.postMessage(finishedMessage);
|
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 { createLocalStorageVariable } from "./storage";
|
||||||
import { SimplePlaneChangeGui } from "./gui/simpleplanechange";
|
import { SimplePlaneChangeGui } from "./gui/simpleplanechange";
|
||||||
import { TargetOrbitGui } from "./gui/targetorbit";
|
import { TargetOrbitGui } from "./gui/targetorbit";
|
||||||
|
import { InterceptTargetGui } from "./gui/intercept";
|
||||||
|
|
||||||
type CalculationType = "planeChange" | "targetOrbit";
|
type CalculationType = "planeChange" | "targetOrbit" | "intercept";
|
||||||
function decodeCalculationType(input: string): CalculationType {
|
function decodeCalculationType(input: string): CalculationType {
|
||||||
let calculationType = input as CalculationType;
|
let calculationType = input as CalculationType;
|
||||||
if (calculationType !== undefined) {
|
if (calculationType !== undefined) {
|
||||||
@ -24,9 +25,10 @@ let [timeToPeriapsis, setTimeToPeriapsis] = createLocalStorageVariable("timeToPe
|
|||||||
let [planet, setPlanet] = createLocalStorageVariable("planet", getPlanetByName, p => p.planetName, Kerbol);
|
let [planet, setPlanet] = createLocalStorageVariable("planet", getPlanetByName, p => p.planetName, Kerbol);
|
||||||
let [orbitalParameters, setOrbitalParameters] = createLocalStorageVariable("orbitalParameters", decodeOrbitalParameters, encodeOrbitalParameters, DefaultOrbitalParameters);
|
let [orbitalParameters, setOrbitalParameters] = createLocalStorageVariable("orbitalParameters", decodeOrbitalParameters, encodeOrbitalParameters, DefaultOrbitalParameters);
|
||||||
let [calculationType, setCalculationType] = createLocalStorageVariable("calculationType", decodeCalculationType, s => s, "planeChange");
|
let [calculationType, setCalculationType] = createLocalStorageVariable("calculationType", decodeCalculationType, s => s, "planeChange");
|
||||||
|
|
||||||
let [targetOrbitalParameters, setTargetOrbitalParameters] = createLocalStorageVariable("targetOrbitalParameters", decodeOrbitalParameters, encodeOrbitalParameters, DefaultOrbitalParameters);
|
let [targetOrbitalParameters, setTargetOrbitalParameters] = createLocalStorageVariable("targetOrbitalParameters", decodeOrbitalParameters, encodeOrbitalParameters, DefaultOrbitalParameters);
|
||||||
let [circularizeOrbit, setCircularizeOrbit] = createLocalStorageVariable("circularizeOrbit", s => s == "true", bool => bool ? "true" : "false", false)
|
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 dateInputContainerCreator = () => {
|
||||||
let dateInputContainer = document.createElement("span");
|
let dateInputContainer = document.createElement("span");
|
||||||
@ -79,6 +81,13 @@ simplePlaneChangeGui.addListener((targetInclination, targetLongitudeOfAscendingN
|
|||||||
const targetOrbitGui = new TargetOrbitGui(orbitalParameters, targetOrbitalParameters, currentTime, timeToPeriapsis, planet);
|
const targetOrbitGui = new TargetOrbitGui(orbitalParameters, targetOrbitalParameters, currentTime, timeToPeriapsis, planet);
|
||||||
targetOrbitGui.addListener(setTargetOrbitalParameters);
|
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() {
|
function populateCalculationDiv() {
|
||||||
calculationDiv.innerHTML = "";
|
calculationDiv.innerHTML = "";
|
||||||
calculationDiv.className = "";
|
calculationDiv.className = "";
|
||||||
@ -87,8 +96,10 @@ function populateCalculationDiv() {
|
|||||||
simplePlaneChangeGui.addToParentElement(calculationDiv);
|
simplePlaneChangeGui.addToParentElement(calculationDiv);
|
||||||
} else if (calculationType.value == "targetOrbit") {
|
} else if (calculationType.value == "targetOrbit") {
|
||||||
calculationDiv.classList.add("targetOrbit");
|
calculationDiv.classList.add("targetOrbit");
|
||||||
targetOrbitGui
|
|
||||||
targetOrbitGui.addToParentElement(calculationDiv);
|
targetOrbitGui.addToParentElement(calculationDiv);
|
||||||
|
} else if (calculationType.value == "intercept") {
|
||||||
|
calculationDiv.classList.add("intercept");
|
||||||
|
interceptTargetGui.addToParentElement(calculationDiv);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,3 +142,14 @@ targetOrbitButton.addEventListener("change", () => {
|
|||||||
setCalculationType("targetOrbit");
|
setCalculationType("targetOrbit");
|
||||||
populateCalculationDiv();
|
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