Last active
May 9, 2025 12:48
-
-
Save numberlesstim/936a32347842ad8f43615443d643f3ab to your computer and use it in GitHub Desktop.
kOS script for KSP
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // BringMeThere.ks | |
| // Pastebin: MmeLfXSM | |
| // Gist: 936a32347842ad8f43615443d643f3ab | |
| function getAltitudeAtAnomaly { | |
| parameter _ta. | |
| parameter _Ap is apoapsis. | |
| parameter _Pe is periapsis. | |
| parameter _r is body:radius. | |
| local _a is (_Ap + _Pe)/2 + _r. | |
| local _le is _a - _Pe - _r. | |
| local _ecc is _le/_a. | |
| return _a * ((1 - _ecc^2) / (1 + _ecc * cos(_ta))) - _r. | |
| } | |
| function getAnomalyAtAltitude { | |
| //NOTE: this will always return between 0 and 180 | |
| parameter _alt. | |
| parameter _Ap is apoapsis. | |
| parameter _Pe is periapsis. | |
| parameter _r is body:radius. | |
| if _alt <= _Pe return 0. | |
| if _alt >= _Ap return 180. | |
| local _h is _r + _alt. | |
| local _c is _Ap - _Pe. | |
| local _q is 2 * _r + _Ap + _Pe - _h. | |
| local _beta is arccos((_h^2 + _c^2 - _q^2) / (2 * _h * _c)). | |
| return 180 - _beta. | |
| } | |
| function getOrbitalPeriod { | |
| parameter _a. //semi-major-axis | |
| parameter _mu is body:mu. | |
| return 2 * constant:pi * sqrt(_a^3 / _mu). | |
| } | |
| function getApoapsis { | |
| parameter _spd. | |
| parameter _Pe is periapsis. | |
| parameter _bd is body. | |
| parameter _mu is _bd:mu. | |
| local _peR is _pe + _bd:radius. | |
| return (-_peR * _spd + sqrt(_peR * (_peR * _spd^2 + 8 * _mu))) / (2 * _spd) - _bd:radius. | |
| } | |
| function getTravelTime { | |
| parameter _ta_end. | |
| parameter _ta_start is orbit:trueAnomaly. | |
| parameter _Ap is apoapsis. | |
| parameter _Pe is periapsis. | |
| parameter _r is body:radius. | |
| local _a is (_Ap + _Pe)/2 + _r. | |
| local _b is getSemiMinorAxis(_Ap, _Pe, _r). | |
| local _areaspd is constant:pi * _a * _b / getOrbitalPeriod(_a). | |
| return getSweptArea(_ta_end, _ta_start, _Ap, _Pe, _r)/_areaspd. | |
| } | |
| function getSemiMinorAxis { | |
| parameter _Ap is apoapsis. | |
| parameter _Pe is periapsis. | |
| parameter _r is body:radius. | |
| local _a is (_Ap + _Pe)/2 + _r. | |
| local _le is _a - _Pe - _r. | |
| local _ecc is _le/_a. | |
| return _a * sqrt(1 - _ecc^2). | |
| } | |
| function getSemiMajorAxisFromSpeedAndAltitude { | |
| parameter _spd. | |
| parameter _alt. | |
| parameter _r is body:radius. | |
| parameter _mu is body:mu. | |
| return 1/(2/(_r + _alt) - _spd^2/_mu). | |
| } | |
| function getPeriapsisFromAngleAndAltitude { | |
| parameter _alpha. | |
| parameter _alt. | |
| parameter _sma is obt:semimajoraxis. | |
| parameter _r is body:radius. | |
| global h is _alt + _r. | |
| global q is _sma * 2 - h. | |
| global gamma is _alpha * 2. | |
| global c is sqrt(h^2 + q^2 - 2*h*q*cos(gamma)). | |
| return _sma - c/2 - _r. | |
| } | |
| function getEllipseAreaSection { | |
| parameter _ta. //true anomaly in deg. 0 --> periapsis, 180 --> apoapsis | |
| parameter _Ap is apoapsis. | |
| parameter _Pe is periapsis. | |
| parameter _r is body:radius. | |
| local _a is (_Ap + _Pe)/2 + _r. | |
| local _b is getSemiMinorAxis(_Ap, _Pe, _r). | |
| local _counter is floor(_ta/180)/2. | |
| set _ta to mod(_ta, 360). | |
| local _inverter is 1. | |
| if _ta >= 180 { | |
| set _ta to 360 - _ta. | |
| set _inverter to -1. | |
| set _counter to _counter + .5. | |
| } | |
| local _h is getAltitudeAtAnomaly(_ta, _Ap, _Pe, _r) + _r. | |
| local _l is 2 * _r + _Ap + _Pe - _h. | |
| local _beta is arctan(_h*sin(_ta)/abs(_a-_Pe-_r + _h*cos(_ta))). | |
| local _betaTrans is 0. | |
| if _l >= _h { | |
| set _betaTrans to arctan(_a/_b*abs(tan(_beta))). | |
| } | |
| else { | |
| set _betaTrans to 180-arctan(_a/_b*abs(tan(_beta))). | |
| set _beta to 180 - _beta. | |
| } | |
| local _K is _betaTrans * _a * _b * constant:pi/360. | |
| local _Q is _h * sin(_ta) * (_a - _Pe - _r) / 2. | |
| return _counter * constant:pi * _a * _b + _inverter * (_K - _Q). | |
| } | |
| function getSweptArea { | |
| parameter _ta_end. //true anomaly in deg. 0 --> periapsis, 180 --> apoapsis | |
| parameter _ta_start. //return < 0 if start > end!! | |
| parameter _Ap is apoapsis. | |
| parameter _Pe is periapsis. | |
| parameter _r is body:radius. | |
| until min(_ta_start, _ta_end) >= 0 { | |
| set _ta_start to _ta_start + 360. | |
| set _ta_end to _ta_end + 360. | |
| } | |
| return getEllipseAreaSection(_ta_end, _Ap, _Pe, _r) - getEllipseAreaSection(_ta_start, _Ap, _Pe, _r). | |
| } | |
| function showMeTheWayTo { | |
| parameter tarGp. | |
| parameter tarAlt. | |
| parameter tarVs. | |
| parameter tarGs. | |
| parameter extraOrbits is 0. | |
| if tarVs > 0 return false. | |
| if tarGs < 0 return false. | |
| if obt:eccentricity > 0.005 { | |
| print "Orbit not circular enough". | |
| return lex("Function", "ShowMeTheWayTo", "Params", lex("TarGp", tarGp, "TarAlt", tarAlt, "TarVs", tarVs, "TarGs", tarGs, "ExtraOrbits", extraOrbits), "Success", false, "Reason", "Initial orbit is not circular enough"). | |
| } | |
| global finalObt is lex(). | |
| set finalObt:SemiMajorAxis to getSemiMajorAxisFromSpeedAndAltitude(sqrt(tarVs^2 + tarGs^2), tarAlt). | |
| set finalObt:periapsis to getPeriapsisFromAngleAndAltitude(arctan(-tarVs/tarGs), tarAlt, finalObt:SemiMajorAxis). | |
| set finalObt:apoapsis to 2*(finalObt:SemiMajorAxis - body:radius) - finalObt:periapsis. | |
| if finalObt:apoapsis < body:atm:height { | |
| if finalObt:apoapsis >= 0 print "Final apoapsis below atmosphere". | |
| else print "Final apoapsis below ground or outside of SOI". | |
| return lex("Function", "ShowMeTheWayTo", "Params", lex("TarGp", tarGp, "TarAlt", tarAlt, "TarVs", tarVs, "TarGs", tarGs, "ExtraOrbits", extraOrbits), "Success", false, "Reason", "Final apoapsis < 0 or below atmosphere"). | |
| } | |
| set finalObt:trueAnomaly to getAnomalyAtAltitude(tarAlt, finalObt:apoapsis, finalObt:periapsis). | |
| if tarVs < 0 set finalObt:trueanomaly to 360 - finalObt:trueanomaly. | |
| set finalObt:timeSinceAp to getTravelTime(finalObt:trueAnomaly, 180, finalObt:apoapsis, finalObt:periapsis). | |
| set finalObt:timeSincePe to getTravelTime(finalObt:trueAnomaly, 0, finalObt:apoapsis, finalObt:periapsis). | |
| set finalObt:duration to finalObt:timeSinceAp. | |
| set finalObt:ApSpd to sqrt(body:mu*(2/(finalObt:apoapsis+body:radius) - 1/finalObt:SemiMajorAxis)). | |
| if finalObt:apoapsis >= body:soiradius - body:radius { | |
| print "Failed! Required apoapsis would be outside of SOI". | |
| return lex("Function", "ShowMeTheWayTo", "Params", lex("TarGp", tarGp, "TarAlt", tarAlt, "TarVs", tarVs, "TarGs", tarGs, "ExtraOrbits", extraOrbits), "Success", false, "Reason", "Final apoapsis outside of SOI"). | |
| } | |
| global transferObt is lex(). | |
| set transferObt:semiMajorAxis to (finalObt:apoapsis + altitude)/2 + body:radius. | |
| set transferObt:period to getOrbitalPeriod(transferObt:semiMajorAxis). | |
| set transferObt:duration to transferObt:period/2. | |
| set totalduration to transferObt:duration + finalObt:duration. | |
| set totalAngle to finalObt:trueanomaly. | |
| set rotationangle to 360/body:rotationperiod * totalduration. | |
| set startlngRaw to tarGp:lng - totalAngle + rotationangle. | |
| set startlng to mod(startlngRaw + 180, 360) - 180. | |
| set coastangle to startlng - longitude. | |
| until coastangle > 0 set coastangle to coastangle + 360. | |
| set osd to 360/obt:period. | |
| set bsd to 360/body:rotationperiod. | |
| set tsd to osd - bsd. | |
| if tsd < 0 set coastangle to coastangle - 360. | |
| set coastangle to coastangle + 360*extraorbits * (choose 1 if tsd > 0 else -1). | |
| set transferObt:node to node(time:seconds + coastangle/tsd, 0, 0, 0). | |
| set finalObt:node to node(transferObt:node:time + transferObt:duration, 0, 0, 0). | |
| if altitude >= finalObt:apoapsis { | |
| set transferObt:apoapsis to 2 * (transferObt:SemiMajorAxis - body:radius) - finalObt:apoapsis. | |
| set transferObt:periapsis to finalObt:apoapsis. | |
| set transferObt:departSpd to sqrt(body:mu*(2/(transferObt:apoapsis+body:radius) - 1/transferObt:SemiMajorAxis)). | |
| set transferObt:arriveSpd to sqrt(body:mu*(2/(transferObt:periapsis+body:radius) - 1/transferObt:SemiMajorAxis)). | |
| set transferObt:departAnom to 180. | |
| } | |
| else { | |
| set transferObt:periapsis to 2 * (transferObt:SemiMajorAxis - body:radius) - finalObt:apoapsis. | |
| set transferObt:apoapsis to finalObt:apoapsis. | |
| set transferObt:DepartSpd to sqrt(body:mu*(2/(transferObt:periapsis+body:radius) - 1/transferObt:SemiMajorAxis)). | |
| set transferObt:arriveSpd to sqrt(body:mu*(2/(transferObt:apoapsis+body:radius) - 1/transferObt:SemiMajorAxis)). | |
| set transferObt:departAnom to 0. | |
| } | |
| set transferObt:node:prograde to transferObt:departSpd - velocityat(ship, transferobt:node:time):obt:mag. | |
| set finalObt:departSpd to sqrt(body:mu*(2/(finalObt:apoapsis+body:radius) - 1/finalObt:SemiMajorAxis)). | |
| set finalObt:node:prograde to finalObt:departSpd - transferObt:arriveSpd. | |
| add transferObt:node. | |
| add finalObt:node. | |
| //inclination | |
| global obtNormVec is vcrs(velocity:orbit, up:vector). | |
| global tarNormVec is vcrs(latlng(0, tarGp:lng + 90 + (transferObt:node:time - time:seconds + totalDuration) * 360/body:rotationperiod):position - body:position, latlng(tarGP:lat, tarGp:lng + (transferObt:node:time - time:seconds + totalDuration) * 360/body:rotationperiod):position - body:position). | |
| global ascAnVec is vcrs(tarNormVec, obtNormVec). | |
| //ensure ascAnVec points away from the active transferOrbit half, so that incNode will be the first. | |
| if vang(ascAnVec, up:vector) > vang(up:vector, positionat(ship, transferObt:node:time) - body:position) set ascAnVec to -ascAnVec. | |
| set incAngle to vang(ascAnVec, positionat(ship, transferObt:node:time) - body:position). | |
| set incTime to transferObt:node:time - incAngle * obt:period/360. | |
| if incTime - time:seconds < 0 { | |
| //incNode will be in the past. Move everything one obt ahead. | |
| for n in allnodes remove n. | |
| return showMeTheWayTo(tarGp, tarAlt, tarVs, tarGs, extraOrbits + 1). | |
| } | |
| set relinc to vang(obtNormVec, tarNormVec). | |
| set spd to velocityat(ship, incTime):obt. | |
| set sgn to choose 1 if vdot(spd, tarNormVec) < 0 else -1. | |
| set spd to spd:mag. | |
| set incNode to node(incTime, 0, sgn * spd * sin(relinc), spd * cos(relinc) - spd). | |
| add incNode. | |
| print "ETA: <color>" + timespan(finalObt:node:eta + finalObt:duration):full + "</color>". | |
| return lex("Function", "ShowMeTheWayTo", "Params", lex("TarGp", tarGp, "TarAlt", tarAlt, "TarVs", tarVs, "TarGs", tarGs, "ExtraOrbits", extraOrbits), "Success", true). | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment