diff --git a/dock.ks b/dock.ks
new file mode 100644
index 0000000..c7b0194
--- /dev/null
+++ b/dock.ks
@@ -0,0 +1,124 @@
+@LAZYGLOBAL off.
+
+run once "lib/rendezvous".
+run once "lib/rocket".
+run once "lib/vectors".
+
+
+function translate {
+ //
+ // Translate the ship's position by the given vector using RCS thrusters.
+ //
+ parameter translation_vector_delegate.
+ parameter max_speed.
+
+ local lock translation_vector to translation_vector_delegate().
+ // Find available RCS acceleration by Newton's second law: (F=mg). Note that the acceleration is conservatively divided
+ // by 6 since thrusters might not be aligned with thrust vector (but assumed evenly distributed on the 6 axes).
+ // Furthermore, acceleration is limited to 0.05m/s^2 since small ships with low mass are uncontrollable if allowed to accelerate wildly.
+ local acceleration is min(0.05, (rcs_maxthrust() / SHIP:MASS) / 6).
+ // Time t to travel distance d under constant acceleration a is t = sqrt(2d/a) (https://en.wikipedia.org/wiki/Equations_for_a_falling_body).
+ // Multiply this by the available acceleration to get speed (s * m/s^2 = m/s) required to decelerate to 0 by the time d=0.
+ local lock desired_speed to min(max_speed, sqrt(2*translation_vector:mag / acceleration) * acceleration).
+ local lock desired_velocity to translation_vector:normalized * desired_speed.
+
+ // The proportional gain factor is set to the square root of the ship's mass. Why? It was empirically shown to work.
+ // The idea is that small ships are more sensitive to adjustments, and so small error corrections will do more harm than good.
+ local pid_x is PIDLOOP(sqrt(SHIP:MASS), 0.01, 0.001, -1, 1). // kp, ki, kd, minoutput, maxoutput
+ local pid_y is PIDLOOP(sqrt(SHIP:MASS), 0.01, 0.001, -1, 1).
+ local pid_z is PIDLOOP(sqrt(SHIP:MASS), 0.01, 0.001, -1, 1).
+
+ //vdraw(SHIP:controlpart:position, translation_vector@, WHITE).
+ //vdraw(SHIP:controlpart:position, relative_velocity@, GREEN).
+
+ RCS on.
+ until translation_vector:mag < 0.05 and relative_velocity():mag < 0.01 {
+ set pid_x:setpoint to desired_velocity:x.
+ set pid_y:setpoint to desired_velocity:y.
+ set pid_z:setpoint to desired_velocity:z.
+
+ local x is pid_x:update(TIME:seconds, relative_velocity():x).
+ local y is pid_y:update(TIME:seconds, relative_velocity():y).
+ local z is pid_z:update(TIME:seconds, relative_velocity():z).
+
+ set SHIP:CONTROL:TRANSLATION to ship_raw_to_ship_control(V(x,y,z)).
+ }
+}
+
+
+function dock {
+ //
+ // Dock the ship with another vessel.
+ // Prerequisites:
+ // Must be controlling from a local docking port (right click -> control from here).
+ // TARGET must be a docking port (right click -> set as target).
+ //
+ parameter max_translation_speed is 1.0.
+ parameter final_docking_speed is 0.1.
+
+ print "==================== DOCKING =====================".
+
+ // Target port vectors
+ local lock target_port to TARGET:nodeposition.
+ local lock target_port_facing to TARGET:portfacing:vector.
+ //vdraw(target_port@, target_port_facing@, CYAN).
+
+ // Local port vectors
+ local lock local_port to SHIP:controlpart:nodeposition.
+ local lock local_port_facing to SHIP:controlpart:portfacing:vector.
+ //vdraw(local_port@, local_port_facing@, CYAN).
+
+ SAS off.
+ set NAVMODE to "TARGET".
+
+ print "==> Aligning with target port".
+ local lock alignment_direction to lookdirup(-target_port_facing, TARGET:ship:facing:vector). // align dock-on-dock but with UP the same direction
+ lock STEERING to alignment_direction.
+ wait until vang(SHIP:facing:vector, alignment_direction:vector) <= 1.
+
+ print "==> Translating".
+ // TODO: Translate "around" the target vessel on different axes first so we dont fail if behind the target docking port.
+ translate({return (target_port + target_port_facing) - (local_port + local_port_facing).}, max_translation_speed).
+
+ print "==> Docking".
+ // TARGET will be unset the moment we dock, causing many of the calculations and local locks to cause errors.
+ // Therefore, the final docking will be done using the information available to us now, without any error corrections.
+ lock STEERING to SHIP:facing.
+ set SHIP:CONTROL:TRANSLATION to ship_raw_to_ship_control(target_port - local_port).
+ wait until relative_velocity():mag >= final_docking_speed.
+ unlock_control().
+
+ wait until not HASTARGET.
+ print "==> DOCKING COMPLETE".
+}
+
+
+dock().
+
+
+
+
+// TODO: Make GUI with dropdown of target:dockingports and SHIP:dockingports so user can't fuck it up.
+//if not HASTARGET {
+// print "Please select a target".
+// return.
+//}
+//function find_docking_port {
+// parameter target. // SHIP or TARGET.
+// parameter target_name is target:name.
+//
+// if target:istype("DockingPort") {
+// return target.
+// }
+//
+// local target_ports is target:dockingports.
+// if target_ports:empty {
+// print "No docking ports on " + target_name.
+// return.
+// }
+// if target_ports:length <> 1 {
+// print "Multiple docking ports on " + target_name + ", please select one and try again".
+// return.
+// }
+// return target_ports[0]
+//}
\ No newline at end of file
diff --git a/kOS.xml b/kOS.xml
index 51d2349..608bb9e 100644
--- a/kOS.xml
+++ b/kOS.xml
@@ -83,7 +83,7 @@
-
+
diff --git a/lib/node.ks b/lib/node.ks
index 7e3ad4e..f6cc24f 100644
--- a/lib/node.ks
+++ b/lib/node.ks
@@ -12,13 +12,24 @@ function estimated_burn_duration {
// https://space.stackexchange.com/questions/27375/how-do-i-calculate-a-rockets-burn-time-from-required-velocity
//
parameter burn.
+ parameter isp is isp_sum().
+ parameter maxthrust is SHIP:maxthrust.
if burn:istype("Node") {
set burn to burn:deltav.
}
- local exhaust_velocity is isp_sum() * (CONSTANT:G * KERBIN:mass).
- return ((SHIP:mass * exhaust_velocity) / SHIP:maxthrust) * (1 - CONSTANT:E^(-burn:mag/exhaust_velocity)).
+ local exhaust_velocity is isp * (CONSTANT:G * KERBIN:mass).
+ return ((SHIP:mass * exhaust_velocity) / maxthrust) * (1 - CONSTANT:E^(-burn:mag/exhaust_velocity)).
+}
+
+
+function estimated_rcs_burn_duration {
+ //
+ // Calculate estimated burn duration of a vector or node using RCS thrusters instead of main engines.
+ //
+ parameter burn.
+ return estimated_burn_duration(burn, rcs_isp_sum(), rcs_maxthrust()).
}
diff --git a/lib/rendezvous.ks b/lib/rendezvous.ks
index 8c6cf4a..ecd7969 100644
--- a/lib/rendezvous.ks
+++ b/lib/rendezvous.ks
@@ -3,12 +3,26 @@
run once "lib/node".
+function relative_velocity {
+ //
+ // Return the relative orbital velocity between the ship and our target in the target's inertial frame of reference.
+ //
+ if not HASTARGET {
+ return V(0,0,0). // avoids 'has no target' errors upon docking
+ }
+
+ local t is TARGET.
+ if TARGET:istype("Part") { // e.g. if selected docking port as target
+ set t to TARGET:ship.
+ }
+ return SHIP:velocity:orbit - t:velocity:orbit.
+}
+
+
function kill_relative_velocity {
- parameter target is TARGET.
parameter tolerance is 0.1.
- local relative_velocity is target:velocity:orbit - SHIP:velocity:orbit.
- if relative_velocity:mag > tolerance {
- execute_burn(relative_velocity).
+ if relative_velocity():mag > tolerance {
+ execute_burn(-relative_velocity()).
}
}
diff --git a/lib/rocket.ks b/lib/rocket.ks
index 75138d8..fc37320 100644
--- a/lib/rocket.ks
+++ b/lib/rocket.ks
@@ -51,6 +51,41 @@ function isp_sum {
}
+function rcs_isp_sum {
+ //
+ // Return the sum of ISP for RCS thrusters.
+ //
+ local sum is 0.
+ for thruster in SHIP:modulesnamed("ModuleRCSFX") {
+ set sum to sum + thruster:getfield("rcs isp").
+ }
+ return sum.
+}
+
+
+function rcs_maxthrust {
+ // There's no way to get thrust of RCS dynamically in kOS; gonna have to hard-code it :/
+ // Source: https://wiki.kerbalspaceprogram.com/wiki/Reaction_Control_System#Thrusters
+ local rcs_thrusts is LEXICON( // in kN
+ "RCSBlock", 1.0, // RV-105 RCS Thruster Block
+ "linearRcs", 2.0, // Place-Anywhere 7 Linear RCS Port
+ "vernierEngine", 12.0 // Vernor Engine
+ ).
+
+ local thrust_sum is 0.
+ for module in SHIP:modulesnamed("ModuleRCSFX") {
+ local part_name is module:part:name.
+ if rcs_thrusts:haskey(part_name) {
+ set thrust_sum to thrust_sum + rcs_thrusts[part_name].
+ } else { // if non-stock part
+ print "WARNING: Unknown RCS Thruster '" + part_name + "'; using default thrust of 1.0 kN".
+ set thrust_sum to thrust_sum + 1.0.
+ }
+ }
+ return thrust_sum.
+}
+
+
function deploy_fairings {
print "Deploying fairings".
for module in SHIP:modulesnamed("ModuleProceduralFairing") {
diff --git a/lib/vectors.ks b/lib/vectors.ks
index 7cde524..b49da6a 100644
--- a/lib/vectors.ks
+++ b/lib/vectors.ks
@@ -16,23 +16,40 @@ set_actual_prograde().
function vdraw {
+ //
+ // Wrapper for the VECDRAW-function that doesn't hide the vector by default.
+ //
parameter start. // vector or function. For example, pass {return UP:vector.} to automatically update the draw
parameter vector. // same as start
parameter color is RED.
parameter label is "".
+ parameter scale is 1.0.
+ parameter width is 0.2.
- vecdraw(
+ return vecdraw(
start,
vector,
color,
label,
- 1.0, // scale
- true, // show? defaults to false in original vecdraw
- 0.2 // width
+ scale,
+ true, // show
+ width
).
}
+function ship_raw_to_ship_control {
+ //
+ // Translate a vector in SHIP-RAW coordinates to one in SHIP-CONTROL (STARBOARD, TOP, FORE) coordinates.
+ //
+ parameter vec.
+
+ return V(vdot(vec, FACING:starvector), // projection is correct because SHIP's FACING vectors are normalised
+ vdot(vec, FACING:topvector),
+ vdot(vec, FACING:forevector)). // Same order as SHIP:CONTROL:TRANSLATION's input
+}
+
+
// ANGLES
lock actual_prograde_pitch to vang(actual_prograde:vector, UP:vector).