Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Orbital maneuvers #64

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
50 changes: 50 additions & 0 deletions library/lib_exec_node.ks
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
@lazyglobal off.

// This file is currently just a placeholder for a more robust version,
// uploaded solely because it is a very basic function for KSP.
// I would be glad if anyone helps to develop it (add staging,
// perhaps improve burntime calculations, add special cases such
// as not enough fuel, etc.)

function MaxShipThrust{
// this function is needed as a workaround
// to kOS bug regarding ship thrust.
local engs is list().
list engines in engs.
local mth is 0.
for eng in engs{
if eng:VISP > 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a way to test if the engine is active?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function was not written by me - it's taken from here: KSP-KOS/KOS#940

set mth to mth + eng:MAXTHRUST.
}
}
return mth.
}

function exec_node{
parameter nd.

local mth is MaxShipThrust().
lock burntime to nd:deltav:mag/(mth/mass).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. if maximum thrust is 0 (e.g. the player forgot to activate engines or the stage burned out) it will crash with cryptic error message

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. If I understand it right this formula is not perfect: it doesn't account for the fact that the ship mass decreases in the process.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I think this PR should contain just the orbital library - the exec_node is not really finished and I included it here just because I noticed there was none in KSLib. But really, as it stands now, exec_node's quality is likely too far below quality to be included.


lock steering to nd.
wait until nd:eta<burntime/2.
local timeout is time:seconds+max(burntime*2,100). // just a guess

lock throttle to cos(vang(nd:deltav,ship:facing:vector)).
// Thanks to cosine, we burn only when we face the correct direction.

wait until burntime<5 or time:seconds>timeout.
// last 5 seconds of burn should be slower
local scale is nd:deltav:mag.

lock throttle to nd:deltav:mag/scale*
cos(vang(nd:deltav,ship:facing:vector)).
// We burn slower and slower.

local timeout is time:seconds+100.
wait until nd:deltav:mag<scale*0.01 or time:seconds>timeout.
lock throttle to 0.

unlock throttle.
unlock steering.
}
259 changes: 259 additions & 0 deletions library/lib_orbital_maneuvers.ks
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
@lazyglobal off.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs // This file is distributed under the terms of the MIT license, (c) the KSLib team


// This file contains a few functions that concentrate on creating
// maneuver nodes completing most common KSP tasks.

// This function creates a node (but doesn't place it) at time t, with
// specified delta-v vector.
function make_node_t_deltav{
parameter
t,
dv.

local pro_unit is velocityat(ship,t):orbit:normalized.
local rad_unit is -VXCL(pro_unit,body:position-positionat(ship,t))
:normalized.
local nor_unit is VCRS(pro_unit,rad_unit).
return node(t,rad_unit*dv,nor_unit*dv,pro_unit*dv).
}

// This function gets time as a parameter, and returns a maneuver
// node representing velocity change leading to circular orbit
// with that burn happening at specified time. E.g. if you want to
// circularize at apoapsis, use:
// circularize_at_time(time:seconds+eta:apoapsis).
function circularize_at_time{
parameter t.

local r is (body:position-positionat(ship,t)).
local actual_vel is velocityat(ship,t):orbit.
local expected_vel is VXCL(r,actual_vel):normalized*sqrt(body:mu/r:mag).
local dv is expected_vel-actual_vel.
return make_node_t_deltav(t,dv).
}

// This function gets two arguments: orbit and true anomaly in degrees
// and then returns time remaining until orbiting object will pass
// through that true anomaly
function time_to_true_anomaly{
parameter
orb,
a2.

local e is orb:eccentricity.
local a1 is orb:trueanomaly. // the current one
local f is sqrt((1-e)/(1+e)).

local e1 is 2*arctan(f*tan(a1/2)).
local e2 is 2*arctan(f*tan(a2/2)).
// e1 and e2 are eccentric anomalies at these times in degrees
local m1 is constant():pi/180*e1-e*sin(e1).
local m2 is constant():pi/180*e2-e*sin(e2).
// m1 and m2 are mean anomalies at these times in radians
local n is 2*constant():pi/orb:period.
// n is mean angular velocity
local t1 is m1/n.
local t2 is m2/n.
// t1 and t2 are times with regard to some, non-disclosed epoch,
// at which orbitable will be at true anomaly a1 or a2 respectively
local diff is t2-t1.
if diff<0{ // ETA must be positive, so switch to next orbit
set diff to orb:period+diff.
}
else if diff>orb:period{ // we can do one full orbit less
set diff to diff-orb:period.
}
return diff.
}

// This function takes two orbits CONTAINED IN THE SAME PLANE and returns
// a list of at most three elements:
// - first one is a "status" - if it's equal to -1, then obt1 is fully
// contained within obt2, vice versa for +1. Otherwise, it is equal to
// 0 and the list contains two more elements
// - two other elements, existing only if orbits cross at some points,
// represent the time remaining until ship orbiting obt1 will pass common
// orbit point. This should be the point at which you would do the burn
// to randez-vous
function get_orbit_intersections{
parameter
obt1,
obt2.

local e1 is obt1:eccentricity.
local e2 is obt2:eccentricity.
local a1 is obt1:semimajoraxis.
local a2 is obt2:semimajoraxis.
local B is a1/a2*(1-e1*e1)/(1-e2*e2).
local delta is obt2:argumentofperiapsis-obt1:argumentofperiapsis.
local gamma is arctan2(e1-B*e2*cos(delta), -B*e2*sin(delta)).
local S is (1-B)*cos(gamma)/B/e2/sin(delta).
if S > 1-0.00001{
return list(1).
}
else if S < -1+0.00001{
return list(-1).
}
local beta1 is arcsin(S)-gamma.
local beta2 is 180-arcsin(S)-gamma.
return list(
0,
time_to_true_anomaly(obt1,beta1),
time_to_true_anomaly(obt1,beta2)
).
}

