Merge pull request #31 from Krafpy/trajectory-to-text

Trajectory to text
This commit is contained in:
Krafpy
2023-01-06 22:58:52 +01:00
committed by GitHub
21 changed files with 731 additions and 112 deletions
+2 -19
View File
@@ -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;
}
}
+81
View File
@@ -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();
+33 -8
View File
@@ -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();
+7 -6
View File
@@ -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();
+20 -20
View File
@@ -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;
+6
View File
@@ -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;
}
+6
View File
@@ -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;
}
+9
View File
@@ -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
View File
@@ -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
View File
@@ -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>
+2 -22
View File
@@ -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;
}
}
+101
View File
@@ -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();
}
}
+36 -8
View File
@@ -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) => {
+10 -8
View File
@@ -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){
+22 -20
View File
@@ -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;
+7
View File
@@ -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;
}
+7
View File
@@ -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;
}
+10
View File
@@ -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;
}
+108
View File
@@ -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");
}
+2
View File
@@ -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
};
+148
View File
@@ -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;
}