Added trajectory to text conversion.

This commit is contained in:
Krafpy
2023-01-02 00:01:43 +01:00
parent 4f817f767d
commit b6b210d269
11 changed files with 273 additions and 48 deletions

View File

@@ -15,6 +15,7 @@ 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";
export async function initEditorWithSystem(systems, systemIndex) {
const canvas = document.getElementById("three-canvas");
const width = canvas.clientWidth;
@@ -179,8 +180,8 @@ export async function initEditorWithSystem(systems, systemIndex) {
if (trajectory)
trajectory.remove();
};
const displayFoundTrajectory = () => {
trajectory = new Trajectory(solver.bestSteps, system, config);
const displayFoundTrajectory = (sequence) => {
trajectory = new Trajectory(solver, system, config);
trajectory.draw(canvas);
trajectory.fillResultControls(resultItems, systemTime, controls);
systemTime.input(() => {
@@ -192,6 +193,7 @@ export async function initEditorWithSystem(systems, systemIndex) {
stepSlider.enable();
trajectory.updatePodPosition(systemTime);
console.log(solver.bestDeltaV);
console.log(trajectoryToText(trajectory, sequence));
};
const findTrajectory = async () => {
paramsErr.hide();
@@ -225,7 +227,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")

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

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);
@@ -272,7 +275,7 @@ export class Trajectory {
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");
@@ -336,9 +339,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;

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

87
dist/main/utilities/trajectory-text.js vendored Normal file
View File

@@ -0,0 +1,87 @@
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);
add("Date", KSPTime(details.dateMET, config.time).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;
add(`Flyby around ${bodyName}`, "", 1);
add("SOI enter date", KSPTime(details.soiEnterDateMET, config.time).stringYDHMS("hms", "emt") + " MET", 2);
add("SOI exit date", KSPTime(details.soiExitDateMET, config.time).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");
}

View File

@@ -16,6 +16,7 @@ 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";
export async function initEditorWithSystem(systems: SolarSystemData[], systemIndex: number){
@@ -240,8 +241,8 @@ export async function initEditorWithSystem(systems: SolarSystemData[], systemInd
if(trajectory) trajectory.remove();
}
const displayFoundTrajectory = () => {
trajectory = new Trajectory(solver.bestSteps, system, config);
const displayFoundTrajectory = (sequence: FlybySequence) => {
trajectory = new Trajectory(solver, system, config);
trajectory.draw(canvas);
trajectory.fillResultControls(resultItems, systemTime, controls);
@@ -256,6 +257,8 @@ export async function initEditorWithSystem(systems: SolarSystemData[], systemInd
trajectory.updatePodPosition(systemTime);
console.log(solver.bestDeltaV);
console.log(trajectoryToText(trajectory, sequence));
};
const findTrajectory = async () => {
@@ -298,7 +301,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")

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){

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");
@@ -382,7 +387,7 @@ export class Trajectory {
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);
@@ -478,9 +483,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;

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

View File

@@ -0,0 +1,102 @@
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);
add("Date", KSPTime(details.dateMET, config.time).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;
add(`Flyby around ${bodyName}`, "", 1);
add("SOI enter date", KSPTime(details.soiEnterDateMET, config.time).stringYDHMS("hms", "emt") + " MET", 2);
add("SOI exit date", KSPTime(details.soiExitDateMET, config.time).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");
}

1
src/types.d.ts vendored
View File

@@ -299,6 +299,7 @@ type ManeuvreDetails = {
progradeDV: number,
normalDV: number,
radialDV: number,
totalDV: number,
ejectAngle?: number
};