// This function will put a randez-vous node at orbit intersection
// between your orbit and tgt's orbit, if it exists. It assumes orbits
// share common plane.
function match_orbits{
parameter tgt.

local l is get_orbit_intersections(obt,tgt:obt).
if l[0]<>0{
return 0.
}
local t is l[1].
if l[2]<t{
set t to l[2].
}

local actual_vel is velocityat(ship,time:seconds+t):orbit.
local intersection_r is body:position-positionat(ship,time:seconds+t).

local tgt_r is body:position-tgt:position.
local tgt_normal is VCRS(tgt:velocity:orbit,tgt_r).
local side is VCRS(tgt_normal,tgt_r).
local true_anomaly is VANG(intersection_r,tgt_r).
if VDOT(side,intersection_r)<0{
set true_anomaly to -true_anomaly.
}
set true_anomaly to true_anomaly+tgt:obt:trueanomaly.
local t2 is time_to_true_anomaly(tgt:obt,true_anomaly).

local expected_vel is velocityat(tgt,time:seconds+t2):orbit.
return make_node_t_deltav(time:seconds+t,expected_vel-actual_vel).
}

// This function takes an orbit as an argument. It should be IDENTICAL to
// your ship's orbit, other than phase (the other ship or body can be in
// different position in the orbit). The function will then return a node
// that in N orbits (where N is integer argument) will match your position.
function match_orbital_phase{
parameter
orb,
n.

local t1 is time_to_true_anomaly(obt,0).
local t2 is time_to_true_anomaly(orb,0).

local period is obt:period.
local dt is mod(t2-t1+period*1.5,period)-period/2.
// now dt is time difference [-period/2,period/2] between target's
// and my periapsis
local h is 2*obt:semimajoraxis*(1+dt/n/period)^(2/3)-
obt:periapsis-2*obt:body:radius.
return change_opposite_height(time:seconds+t1,h).
}

// This function returns a node that will at specified time raise or lower
// opposite orbit's side to specified height. If called at Pe, it will
// simply change Ap to specified height and vice versa.
function change_opposite_height{
parameter
t,
height.

local r is (body:position-positionat(ship,t)).
local actual_vel is velocityat(ship,t):orbit.
local expected_vel is VXCL(r,actual_vel):normalized*sqrt(
2*body:mu*(height+body:radius)/r:mag/(r:mag+height+body:radius)
).
return make_node_t_deltav(t,expected_vel-actual_vel).
}

// This function rotates ship's orbit by burning normally at specified time
// BY given amomunt (not TO angle, but BY angle).
function change_inclination{
parameter
t,
ang.

local r is (body:position-positionat(ship,t)).
local actual_vel is velocityat(ship,t):orbit.
local normal_vel is VCRS(actual_vel,r):normalized*actual_vel:mag.
return make_node_t_deltav(t,actual_vel*(cos(ang)-1)+normal_vel*sin(ang)).
}

// This function returns a maneuver node aligning orbital planes to
// orbitable passed as an argument.
function align_orbital_plane{
parameter other.

local my_r is (body:position-ship:position).
local my_normal is VCRS(obt:velocity:orbit,my_r).

local other_r is (body:position-other:position).
local other_normal is VCRS(other:obt:velocity:orbit,other_r).

local target_place is VCRS(my_normal,other_normal):normalized.
local pe_place is positionat(ship,time:seconds+eta:periapsis)-body:position.
local my_place is ship:position-body:position.

local vel_at_pe is velocityat(ship,time:seconds+eta:periapsis):orbit.

local true_anomaly is VANG(target_place,pe_place).
if VDOT(target_place,vel_at_pe)<0{
set true_anomaly to -true_anomaly.
}
local true_anomaly_of_me is VANG(my_place,pe_place).
if VDOT(my_place,vel_at_pe)<0{
set true_anomaly_of_me to -true_anomaly_of_me.
}
local diff is mod(true_anomaly-true_anomaly_of_me+360,360).
if diff>180{
set true_anomaly to true_anomaly-180.
// switch descending to ascending node or vice versa to closer one
}
local t is time_to_true_anomaly(obt,true_anomaly).

local inc is VANG(my_normal,other_normal).
local vel_at_t is velocityat(ship,time:seconds+t):orbit.
if VDOT(other_normal,vel_at_t)>0{
set inc to -inc.
}

return change_inclination(time:seconds+t,inc).
}

// This function takes as argument an orbit and a height above ground.
// It returns a list of at most three elements:
// - first one is a status - if -1, then the orbit's apoapsis is too low,
// if +1, orbit's periapsis is too high, otherwise it's zero
// - the next two elements contain time remaining until orbiting object
// passes through specified height
function time_to_pass_height{
parameter
orb,
h.

set h to h+orb:body:radius.
local e is orb:eccentricity.
local p is orb:semimajoraxis*(1-e*e).
if e=0{
set e to 0.0000001.
}
local c is (h-p)/e/h.
if c>1{
return list(-1).
}
if c<-1{
return list(1).
}
local theta is 180-arccos(c).
return list(
0,
time_to_true_anomaly(orb,theta),
time_to_true_anomaly(orb,-theta)
).
}
Loading