mirror of
https://github.com/Krafpy/KSP-MGA-Planner.git
synced 2026-06-12 19:11:22 -07:00
Merge pull request #31 from Krafpy/trajectory-to-text
Trajectory to text
This commit is contained in:
Vendored
+2
-19
@@ -1,4 +1,4 @@
|
||||
class Button {
|
||||
export class Button {
|
||||
constructor(id) {
|
||||
this._btn = document.getElementById(id);
|
||||
}
|
||||
@@ -8,24 +8,7 @@ class Button {
|
||||
disable() {
|
||||
this._btn.disabled = true;
|
||||
}
|
||||
}
|
||||
export class SubmitButton extends Button {
|
||||
constructor(id) {
|
||||
super(id);
|
||||
}
|
||||
click(asyncAction) {
|
||||
this._btn.onclick = async () => {
|
||||
this.disable();
|
||||
await asyncAction();
|
||||
this.enable();
|
||||
};
|
||||
}
|
||||
}
|
||||
export class StopButton extends Button {
|
||||
constructor(id) {
|
||||
super(id);
|
||||
}
|
||||
click(action) {
|
||||
this._btn.onclick = () => action();
|
||||
this._btn.onclick = action;
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+81
@@ -0,0 +1,81 @@
|
||||
export class DraggableTextbox {
|
||||
static create(title, content) {
|
||||
for (const [id, item] of this.boxes) {
|
||||
if (item.title == title) {
|
||||
this.moveToFront(id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
const template = document.getElementById("draggable-text-template");
|
||||
const div = template.cloneNode(true);
|
||||
document.body.insertBefore(div, document.body.lastChild);
|
||||
const id = this.nextId;
|
||||
div.id = `draggable-text-${id}`;
|
||||
this.nextId++;
|
||||
this.boxes.set(id, { div, title });
|
||||
const header = div.getElementsByClassName("draggable-text-header")[0];
|
||||
const h = header.getElementsByClassName("draggable-text-title")[0];
|
||||
h.innerHTML = title;
|
||||
const textarea = div.getElementsByTagName("textarea")[0];
|
||||
textarea.value = content;
|
||||
const closeBtn = div.getElementsByClassName("draggable-close-btn")[0];
|
||||
closeBtn.onclick = () => {
|
||||
div.remove();
|
||||
this.boxes.delete(id);
|
||||
};
|
||||
const copyBtn = div.getElementsByClassName("draggable-copy-btn")[0];
|
||||
copyBtn.onclick = () => navigator.clipboard.writeText(content);
|
||||
header.onmousedown = (e) => {
|
||||
e = e || window.event;
|
||||
e.preventDefault();
|
||||
let x = e.clientX;
|
||||
let y = e.clientY;
|
||||
let left, top;
|
||||
const setPos = () => {
|
||||
div.style.top = top.toString() + "px";
|
||||
div.style.left = left.toString() + "px";
|
||||
};
|
||||
const clamp = (x, a, b) => x < a ? a : x > b ? b : x;
|
||||
document.onmouseup = () => {
|
||||
left = clamp(left, 0, window.innerWidth - header.offsetWidth);
|
||||
top = clamp(top, 0, window.innerHeight - header.offsetHeight);
|
||||
setPos();
|
||||
document.onmouseup = null;
|
||||
document.onmousemove = null;
|
||||
};
|
||||
document.onmousemove = (e) => {
|
||||
e = e || window.event;
|
||||
e.preventDefault();
|
||||
let dx = x - e.clientX;
|
||||
let dy = y - e.clientY;
|
||||
x = e.clientX;
|
||||
y = e.clientY;
|
||||
left = div.offsetLeft - dx;
|
||||
top = div.offsetTop - dy;
|
||||
setPos();
|
||||
};
|
||||
this.moveToFront(id);
|
||||
};
|
||||
textarea.onclick = () => this.moveToFront(id);
|
||||
this.moveToFront(id);
|
||||
this.startZ++;
|
||||
div.hidden = false;
|
||||
div.style.display = "";
|
||||
return id;
|
||||
}
|
||||
static moveToFront(id) {
|
||||
const item = this.boxes.get(id);
|
||||
const div0 = item.div;
|
||||
const z0 = parseInt(div0.style.zIndex);
|
||||
this.boxes.forEach(({ div }, _) => {
|
||||
const z = parseInt(div.style.zIndex);
|
||||
if (z > z0) {
|
||||
div.style.zIndex = (z - 1).toString();
|
||||
}
|
||||
});
|
||||
div0.style.zIndex = this.startZ.toString();
|
||||
}
|
||||
}
|
||||
DraggableTextbox.nextId = 0;
|
||||
DraggableTextbox.startZ = 9;
|
||||
DraggableTextbox.boxes = new Map();
|
||||
Vendored
+33
-8
@@ -9,12 +9,14 @@ import { BodySelector } from "./body-selector.js";
|
||||
import { EvolutionPlot } from "./plot.js";
|
||||
import { ProgressMessage } from "./progress-msg.js";
|
||||
import { SequenceSelector } from "./sequence-selector.js";
|
||||
import { SubmitButton, StopButton } from "./buttons.js";
|
||||
import { Button } from "./buttons.js";
|
||||
import { FlybySequence } from "../solvers/sequence.js";
|
||||
import { Trajectory } from "../solvers/trajectory.js";
|
||||
import { Selector } from "./selector.js";
|
||||
import { DiscreteRange } from "./range.js";
|
||||
import { loadBodiesData, loadConfig } from "../utilities/data.js";
|
||||
import { trajectoryToText } from "../utilities/trajectory-text.js";
|
||||
import { DraggableTextbox } from "./draggable-text.js";
|
||||
export async function initEditorWithSystem(systems, systemIndex) {
|
||||
const canvas = document.getElementById("three-canvas");
|
||||
const width = canvas.clientWidth;
|
||||
@@ -120,8 +122,14 @@ export async function initEditorWithSystem(systems, systemIndex) {
|
||||
systemSelector.enable();
|
||||
}
|
||||
};
|
||||
new SubmitButton("sequence-btn").click(() => generateSequences());
|
||||
new StopButton("sequence-stop-btn").click(() => generator.cancel());
|
||||
const seqGenBtn = new Button("sequence-btn");
|
||||
seqGenBtn.click(async () => {
|
||||
seqGenBtn.disable();
|
||||
await generateSequences();
|
||||
seqGenBtn.enable();
|
||||
});
|
||||
const seqStopBtn = new Button("sequence-stop-btn");
|
||||
seqStopBtn.click(() => generator.cancel());
|
||||
}
|
||||
{
|
||||
const timeRangeStart = new TimeSelector("start", config);
|
||||
@@ -146,6 +154,8 @@ export async function initEditorWithSystem(systems, systemIndex) {
|
||||
let trajectory;
|
||||
const detailsSelector = new Selector("details-selector");
|
||||
const stepSlider = new DiscreteRange("displayed-steps-slider");
|
||||
const showTrajDetailsBtn = new Button("show-text-btn");
|
||||
showTrajDetailsBtn.disable();
|
||||
detailsSelector.disable();
|
||||
stepSlider.disable();
|
||||
const getSpan = (id) => document.getElementById(id);
|
||||
@@ -176,11 +186,13 @@ export async function initEditorWithSystem(systems, systemIndex) {
|
||||
detailsSelector.clear();
|
||||
detailsSelector.disable();
|
||||
stepSlider.disable();
|
||||
showTrajDetailsBtn.disable();
|
||||
if (trajectory)
|
||||
trajectory.remove();
|
||||
};
|
||||
const displayFoundTrajectory = () => {
|
||||
trajectory = new Trajectory(solver.bestSteps, system, config);
|
||||
let trajectoryCounter = 0;
|
||||
const displayFoundTrajectory = (sequence) => {
|
||||
trajectory = new Trajectory(solver, system, config);
|
||||
trajectory.draw(canvas);
|
||||
trajectory.fillResultControls(resultItems, systemTime, controls);
|
||||
systemTime.input(() => {
|
||||
@@ -192,6 +204,13 @@ export async function initEditorWithSystem(systems, systemIndex) {
|
||||
stepSlider.enable();
|
||||
trajectory.updatePodPosition(systemTime);
|
||||
console.log(solver.bestDeltaV);
|
||||
const trajText = trajectoryToText(trajectory, sequence);
|
||||
console.log(trajText);
|
||||
trajectoryCounter++;
|
||||
showTrajDetailsBtn.click(() => {
|
||||
DraggableTextbox.create(`Trajectory ${trajectoryCounter}`, trajText);
|
||||
});
|
||||
showTrajDetailsBtn.enable();
|
||||
};
|
||||
const findTrajectory = async () => {
|
||||
paramsErr.hide();
|
||||
@@ -225,7 +244,7 @@ export async function initEditorWithSystem(systems, systemIndex) {
|
||||
const perfStart = performance.now();
|
||||
await solver.searchOptimalTrajectory(sequence, userSettings);
|
||||
console.log(`Search time: ${performance.now() - perfStart} ms`);
|
||||
displayFoundTrajectory();
|
||||
displayFoundTrajectory(sequence);
|
||||
}
|
||||
catch (err) {
|
||||
if (err instanceof Error && err.message != "TRAJECTORY FINDER CANCELLED")
|
||||
@@ -236,8 +255,14 @@ export async function initEditorWithSystem(systems, systemIndex) {
|
||||
systemSelector.enable();
|
||||
}
|
||||
};
|
||||
new SubmitButton("search-btn").click(() => findTrajectory());
|
||||
new StopButton("search-stop-btn").click(() => solver.cancel());
|
||||
const searchStartBtn = new Button("search-btn");
|
||||
searchStartBtn.click(async () => {
|
||||
searchStartBtn.disable();
|
||||
await findTrajectory();
|
||||
searchStartBtn.enable();
|
||||
});
|
||||
const searchStopBtn = new Button("search-stop-btn");
|
||||
searchStopBtn.click(() => solver.cancel());
|
||||
systemSelector.change((_, index) => {
|
||||
stopLoop();
|
||||
deltaVPlot.destroy();
|
||||
|
||||
Vendored
+7
-6
@@ -1,3 +1,4 @@
|
||||
import { joinStrings } from "../utilities/array.js";
|
||||
export class FlybySequence {
|
||||
constructor(system, ids) {
|
||||
this.ids = ids;
|
||||
@@ -6,12 +7,12 @@ export class FlybySequence {
|
||||
this.bodies.push(system.bodyFromId(id));
|
||||
}
|
||||
this.length = this.bodies.length;
|
||||
const getSubstr = (i) => this.bodies[i].name.substring(0, 2);
|
||||
let str = getSubstr(0);
|
||||
for (let i = 1; i < this.length; i++) {
|
||||
str += "-" + getSubstr(i);
|
||||
}
|
||||
this.seqString = str;
|
||||
const initials = this.bodies.map((body) => body.name.substring(0, 2));
|
||||
this.seqString = joinStrings(initials, "-");
|
||||
}
|
||||
get seqStringFullNames() {
|
||||
const names = this.bodies.map((body) => body.name);
|
||||
return joinStrings(names, "-");
|
||||
}
|
||||
static fromString(str, system) {
|
||||
str = str.trim();
|
||||
|
||||
Vendored
+20
-20
@@ -3,18 +3,19 @@ import { Orbit } from "../objects/orbit.js";
|
||||
import { KSPTime } from "../time/time.js";
|
||||
import { SpriteManager } from "../utilities/sprites.js";
|
||||
export class Trajectory {
|
||||
constructor(steps, system, config) {
|
||||
this.steps = steps;
|
||||
constructor(solver, system, config) {
|
||||
this.solver = solver;
|
||||
this.system = system;
|
||||
this.config = config;
|
||||
this._orbitObjects = [];
|
||||
this._spriteObjects = [];
|
||||
this._podSpriteIndex = 0;
|
||||
this.orbits = [];
|
||||
this._maneuvres = [];
|
||||
this._flybys = [];
|
||||
this.maneuvres = [];
|
||||
this.flybys = [];
|
||||
this._displayedSteps = [];
|
||||
this._spritesUpdateFunId = -1;
|
||||
this.steps = solver.bestSteps;
|
||||
for (const { orbitElts, attractorId } of this.steps) {
|
||||
const attractor = this.system.bodyFromId(attractorId);
|
||||
const orbit = Orbit.fromOrbitalElements(orbitElts, attractor, config.orbit);
|
||||
@@ -158,15 +159,17 @@ export class Trajectory {
|
||||
progradeDV: progradeDir.dot(deltaV),
|
||||
normalDV: normalDir.dot(deltaV),
|
||||
radialDV: radialDir.dot(deltaV),
|
||||
totalDV: deltaV.length(),
|
||||
ejectAngle: ejectAngle
|
||||
};
|
||||
this._maneuvres.push(details);
|
||||
this.maneuvres.push(details);
|
||||
}
|
||||
}
|
||||
}
|
||||
_calculateFlybyDetails() {
|
||||
const departureDate = this.steps[0].dateOfStart;
|
||||
for (const { flyby } of this.steps) {
|
||||
for (let i = 0; i < this.steps.length; i++) {
|
||||
const { flyby } = this.steps[i];
|
||||
if (flyby) {
|
||||
const body = this.system.bodyFromId(flyby.bodyId);
|
||||
let inc = flyby.inclination * 57.2957795131;
|
||||
@@ -178,14 +181,14 @@ export class Trajectory {
|
||||
periAltitude: (flyby.periRadius - body.radius) / 1000,
|
||||
inclinationDeg: inc
|
||||
};
|
||||
this._flybys.push(details);
|
||||
this.flybys.push(details);
|
||||
}
|
||||
}
|
||||
}
|
||||
fillResultControls(resultItems, systemTime, controls) {
|
||||
const depDate = KSPTime(this.steps[0].dateOfStart, this.config.time);
|
||||
const arrDate = KSPTime(this.steps[this.steps.length - 1].dateOfStart, this.config.time);
|
||||
resultItems.totalDVSpan.innerHTML = this._totalDeltaV.toFixed(1);
|
||||
resultItems.totalDVSpan.innerHTML = this.totalDeltaV.toFixed(1);
|
||||
resultItems.depDateSpan.innerHTML = depDate.stringYDHMS("hms", "ut");
|
||||
resultItems.arrDateSpan.innerHTML = arrDate.stringYDHMS("hms", "ut");
|
||||
const onDateClick = (date) => () => {
|
||||
@@ -207,7 +210,7 @@ export class Trajectory {
|
||||
for (let i = 0; i < this.steps.length; i++) {
|
||||
const { maneuvre, flyby } = this.steps[i];
|
||||
if (maneuvre) {
|
||||
const details = this._maneuvres[maneuvreIdx];
|
||||
const details = this.maneuvres[maneuvreIdx];
|
||||
const step = this.steps[details.stepIndex];
|
||||
const context = step.maneuvre.context;
|
||||
let optionName;
|
||||
@@ -233,7 +236,7 @@ export class Trajectory {
|
||||
selectorOptions.push(option);
|
||||
}
|
||||
else if (flyby) {
|
||||
const details = this._flybys[flybyIdx];
|
||||
const details = this.flybys[flybyIdx];
|
||||
const bodyName = this.system.bodyFromId(details.bodyId).name;
|
||||
const optionName = `${++optionNumber}: ${bodyName} flyby`;
|
||||
const option = {
|
||||
@@ -251,7 +254,7 @@ export class Trajectory {
|
||||
detailsSelector.change((_, index) => {
|
||||
const option = selectorOptions[index];
|
||||
if (option.type == "maneuver") {
|
||||
const details = this._maneuvres[option.origin];
|
||||
const details = this.maneuvres[option.origin];
|
||||
const dateEMT = KSPTime(details.dateMET, this.config.time);
|
||||
resultItems.dateSpan.innerHTML = dateEMT.stringYDHMS("hm", "emt");
|
||||
resultItems.progradeDVSpan.innerHTML = details.progradeDV.toFixed(1);
|
||||
@@ -266,13 +269,12 @@ export class Trajectory {
|
||||
else {
|
||||
ejAngleLI.hidden = true;
|
||||
}
|
||||
const date = depDate.dateSeconds + dateEMT.dateSeconds;
|
||||
resultItems.dateSpan.onclick = onDateClick(date);
|
||||
resultItems.dateSpan.onclick = onDateClick(dateEMT.toUT(depDate).dateSeconds);
|
||||
resultItems.flybyDiv.hidden = true;
|
||||
resultItems.maneuverDiv.hidden = false;
|
||||
}
|
||||
else if (option.type == "flyby") {
|
||||
const details = this._flybys[option.origin];
|
||||
const details = this.flybys[option.origin];
|
||||
const startDateEMT = KSPTime(details.soiEnterDateMET, this.config.time);
|
||||
const endDateEMT = KSPTime(details.soiExitDateMET, this.config.time);
|
||||
resultItems.startDateSpan.innerHTML = startDateEMT.stringYDHMS("hm", "emt");
|
||||
@@ -280,10 +282,8 @@ export class Trajectory {
|
||||
resultItems.periAltitudeSpan.innerHTML = details.periAltitude.toFixed(0);
|
||||
resultItems.inclinationSpan.innerHTML = details.inclinationDeg.toFixed(0);
|
||||
resultItems.flybyNumberSpan.innerHTML = (option.origin + 1).toString();
|
||||
let enterDate = depDate.dateSeconds + startDateEMT.dateSeconds;
|
||||
resultItems.startDateSpan.onclick = onDateClick(enterDate);
|
||||
let exitDate = depDate.dateSeconds + endDateEMT.dateSeconds;
|
||||
resultItems.endDateSpan.onclick = onDateClick(exitDate);
|
||||
resultItems.startDateSpan.onclick = onDateClick(startDateEMT.toUT(depDate).dateSeconds);
|
||||
resultItems.endDateSpan.onclick = onDateClick(endDateEMT.toUT(depDate).dateSeconds);
|
||||
resultItems.flybyDiv.hidden = false;
|
||||
resultItems.maneuverDiv.hidden = true;
|
||||
}
|
||||
@@ -336,9 +336,9 @@ export class Trajectory {
|
||||
pod.position.set(pos.x, pos.y, pos.z);
|
||||
pod.position.multiplyScalar(scale);
|
||||
}
|
||||
get _totalDeltaV() {
|
||||
get totalDeltaV() {
|
||||
let total = 0;
|
||||
for (const details of this._maneuvres) {
|
||||
for (const details of this.maneuvres) {
|
||||
const x = details.progradeDV;
|
||||
const y = details.normalDV;
|
||||
const z = details.radialDV;
|
||||
|
||||
Vendored
+6
@@ -27,6 +27,12 @@ export class BaseKSPTime {
|
||||
return `T+ ${year - 1}y - ${day - 1}d - ${hmsStr}`;
|
||||
}
|
||||
}
|
||||
toUT(from) {
|
||||
if (typeof from == "number")
|
||||
return new BaseKSPTime(from + this._exactDate, this.config);
|
||||
else
|
||||
return new BaseKSPTime(from.dateSeconds + this._exactDate, this.config);
|
||||
}
|
||||
get dateSeconds() {
|
||||
return this._exactDate;
|
||||
}
|
||||
|
||||
Vendored
+6
@@ -43,6 +43,12 @@ export class RealKSPTime {
|
||||
return `T+ ${year}y - ${day}d - ${hmsStr}`;
|
||||
}
|
||||
}
|
||||
toUT(from) {
|
||||
if (typeof from == "number")
|
||||
return new RealKSPTime(from + this._exactDate, this.config);
|
||||
else
|
||||
return new RealKSPTime(from.dateSeconds + this._exactDate, this.config);
|
||||
}
|
||||
get dateSeconds() {
|
||||
return this._exactDate;
|
||||
}
|
||||
|
||||
Vendored
+9
@@ -23,3 +23,12 @@ export function shuffleArray(array) {
|
||||
array[j] = tmp;
|
||||
}
|
||||
}
|
||||
export function joinStrings(arr, sep) {
|
||||
if (arr.length == 0)
|
||||
return "";
|
||||
let str = arr[0];
|
||||
for (let i = 1; i < arr.length; i++) {
|
||||
str += sep + arr[i];
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
import { KSPTime } from "../time/time.js";
|
||||
import { joinStrings } from "./array.js";
|
||||
export function trajectoryToText(traj, seq) {
|
||||
const { steps, system, config } = traj;
|
||||
const pairs = [];
|
||||
const add = (label, data, indent) => {
|
||||
pairs.push({ label, data, indent });
|
||||
};
|
||||
const space = () => add("", "", 0);
|
||||
add("Sequence", seq.seqStringFullNames, 0);
|
||||
const depDate = KSPTime(steps[0].dateOfStart, config.time);
|
||||
const arrDate = KSPTime(steps[steps.length - 1].dateOfStart, config.time);
|
||||
add("Departure", depDate.stringYDHMS("hms", "ut"), 0);
|
||||
add("Arrival", arrDate.stringYDHMS("hms", "ut"), 0);
|
||||
add("Total ΔV", `${traj.totalDeltaV.toFixed(1)} m/s`, 0);
|
||||
space();
|
||||
add("Steps", "", 0);
|
||||
let maneuvreIdx = 0, flybyIdx = 0;
|
||||
for (let i = 0; i < steps.length; i++) {
|
||||
const { maneuvre, flyby } = steps[i];
|
||||
if (maneuvre) {
|
||||
space();
|
||||
const step = steps[i];
|
||||
const details = traj.maneuvres[maneuvreIdx];
|
||||
const context = step.maneuvre.context;
|
||||
const { progradeDV, normalDV, radialDV, totalDV } = details;
|
||||
let label;
|
||||
if (context.type == "ejection") {
|
||||
const startBodyName = system.bodyFromId(step.attractorId).name;
|
||||
label = `${startBodyName} escape`;
|
||||
}
|
||||
else if (context.type == "dsm") {
|
||||
const originName = system.bodyFromId(context.originId).name;
|
||||
const targetName = system.bodyFromId(context.targetId).name;
|
||||
label = `${originName}-${targetName} DSM`;
|
||||
}
|
||||
else {
|
||||
const arrivalBodyName = system.bodyFromId(step.attractorId).name;
|
||||
label = `${arrivalBodyName} circularization`;
|
||||
}
|
||||
add(label, "", 1);
|
||||
const dateMET = KSPTime(details.dateMET, config.time);
|
||||
add("Date", dateMET.toUT(depDate).stringYDHMS("hms", "ut") + " UT", 2);
|
||||
add("", dateMET.stringYDHMS("hms", "emt") + " MET", 2);
|
||||
if (details.ejectAngle !== undefined) {
|
||||
add("Ejection angle", `${details.ejectAngle.toFixed(1)}°`, 2);
|
||||
}
|
||||
add("ΔV", `${totalDV.toFixed(1)} m/s`, 2);
|
||||
add("Prograde", `${progradeDV.toFixed(1)}`, 3);
|
||||
add("Normal", `${normalDV.toFixed(1)}`, 3);
|
||||
add("Radial", `${radialDV.toFixed(1)}`, 3);
|
||||
maneuvreIdx++;
|
||||
}
|
||||
else if (flyby) {
|
||||
space();
|
||||
const details = traj.flybys[flybyIdx];
|
||||
const bodyName = system.bodyFromId(details.bodyId).name;
|
||||
const enterMET = KSPTime(details.soiEnterDateMET, config.time);
|
||||
const exitMET = KSPTime(details.soiExitDateMET, config.time);
|
||||
add(`Flyby around ${bodyName}`, "", 1);
|
||||
add("SOI enter date", enterMET.toUT(depDate).stringYDHMS("hms", "ut") + " UT", 2);
|
||||
add("", enterMET.stringYDHMS("hms", "emt") + " MET", 2);
|
||||
add("SOI exit date", exitMET.toUT(depDate).stringYDHMS("hms", "ut") + " UT", 2);
|
||||
add("", exitMET.stringYDHMS("hms", "emt") + " MET", 2);
|
||||
add("Periapsis altitude", `${details.periAltitude.toFixed(0)} km`, 2);
|
||||
add("Inclination", `${details.inclinationDeg.toFixed(0)}°`, 2);
|
||||
flybyIdx++;
|
||||
}
|
||||
}
|
||||
return pairsToString(pairs);
|
||||
}
|
||||
function pairsToString(pairs) {
|
||||
const lines = [];
|
||||
for (const pair of pairs) {
|
||||
if (pair.label == "") {
|
||||
lines.push("");
|
||||
continue;
|
||||
}
|
||||
let indent = " ".repeat(pair.indent * 2);
|
||||
lines.push(`${indent}${pair.label}:`);
|
||||
}
|
||||
let maxLen = 0;
|
||||
for (const line of lines) {
|
||||
maxLen = Math.max(maxLen, line.length);
|
||||
}
|
||||
for (let i = 0; i < pairs.length; i++) {
|
||||
if (pairs[i].label == "" && pairs[i].data == "")
|
||||
continue;
|
||||
const spaces = " ".repeat(maxLen - lines[i].length + 1);
|
||||
lines[i] += spaces + pairs[i].data;
|
||||
}
|
||||
return joinStrings(lines, "\n");
|
||||
}
|
||||
+21
-1
@@ -6,6 +6,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<link rel="shortcut icon" href="#">
|
||||
<link rel="stylesheet" type="text/css" href="style.css">
|
||||
<!-- icons -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/all.min.css">
|
||||
<title>KSP MGA Planner</title>
|
||||
</head>
|
||||
<body>
|
||||
@@ -237,7 +239,12 @@
|
||||
|
||||
<div id="result-panel">
|
||||
<div id="result-panel-header">
|
||||
<h2 class="calculation-panel-title">Calculated trajectory</h2>
|
||||
<div id="result-panel-header-left">
|
||||
<h2 class="calculation-panel-title">Calculated trajectory</h2>
|
||||
<button id="show-text-btn" class="submit-btn" disabled>
|
||||
<i class="fa-solid fa-circle-info"></i> Steps
|
||||
</button>
|
||||
</div>
|
||||
<div id="steps-slider-control-group" class="control-group">
|
||||
<label class="control-label" for="displayed-steps-slider">Displayed steps:</label>
|
||||
<div id="steps-slider-controls" class="controls">
|
||||
@@ -461,6 +468,19 @@
|
||||
|
||||
</div>
|
||||
|
||||
<!-- template -->
|
||||
<!-- hidden style="display: none;" -->
|
||||
<div id="draggable-text-template" class="draggable-text-box" hidden style="display: none;">
|
||||
<div class="draggable-text-header">
|
||||
<h3 class="draggable-text-title"></h3>
|
||||
<div>
|
||||
<button class="draggable-copy-btn"><i class="fa-regular fa-clipboard"></i></button>
|
||||
<button class="draggable-close-btn"><i class="fa fa-close"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<textarea name="draggable-textaera" cols="70" rows="30" readonly></textarea>
|
||||
</div>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/js-yaml/4.1.0/js-yaml.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
class Button {
|
||||
export class Button {
|
||||
protected readonly _btn!: HTMLButtonElement;
|
||||
|
||||
constructor(id: string) {
|
||||
@@ -12,28 +12,8 @@ class Button {
|
||||
public disable(){
|
||||
this._btn.disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
export class SubmitButton extends Button {
|
||||
constructor(id: string) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
click(asyncAction: () => Promise<void>){
|
||||
this._btn.onclick = async () => {
|
||||
this.disable();
|
||||
await asyncAction();
|
||||
this.enable();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class StopButton extends Button {
|
||||
constructor(id: string) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
click(action: () => void){
|
||||
this._btn.onclick = () => action();
|
||||
this._btn.onclick = action;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
export class DraggableTextbox {
|
||||
private static nextId = 0;
|
||||
private static startZ = 9;
|
||||
private static boxes = new Map<number, {div: HTMLDivElement, title: string}>();
|
||||
|
||||
public static create(title: string, content: string){
|
||||
for(const [id, item] of this.boxes){
|
||||
if(item.title == title) {
|
||||
this.moveToFront(id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const template = document.getElementById("draggable-text-template") as HTMLDivElement;
|
||||
const div = template.cloneNode(true) as HTMLDivElement;
|
||||
document.body.insertBefore(div,document.body.lastChild);
|
||||
const id = this.nextId;
|
||||
div.id = `draggable-text-${id}`;
|
||||
this.nextId++;
|
||||
this.boxes.set(id, {div, title});
|
||||
|
||||
const header = div.getElementsByClassName("draggable-text-header")[0] as HTMLDivElement;
|
||||
const h = header.getElementsByClassName("draggable-text-title")[0] as HTMLTitleElement;
|
||||
h.innerHTML = title;
|
||||
|
||||
const textarea = div.getElementsByTagName("textarea")[0] as HTMLTextAreaElement;
|
||||
textarea.value = content;
|
||||
|
||||
const closeBtn = div.getElementsByClassName("draggable-close-btn")[0] as HTMLButtonElement;
|
||||
closeBtn.onclick = () => {
|
||||
div.remove();
|
||||
this.boxes.delete(id);
|
||||
};
|
||||
const copyBtn = div.getElementsByClassName("draggable-copy-btn")[0] as HTMLButtonElement;
|
||||
copyBtn.onclick = () => navigator.clipboard.writeText(content);
|
||||
|
||||
header.onmousedown = (e: MouseEvent) => {
|
||||
e = e || window.event;
|
||||
e.preventDefault();
|
||||
|
||||
let x = e.clientX;
|
||||
let y = e.clientY;
|
||||
|
||||
let left: number, top: number;
|
||||
const setPos = () => {
|
||||
div.style.top = top.toString() + "px";
|
||||
div.style.left = left.toString() + "px";
|
||||
};
|
||||
|
||||
const clamp = (x:number, a:number, b:number) => x < a ? a : x > b ? b : x;
|
||||
|
||||
document.onmouseup = () => {
|
||||
left = clamp(left, 0, window.innerWidth - header.offsetWidth);
|
||||
top = clamp(top, 0, window.innerHeight - header.offsetHeight);
|
||||
setPos();
|
||||
|
||||
document.onmouseup = null;
|
||||
document.onmousemove = null;
|
||||
};
|
||||
|
||||
document.onmousemove = (e: MouseEvent) => {
|
||||
e = e || window.event;
|
||||
e.preventDefault();
|
||||
|
||||
let dx = x - e.clientX;
|
||||
let dy = y - e.clientY;
|
||||
x = e.clientX;
|
||||
y = e.clientY;
|
||||
|
||||
left = div.offsetLeft - dx;
|
||||
top = div.offsetTop - dy;
|
||||
setPos();
|
||||
};
|
||||
|
||||
this.moveToFront(id);
|
||||
};
|
||||
|
||||
textarea.onclick = () => this.moveToFront(id);
|
||||
|
||||
this.moveToFront(id);
|
||||
this.startZ++;
|
||||
|
||||
div.hidden = false;
|
||||
div.style.display = "";
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
public static moveToFront(id: number){
|
||||
const item = this.boxes.get(id) as {div: HTMLDivElement, title: string};
|
||||
const div0 = item.div;
|
||||
const z0 = parseInt(div0.style.zIndex);
|
||||
this.boxes.forEach(({div}, _) => {
|
||||
const z = parseInt(div.style.zIndex);
|
||||
if(z > z0){
|
||||
div.style.zIndex = (z-1).toString();
|
||||
}
|
||||
});
|
||||
div0.style.zIndex = this.startZ.toString();
|
||||
}
|
||||
}
|
||||
@@ -9,13 +9,15 @@ import { BodySelector } from "./body-selector.js";
|
||||
import { EvolutionPlot } from "./plot.js";
|
||||
import { ProgressMessage } from "./progress-msg.js";
|
||||
import { SequenceSelector } from "./sequence-selector.js";
|
||||
import { SubmitButton, StopButton } from "./buttons.js";
|
||||
import { Button } from "./buttons.js";
|
||||
import { FlybySequence } from "../solvers/sequence.js";
|
||||
import { Trajectory } from "../solvers/trajectory.js";
|
||||
import { Selector } from "./selector.js";
|
||||
import { DiscreteRange } from "./range.js";
|
||||
import { OrbitingBody } from "../objects/body.js";
|
||||
import { loadBodiesData, loadConfig } from "../utilities/data.js";
|
||||
import { trajectoryToText } from "../utilities/trajectory-text.js";
|
||||
import { DraggableTextbox } from "./draggable-text.js";
|
||||
|
||||
|
||||
export async function initEditorWithSystem(systems: SolarSystemData[], systemIndex: number){
|
||||
@@ -161,8 +163,14 @@ export async function initEditorWithSystem(systems: SolarSystemData[], systemInd
|
||||
}
|
||||
|
||||
// Sequence generator buttons
|
||||
new SubmitButton("sequence-btn").click(() => generateSequences());
|
||||
new StopButton("sequence-stop-btn").click(() => generator.cancel());
|
||||
const seqGenBtn = new Button("sequence-btn");
|
||||
seqGenBtn.click(async () => {
|
||||
seqGenBtn.disable();
|
||||
await generateSequences()
|
||||
seqGenBtn.enable();
|
||||
});
|
||||
const seqStopBtn = new Button("sequence-stop-btn");
|
||||
seqStopBtn.click(() => generator.cancel());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -204,6 +212,9 @@ export async function initEditorWithSystem(systems: SolarSystemData[], systemInd
|
||||
const detailsSelector = new Selector("details-selector");
|
||||
const stepSlider = new DiscreteRange("displayed-steps-slider");
|
||||
|
||||
const showTrajDetailsBtn = new Button("show-text-btn");
|
||||
showTrajDetailsBtn.disable();
|
||||
|
||||
detailsSelector.disable();
|
||||
stepSlider.disable();
|
||||
|
||||
@@ -237,11 +248,13 @@ export async function initEditorWithSystem(systems: SolarSystemData[], systemInd
|
||||
detailsSelector.clear();
|
||||
detailsSelector.disable();
|
||||
stepSlider.disable();
|
||||
showTrajDetailsBtn.disable();
|
||||
if(trajectory) trajectory.remove();
|
||||
}
|
||||
|
||||
const displayFoundTrajectory = () => {
|
||||
trajectory = new Trajectory(solver.bestSteps, system, config);
|
||||
let trajectoryCounter = 0;
|
||||
const displayFoundTrajectory = (sequence: FlybySequence) => {
|
||||
trajectory = new Trajectory(solver, system, config);
|
||||
trajectory.draw(canvas);
|
||||
trajectory.fillResultControls(resultItems, systemTime, controls);
|
||||
|
||||
@@ -256,6 +269,15 @@ export async function initEditorWithSystem(systems: SolarSystemData[], systemInd
|
||||
trajectory.updatePodPosition(systemTime);
|
||||
|
||||
console.log(solver.bestDeltaV);
|
||||
|
||||
const trajText = trajectoryToText(trajectory, sequence);
|
||||
console.log(trajText);
|
||||
|
||||
trajectoryCounter++;
|
||||
showTrajDetailsBtn.click(() => {
|
||||
DraggableTextbox.create(`Trajectory ${trajectoryCounter}`, trajText);
|
||||
});
|
||||
showTrajDetailsBtn.enable();
|
||||
};
|
||||
|
||||
const findTrajectory = async () => {
|
||||
@@ -298,7 +320,7 @@ export async function initEditorWithSystem(systems: SolarSystemData[], systemInd
|
||||
await solver.searchOptimalTrajectory(sequence, userSettings);
|
||||
console.log(`Search time: ${performance.now() - perfStart} ms`);
|
||||
|
||||
displayFoundTrajectory();
|
||||
displayFoundTrajectory(sequence);
|
||||
|
||||
} catch(err) {
|
||||
if(err instanceof Error && err.message != "TRAJECTORY FINDER CANCELLED")
|
||||
@@ -312,8 +334,14 @@ export async function initEditorWithSystem(systems: SolarSystemData[], systemInd
|
||||
};
|
||||
|
||||
// Trajectory solver buttons
|
||||
new SubmitButton("search-btn").click(() => findTrajectory());
|
||||
new StopButton("search-stop-btn").click(() => solver.cancel());
|
||||
const searchStartBtn = new Button("search-btn");
|
||||
searchStartBtn.click(async () => {
|
||||
searchStartBtn.disable();
|
||||
await findTrajectory();
|
||||
searchStartBtn.enable();
|
||||
});
|
||||
const searchStopBtn = new Button("search-stop-btn");
|
||||
searchStopBtn.click(() => solver.cancel());
|
||||
|
||||
// Configure the system selector callback
|
||||
systemSelector.change((_, index) => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { OrbitingBody } from "../objects/body";
|
||||
import { SolarSystem } from "../objects/system";
|
||||
import { OrbitingBody } from "../objects/body.js";
|
||||
import { SolarSystem } from "../objects/system.js";
|
||||
import { joinStrings } from "../utilities/array.js";
|
||||
|
||||
export class FlybySequence {
|
||||
public readonly bodies!: OrbitingBody[];
|
||||
@@ -13,12 +14,13 @@ export class FlybySequence {
|
||||
}
|
||||
this.length = this.bodies.length;
|
||||
|
||||
const getSubstr = (i: number) => this.bodies[i].name.substring(0, 2);
|
||||
let str = getSubstr(0);
|
||||
for(let i = 1; i < this.length; i++){
|
||||
str += "-" + getSubstr(i);
|
||||
}
|
||||
this.seqString = str;
|
||||
const initials = this.bodies.map((body: OrbitingBody) => body.name.substring(0, 2));
|
||||
this.seqString = joinStrings(initials, "-");
|
||||
}
|
||||
|
||||
get seqStringFullNames(){
|
||||
const names = this.bodies.map((body: OrbitingBody) => body.name);
|
||||
return joinStrings(names, "-");
|
||||
}
|
||||
|
||||
static fromString(str: string, system: SolarSystem){
|
||||
|
||||
@@ -6,21 +6,24 @@ import { KSPTime } from "../time/time.js";
|
||||
import { CameraController } from "../objects/camera.js";
|
||||
import { SpriteManager } from "../utilities/sprites.js";
|
||||
import { OrbitingBody } from "../objects/body.js";
|
||||
import { TrajectorySolver } from "./trajectory-solver.js";
|
||||
|
||||
export class Trajectory {
|
||||
private _orbitObjects: THREE.Object3D[] = [];
|
||||
private _spriteObjects: THREE.Sprite[][] = [];
|
||||
private _podSpriteIndex: number = 0;
|
||||
|
||||
public readonly steps: TrajectoryStep[];
|
||||
public readonly orbits: Orbit[] = [];
|
||||
|
||||
private readonly _maneuvres: ManeuvreDetails[] = [];
|
||||
private readonly _flybys: FlybyDetails[] = [];
|
||||
public readonly maneuvres: ManeuvreDetails[] = [];
|
||||
public readonly flybys: FlybyDetails[] = [];
|
||||
|
||||
private _displayedSteps: boolean[] = [];
|
||||
private _spritesUpdateFunId: number = -1;
|
||||
|
||||
constructor(public readonly steps: TrajectoryStep[], public readonly system: SolarSystem, public readonly config: Config) {
|
||||
constructor(public readonly solver: TrajectorySolver, public readonly system: SolarSystem, public readonly config: Config) {
|
||||
this.steps = solver.bestSteps;
|
||||
for(const {orbitElts, attractorId} of this.steps) {
|
||||
const attractor = this.system.bodyFromId(attractorId);
|
||||
const orbit = Orbit.fromOrbitalElements(orbitElts, attractor, config.orbit);
|
||||
@@ -224,16 +227,17 @@ export class Trajectory {
|
||||
ejectAngle = Math.acos(cosA) * 180 / Math.PI;
|
||||
ejectAngle *= Math.sign(u.x*v.y - u.y*v.x); // counter clockwise direction of the angle
|
||||
}
|
||||
|
||||
|
||||
const details = {
|
||||
stepIndex: i,
|
||||
dateMET: step.dateOfStart - departureDate,
|
||||
progradeDV: progradeDir.dot(deltaV),
|
||||
normalDV: normalDir.dot(deltaV),
|
||||
radialDV: radialDir.dot(deltaV),
|
||||
totalDV: deltaV.length(),
|
||||
ejectAngle: ejectAngle
|
||||
};
|
||||
this._maneuvres.push(details);
|
||||
this.maneuvres.push(details);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -243,7 +247,8 @@ export class Trajectory {
|
||||
*/
|
||||
private _calculateFlybyDetails(){
|
||||
const departureDate = this.steps[0].dateOfStart;
|
||||
for(const {flyby} of this.steps){
|
||||
for(let i = 0; i < this.steps.length; i++){
|
||||
const {flyby} = this.steps[i];
|
||||
if(flyby){
|
||||
const body = this.system.bodyFromId(flyby.bodyId);
|
||||
// non oriented inclination compared to x-z plane
|
||||
@@ -256,7 +261,7 @@ export class Trajectory {
|
||||
periAltitude: (flyby.periRadius - body.radius) / 1000, // in km
|
||||
inclinationDeg: inc
|
||||
}
|
||||
this._flybys.push(details);
|
||||
this.flybys.push(details);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -272,7 +277,7 @@ export class Trajectory {
|
||||
const arrDate = KSPTime(this.steps[this.steps.length-1].dateOfStart, this.config.time);
|
||||
|
||||
// total delta-V
|
||||
resultItems.totalDVSpan.innerHTML = this._totalDeltaV.toFixed(1);
|
||||
resultItems.totalDVSpan.innerHTML = this.totalDeltaV.toFixed(1);
|
||||
// departure date
|
||||
resultItems.depDateSpan.innerHTML = depDate.stringYDHMS("hms", "ut");
|
||||
// arrival date
|
||||
@@ -305,7 +310,7 @@ export class Trajectory {
|
||||
for(let i = 0; i < this.steps.length; i++){
|
||||
const {maneuvre, flyby} = this.steps[i];
|
||||
if(maneuvre){
|
||||
const details = this._maneuvres[maneuvreIdx];
|
||||
const details = this.maneuvres[maneuvreIdx];
|
||||
const step = this.steps[details.stepIndex];
|
||||
const context = (<ManeuvreInfo>step.maneuvre).context;
|
||||
|
||||
@@ -333,7 +338,7 @@ export class Trajectory {
|
||||
|
||||
} else if(flyby){
|
||||
// details the options in the selector, if it's a flyby
|
||||
const details = this._flybys[flybyIdx];
|
||||
const details = this.flybys[flybyIdx];
|
||||
const bodyName = this.system.bodyFromId(details.bodyId).name;
|
||||
const optionName = `${++optionNumber}: ${bodyName} flyby`;
|
||||
|
||||
@@ -358,7 +363,7 @@ export class Trajectory {
|
||||
const option = selectorOptions[index];
|
||||
|
||||
if(option.type == "maneuver"){
|
||||
const details = this._maneuvres[option.origin];
|
||||
const details = this.maneuvres[option.origin];
|
||||
const dateEMT = KSPTime(details.dateMET, this.config.time);
|
||||
|
||||
resultItems.dateSpan.innerHTML = dateEMT.stringYDHMS("hm", "emt");
|
||||
@@ -375,14 +380,13 @@ export class Trajectory {
|
||||
ejAngleLI.hidden = true;
|
||||
}
|
||||
|
||||
const date = depDate.dateSeconds + dateEMT.dateSeconds;
|
||||
resultItems.dateSpan.onclick = onDateClick(date);
|
||||
resultItems.dateSpan.onclick = onDateClick(dateEMT.toUT(depDate).dateSeconds);
|
||||
|
||||
resultItems.flybyDiv.hidden = true;
|
||||
resultItems.maneuverDiv.hidden = false;
|
||||
|
||||
} else if(option.type == "flyby"){
|
||||
const details = this._flybys[option.origin];
|
||||
const details = this.flybys[option.origin];
|
||||
const startDateEMT = KSPTime(details.soiEnterDateMET, this.config.time);
|
||||
const endDateEMT = KSPTime(details.soiExitDateMET, this.config.time);
|
||||
|
||||
@@ -392,10 +396,8 @@ export class Trajectory {
|
||||
resultItems.inclinationSpan.innerHTML = details.inclinationDeg.toFixed(0);
|
||||
resultItems.flybyNumberSpan.innerHTML = (option.origin + 1).toString();
|
||||
|
||||
let enterDate = depDate.dateSeconds + startDateEMT.dateSeconds;
|
||||
resultItems.startDateSpan.onclick = onDateClick(enterDate);
|
||||
let exitDate = depDate.dateSeconds + endDateEMT.dateSeconds;
|
||||
resultItems.endDateSpan.onclick = onDateClick(exitDate);
|
||||
resultItems.startDateSpan.onclick = onDateClick(startDateEMT.toUT(depDate).dateSeconds);
|
||||
resultItems.endDateSpan.onclick = onDateClick(endDateEMT.toUT(depDate).dateSeconds);
|
||||
|
||||
resultItems.flybyDiv.hidden = false;
|
||||
resultItems.maneuverDiv.hidden = true;
|
||||
@@ -478,9 +480,9 @@ export class Trajectory {
|
||||
/**
|
||||
* Computes and returns the total delta-V of the trajectory
|
||||
*/
|
||||
private get _totalDeltaV(){
|
||||
public get totalDeltaV(){
|
||||
let total = 0;
|
||||
for(const details of this._maneuvres){
|
||||
for(const details of this.maneuvres){
|
||||
const x = details.progradeDV;
|
||||
const y = details.normalDV;
|
||||
const z = details.radialDV;
|
||||
|
||||
@@ -29,6 +29,13 @@ export class BaseKSPTime implements IKSPTime {
|
||||
}
|
||||
}
|
||||
|
||||
public toUT(from: number | IKSPTime): IKSPTime {
|
||||
if(typeof from == "number")
|
||||
return new BaseKSPTime(from + this._exactDate, this.config);
|
||||
else
|
||||
return new BaseKSPTime(from.dateSeconds + this._exactDate, this.config);
|
||||
}
|
||||
|
||||
public get dateSeconds(){
|
||||
return this._exactDate;
|
||||
}
|
||||
|
||||
@@ -44,6 +44,13 @@ export class RealKSPTime implements IKSPTime {
|
||||
}
|
||||
}
|
||||
|
||||
public toUT(from: number | IKSPTime): IKSPTime {
|
||||
if(typeof from == "number")
|
||||
return new RealKSPTime(from + this._exactDate, this.config);
|
||||
else
|
||||
return new RealKSPTime(from.dateSeconds + this._exactDate, this.config);
|
||||
}
|
||||
|
||||
public get dateSeconds(){
|
||||
return this._exactDate;
|
||||
}
|
||||
|
||||
@@ -24,4 +24,14 @@ export function shuffleArray<T>(array: T[]){
|
||||
array[i] = array[j];
|
||||
array[j] = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
export function joinStrings(arr: string[], sep: string){
|
||||
if(arr.length == 0)
|
||||
return "";
|
||||
let str = arr[0];
|
||||
for(let i = 1; i < arr.length; i++){
|
||||
str += sep + arr[i];
|
||||
}
|
||||
return str;
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
import { FlybySequence } from "../solvers/sequence.js";
|
||||
import { Trajectory } from "../solvers/trajectory.js";
|
||||
import { KSPTime } from "../time/time.js";
|
||||
import { joinStrings } from "./array.js";
|
||||
|
||||
type pair = {label: string, data: string, indent: number};
|
||||
|
||||
export function trajectoryToText(traj: Trajectory, seq: FlybySequence) {
|
||||
const {steps, system, config} = traj;
|
||||
|
||||
const pairs: pair[] = [];
|
||||
const add = (label: string, data: string, indent: number) => {
|
||||
pairs.push({label, data, indent} as pair);
|
||||
};
|
||||
const space = () => add("", "", 0);
|
||||
|
||||
|
||||
add("Sequence", seq.seqStringFullNames, 0);
|
||||
|
||||
const depDate = KSPTime(steps[0].dateOfStart, config.time);
|
||||
const arrDate = KSPTime(steps[steps.length-1].dateOfStart, config.time);
|
||||
add("Departure", depDate.stringYDHMS("hms", "ut"), 0);
|
||||
add("Arrival", arrDate.stringYDHMS("hms", "ut"), 0);
|
||||
add("Total ΔV", `${traj.totalDeltaV.toFixed(1)} m/s`, 0);
|
||||
space();
|
||||
add("Steps", "", 0);
|
||||
|
||||
let maneuvreIdx = 0, flybyIdx = 0;
|
||||
|
||||
for(let i = 0; i < steps.length; i++){
|
||||
const {maneuvre, flyby} = steps[i];
|
||||
if(maneuvre){
|
||||
space();
|
||||
const step = steps[i];
|
||||
const details = traj.maneuvres[maneuvreIdx];
|
||||
const context = (<ManeuvreInfo>step.maneuvre).context;
|
||||
const {progradeDV, normalDV, radialDV, totalDV} = details;
|
||||
|
||||
let label: string;
|
||||
if(context.type == "ejection") {
|
||||
const startBodyName = system.bodyFromId(step.attractorId).name;
|
||||
label = `${startBodyName} escape`;
|
||||
} else if(context.type == "dsm") {
|
||||
const originName = system.bodyFromId(context.originId).name;
|
||||
const targetName = system.bodyFromId(context.targetId).name;
|
||||
label = `${originName}-${targetName} DSM`;
|
||||
} else {
|
||||
const arrivalBodyName = system.bodyFromId(step.attractorId).name;
|
||||
label = `${arrivalBodyName} circularization`;
|
||||
}
|
||||
add(label, "", 1);
|
||||
const dateMET = KSPTime(details.dateMET, config.time);
|
||||
add("Date", dateMET.toUT(depDate).stringYDHMS("hms", "ut") + " UT", 2);
|
||||
add("", dateMET.stringYDHMS("hms", "emt") + " MET", 2);
|
||||
if(details.ejectAngle !== undefined){
|
||||
add("Ejection angle", `${details.ejectAngle.toFixed(1)}°`, 2);
|
||||
}
|
||||
add("ΔV", `${totalDV.toFixed(1)} m/s`, 2);
|
||||
add("Prograde", `${progradeDV.toFixed(1)}`, 3);
|
||||
add("Normal", `${normalDV.toFixed(1)}`, 3);
|
||||
add("Radial", `${radialDV.toFixed(1)}`, 3);
|
||||
|
||||
maneuvreIdx++;
|
||||
|
||||
} else if(flyby){
|
||||
space();
|
||||
const details = traj.flybys[flybyIdx];
|
||||
const bodyName = system.bodyFromId(details.bodyId).name;
|
||||
const enterMET = KSPTime(details.soiEnterDateMET, config.time);
|
||||
const exitMET = KSPTime(details.soiExitDateMET, config.time);
|
||||
add(`Flyby around ${bodyName}`, "", 1);
|
||||
add("SOI enter date", enterMET.toUT(depDate).stringYDHMS("hms", "ut") + " UT", 2);
|
||||
add("", enterMET.stringYDHMS("hms", "emt") + " MET", 2);
|
||||
add("SOI exit date", exitMET.toUT(depDate).stringYDHMS("hms", "ut") + " UT", 2);
|
||||
add("", exitMET.stringYDHMS("hms", "emt") + " MET", 2);
|
||||
add("Periapsis altitude", `${details.periAltitude.toFixed(0)} km`, 2);
|
||||
add("Inclination", `${details.inclinationDeg.toFixed(0)}°`, 2);
|
||||
|
||||
flybyIdx++;
|
||||
}
|
||||
}
|
||||
|
||||
return pairsToString(pairs);
|
||||
}
|
||||
|
||||
|
||||
function pairsToString(pairs: pair[]){
|
||||
const lines: string[] = [];
|
||||
for(const pair of pairs){
|
||||
if(pair.label == "") {
|
||||
lines.push("");
|
||||
continue;
|
||||
}
|
||||
let indent = " ".repeat(pair.indent*2);
|
||||
lines.push(`${indent}${pair.label}:`);
|
||||
}
|
||||
let maxLen = 0;
|
||||
for(const line of lines){
|
||||
maxLen = Math.max(maxLen, line.length);
|
||||
}
|
||||
for(let i = 0; i < pairs.length; i++){
|
||||
if(pairs[i].label == "" && pairs[i].data == "")
|
||||
continue;
|
||||
const spaces = " ".repeat(maxLen - lines[i].length + 1);
|
||||
lines[i] += spaces + pairs[i].data;
|
||||
}
|
||||
return joinStrings(lines, "\n");
|
||||
}
|
||||
Vendored
+2
@@ -143,6 +143,7 @@ interface IKSPTime {
|
||||
public displayYDHMS: DateYDH;
|
||||
public readonly defaultDate: number;
|
||||
public stringYDHMS(precision: "h" | "hm" | "hms", display: "emt" | "ut"): string;
|
||||
public toUT(from: IKSPTime | number): IKSPTime;
|
||||
}
|
||||
|
||||
type MessageToWorker =
|
||||
@@ -299,6 +300,7 @@ type ManeuvreDetails = {
|
||||
progradeDV: number,
|
||||
normalDV: number,
|
||||
radialDV: number,
|
||||
totalDV: number,
|
||||
ejectAngle?: number
|
||||
};
|
||||
|
||||
|
||||
@@ -426,6 +426,36 @@ input[type="range"]
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
#result-panel-header-left
|
||||
{
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#show-text-btn
|
||||
{
|
||||
margin-left: 10px;
|
||||
background-image: linear-gradient(rgb(0, 93, 163), rgb(0, 60, 105));
|
||||
}
|
||||
|
||||
#show-text-btn:hover
|
||||
{
|
||||
background-image: linear-gradient(rgb(0, 78, 138), rgb(0, 60, 105));
|
||||
}
|
||||
|
||||
#show-text-btn:active
|
||||
{
|
||||
background-image: none;
|
||||
background-color: rgb(0, 60, 105);
|
||||
}
|
||||
|
||||
#show-text-btn:disabled
|
||||
{
|
||||
background-image: none;
|
||||
background-color: rgb(46, 52, 56);
|
||||
}
|
||||
|
||||
#steps-slider-control-group
|
||||
{
|
||||
margin-bottom: 0;
|
||||
@@ -570,4 +600,122 @@ footer p
|
||||
{
|
||||
margin-bottom: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
|
||||
/* Draggable text box */
|
||||
|
||||
.draggable-text-box
|
||||
{
|
||||
position: fixed;
|
||||
z-index: 9;
|
||||
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-color: #414242;
|
||||
border-radius: 5px;
|
||||
|
||||
background-color: #0d0e0e;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
top: 100px;
|
||||
left: 100px;
|
||||
|
||||
box-shadow: 5px 5px 5px rgba(0,0,0,0.6);
|
||||
-moz-box-shadow: 5px 5px 5px rgba(0,0,0,0.6);
|
||||
-webkit-box-shadow: 5px 5px 5px rgba(0,0,0,0.6);
|
||||
-o-box-shadow: 5px 5px 5px rgba(0,0,0,0.6);
|
||||
}
|
||||
|
||||
.draggable-text-header
|
||||
{
|
||||
padding: 0.3em;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.draggable-text-header .draggable-text-title
|
||||
{
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.draggable-text-box textarea
|
||||
{
|
||||
/*resize: none;*/
|
||||
padding: 10px;
|
||||
color: white;
|
||||
background-color: rgb(22, 23, 24);
|
||||
margin-bottom: 0;
|
||||
min-width: 200px;
|
||||
width: 100%;
|
||||
|
||||
border: none;
|
||||
overflow: auto;
|
||||
outline: none;
|
||||
|
||||
-webkit-box-shadow: none;
|
||||
-moz-box-shadow: none;
|
||||
box-shadow: none;
|
||||
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
||||
.draggable-close-btn, .draggable-copy-btn
|
||||
{
|
||||
color: white;
|
||||
border-radius: 5px;
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
border: 1px solid rgb(54, 54, 54);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* Close button */
|
||||
.draggable-close-btn
|
||||
{
|
||||
background-color: #3e1515;
|
||||
}
|
||||
|
||||
.draggable-close-btn:hover
|
||||
{
|
||||
cursor: pointer;
|
||||
background-color: #512424;
|
||||
}
|
||||
|
||||
.draggable-close-btn:active
|
||||
{
|
||||
cursor: pointer;
|
||||
background-color: #580e0e;
|
||||
}
|
||||
|
||||
.draggable-close-btn:disabled
|
||||
{
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
|
||||
/* Copy to clipboard button */
|
||||
.draggable-copy-btn
|
||||
{
|
||||
background-color: #344c67;
|
||||
}
|
||||
|
||||
.draggable-copy-btn:hover
|
||||
{
|
||||
cursor: pointer;
|
||||
background-color: #4c6786;
|
||||
}
|
||||
|
||||
.draggable-copy-btn:active
|
||||
{
|
||||
cursor: pointer;
|
||||
background-color: #22354d;
|
||||
}
|
||||
Reference in New Issue
Block a user