Skip to content

Instantly share code, notes, and snippets.

@CorvusCorax
Last active September 30, 2021 14:58
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save CorvusCorax/d6e6e98c946b8bfad56563b514df23a9 to your computer and use it in GitHub Desktop.
Save CorvusCorax/d6e6e98c946b8bfad56563b514df23a9 to your computer and use it in GitHub Desktop.
A Greasemonkey user script for SpaceX ISS SIM - useful to enable hard mode (no HUD) - optionally disable collissions, realistic orbital mechanics and other fun stuff
// ==UserScript==
// @name SpaceX ISS-SIM-Customizer
// @version 0.000016
// @author CorvusCorax
// @match https://iss-sim.spacex.com/
// @description A Greasemonkey user script for SpaceX ISS SIM - useful to enable hard mode (no HUD) - optionally disable collissions, realistic orbital mechanics and other fun stuff
// @include https://iss-sim.spacex.com/
// @license Creative Commons - CC BY 4.0
// ==/UserScript==
// add menu to configure iss-sim to settings DOM tree
var JQ=document.querySelector.bind(document)
JQ("#settings .modal-inner").innerHTML+='<div id="setting-tesla" class="setting active">TESLA:<br><span>HIDDEN EASTEREGG</span></div><div id="setting-space" class="setting active">SPACE:<br><span>RESTRICTED</span></div><div id="setting-hud" class="setting active">HUD:<br><span>ONLINE</span></div><div id="setting-limits" class="setting active">DOCKING CONSTRAINTS:<br><span>ORIGINAL SIM +-0.2deg</span></div><div id="setting-collision" class="setting active">COLLISIONS:<br><span>ENABLED</span></div><div id="setting-dock" class="setting active">ISS DOCKING PORT:<br><span>IDA-2</span></div><div id="setting-challenge" class="setting active">CHALLENGE:<br><span>DOCK</span></div>'
JQ("#hud").innerHTML+='<div id="rendezvous" class="hud-item" style="position: absolute; top: 15%; left: 15%; width: 110px; margin: 0; text-align: center; text-shadow: 0 0 10px #000; display: none;"><div class="label">RNDVZ</div><div id="rendezvous-phase" class="rate"></div><div id="rendezvous-time" class="rate"></div><div id="rendezvous-deltav" class="rate"></div><div id="rendezvous-deltat" class="rate"></div></div><div id="orbit" class="hud-item" style="position: absolute; top: 15%; right: 15%; width: 90px; margin: 0; text-align: center; text-shadow: 0 0 10px #000; display: none;"><div class="label">ORBIT</div><div id="orbit-vel" class="rate"></div><div id="orbit-alt" class="rate"></div><div id="orbit-apogee" class="rate"></div><div id="orbit-perigee" class="rate"></div><div id="orbit-period" class="rate"></div></div>'
JQ("#x-range .distance").style.width="100px"
JQ("#y-range .distance").style.width="100px"
JQ("#z-range .distance").style.width="100px"
{
var d=document.createElement('DIV')
d.id="propellant"
d.class="hud-item"
d.style="position: absolute; top: 15%; left: 5%; width: 100px; margin: 0; text-align: left; text-shadow: 0 0 12px #000; display: none; "
d.innerHTML='<div class="label">DELTA-V BUDGET</div><div id="prop-used" class="rate"></div><div id="prop-left" class="rate"></div><div class="label">MAIN BURN (M)</div><div id="burn-onoff" class="rate">OFF</div><div class="label">STATS</div><div id="stats-mission" class="rate"></div><div id="stats-real" class="rate"></div><div id="stats-fps" class="rate"></div>'
JQ("#hud").after(d)
}
// The main functionality override must be loaded AFTER ISS-SIM has finished loading.
function GMInitializer() {
// allow realistic orbital mechanics
window.GMisOrbitalMechanics = 1
// override gravity toggler function
// Bug: Tampermonkey extension for firefox does not support overriding functions already bound to event listeners
// Fix: Unbind event listener, bind our own instead
$("#setting-gravity").removeEventListener("click", toggleGravity, !1)
$("#setting-gravity").removeEventListener("touchstart", toggleGravity, !1)
var GMAmbientLight = new THREE.AmbientLight(0xffffff, 0.1);
toggleGravity = function() {
if (!GMisChallenge || GMisTrajectoryVisible) return // cannot switch of gravity when trying rendezvous manouver or showing trajectory
GMisOrbitalMechanics = GMisOrbitalMechanics<3 ? GMisOrbitalMechanics+1 : 1
switch (GMisOrbitalMechanics) {
case 1:
(isGravity = !0, $("#setting-gravity").classList.add("active"), $("#setting-gravity span").innerHTML = "ON")
break;
case 2:
(isGravity = !1, $("#setting-gravity span").innerHTML = "PROPER ORBITAL MECHANICS")
$("#setting-gravity").classList.add("active")
$("#orbit").style.display="block"
$("#rendezvous").style.display="block"
// Have earth the correct way around, and ISS in correct orbit
earthObject.rotation.fromArray([0,0,(-90+51) * Math.PI/180.0])
camera.children.push(GMAmbientLight)
GMSpeedMeasure=0 // force end speed measurement - fluctuating deltaT messes up orbits
break;
case 3:
($("#setting-gravity").classList.remove("active"), $("#setting-gravity span").innerHTML = "OFF")
$("#orbit").style.display="none"
$("#rendezvous").style.display="none"
// Revert back to ISS-SIM reverse earth
earthObject.rotation.fromArray([0,0,51 * Math.PI/180.0])
starsObject.rotation.fromArray([0,0,0])
lightISS_Primary.position.set(0, 4e10, 4e10)
camera.children.pop(GMAmbientLight)
issObject.children[3].children[0].children[0].children[6].children[8].children[6].children[6].children[3].rotation.x=60*Math.PI/180.0
issObject.children[3].children[0].children[0].children[6].children[8].children[7].children[6].children[3].rotation.x=60*Math.PI/180.0
break;
}
}
$("#setting-gravity").addEventListener("click", toggleGravity, !1)
$("#setting-gravity").addEventListener("touchstart", toggleGravity, !1)
// unhide the Tesla
window.GMisTeslaHidden = 1
function GMToggleTeslaHidden() {
GMisTeslaHidden ? (GMisTeslaHidden = !1, $("#setting-tesla span").innerHTML = "VISIBLE") : (GMisTeslaHidden = !0, $("#setting-tesla span").innerHTML = "HIDDEN EASTEREGG")
}
$("#setting-tesla").addEventListener("click", GMToggleTeslaHidden, !1)
$("#setting-tesla").addEventListener("touchstart", GMToggleTeslaHidden, !1)
// unrestrict space
window.GMisSpaceRestricted = 1
function GMToggleSpaceRestricted() {
GMisSpaceRestricted ? (GMisSpaceRestricted = !1, $("#setting-space span").innerHTML = "INFINITE") : (GMisChallenge && ( GMisSpaceRestricted = !0, $("#setting-space span").innerHTML = "RESTRICTED"))
}
$("#setting-space").addEventListener("click", GMToggleSpaceRestricted, !1)
$("#setting-space").addEventListener("touchstart", GMToggleSpaceRestricted, !1)
// HUD visible
window.GMHUDStatus = 1
window.GMisHUDVisible = 1
window.GMisTrajectoryVisible = 0
function GMshowHUD() {
issObject.children[4].visible=true
issObject.children[5].visible=true
issObject.children[6].visible=true
issObject.children[7].visible=true
$("#hud").style.display="block"
$("#hud-tips").style.display="block"
$("#hud-worms").style.display="block"
}
function GMhideHUD() {
issObject.children[4].visible=false
issObject.children[5].visible=false
issObject.children[6].visible=false
issObject.children[7].visible=false
$("#hud").style.display="none"
$("#hud-tips").style.display="none"
$("#hud-worms").style.display="none"
}
function GMToggleHUDVisible() {
GMHUDStatus = GMHUDStatus<3 ? GMHUDStatus+1 : 1
switch (GMHUDStatus) {
case 1:
GMisHUDVisible = !0, $("#setting-hud").classList.add("active"), $("#setting-hud span").innerHTML = "ONLINE", GMshowHUD()
break;
case 2:
( GMisOrbitalMechanics==2 || ( GMisOrbitalMechanics=1, toggleGravity() ) ), GMisTrajectoryVisible = !0, $("#setting-hud span").innerHTML = "ONLINE + TRAJECTORY"
break;
case 3:
GMisHUDVisible = !1, GMisTrajectoryVisible = !1, $("#setting-hud").classList.remove("active"), $("#setting-hud span").innerHTML = "OFFLINE", GMhideHUD()
break;
}
}
$("#setting-hud").addEventListener("click", GMToggleHUDVisible, !1)
$("#setting-hud").addEventListener("touchstart", GMToggleHUDVisible, !1)
// relaxed limits
window.GMLimits = 1
window.GMisFuelLimit = !1
window.GMisLimitsOriginal = !0
function GMToggleLimits() {
GMLimits = GMLimits < 4 ? GMLimits+1 : 1
switch (GMLimits) {
case 1:
GMisLimitsOriginal = !0, $("#setting-limits span").innerHTML = "ORIGINAL SIM +-0.2deg"
GMisFuelLimit = !1
break;
case 2:
GMisLimitsOriginal = !1, $("#setting-limits span").innerHTML = "IDA SPECS +-4.0deg 0.1m/s"
GMisFuelLimit = !1
break;
case 3:
GMisLimitsOriginal = !0, $("#setting-limits span").innerHTML = "LIMITED PROPELLANT, ORIGINAL SIM +-0.2deg"
GMisFuelLimit = !0
break;
case 4:
GMisLimitsOriginal = !1, $("#setting-limits span").innerHTML = "LIMITED PROPELLANT, IDA SPECS +-4.0deg 0.1m/s"
GMisFuelLimit = !0
break;
}
}
$("#setting-limits").addEventListener("click", GMToggleLimits, !1)
$("#setting-limits").addEventListener("touchstart", GMToggleLimits, !1)
// collision
window.GMisCollisions = 1
function GMToggleCollisions() {
GMisCollisions ? (GMisCollisions = !1, $("#setting-collision").classList.remove("active"), $("#setting-collision span").innerHTML = "DISABLED") : (GMisCollisions = !0, $("#setting-collision").classList.add("active"), $("#setting-collision span").innerHTML = "ENABLED" )
}
$("#setting-collision").addEventListener("click", GMToggleCollisions, !1)
$("#setting-collision").addEventListener("touchstart", GMToggleCollisions, !1)
// docking ports
window.GMisDocking = 1
var GMRotForward = new THREE.Object3D()
GMRotForward.position.fromArray([0,0,-0.1])
var GMRotPort = new THREE.Object3D()
GMRotPort.rotateY(0.5*Math.PI)
GMRotPort.position.fromArray([-0.1,0,0])
var GMRotAft = new THREE.Object3D()
GMRotAft.rotateY(Math.PI)
GMRotAft.position.fromArray([0,0,0.1])
var GMRotZenithUS = new THREE.Object3D()
GMRotZenithUS.rotateX(-0.5*Math.PI)
GMRotZenithUS.position.fromArray([0,-0.1,0])
var GMRotNadirUS = new THREE.Object3D()
GMRotNadirUS.rotateX(0.5*Math.PI)
GMRotNadirUS.position.fromArray([0,0.1,0])
var GMRotZenithRU = GMRotAft.clone()
GMRotZenithRU.rotateX(-0.5*Math.PI)
GMRotZenithRU.position.fromArray([0,-0.1,0])
var GMRotNadirRU = GMRotAft.clone()
GMRotNadirRU.rotateX(0.5*Math.PI)
GMRotNadirRU.position.fromArray([0,0.1,0])
var GMPosIDA2 = new THREE.Vector3(0,0,0)
var GMPosHarmonyZt = new THREE.Vector3(0,3.5,-4.75)
var GMPosHarmonyNd = new THREE.Vector3(0,-2.1,-4.75)
var GMPosPMA3 = new THREE.Vector3(12,0,-21)
var GMPosZvezda = new THREE.Vector3(0,1.5,-53.9)
var GMPosPoisk = new THREE.Vector3(0,7.0,-40.95)
var GMPosPirs = new THREE.Vector3(0,-4.1,-40.95)
var GMPosRassvet = new THREE.Vector3(0,-6.5,-28.0)
var GMIssRestoreOffset = GMPosIDA2.clone()
var GMIssApproach = GMRotForward.clone()
function GMRestoreISSPos() {
issObject.children[0].position.add(GMIssRestoreOffset)
issObject.children[1].position.add(GMIssRestoreOffset)
issObject.children[2].position.add(GMIssRestoreOffset)
issObject.children[3].position.add(GMIssRestoreOffset)
}
function GMSetISSPos() {
var GMnegRestore = GMIssRestoreOffset.clone()
GMnegRestore.negate()
issObject.children[0].position.add(GMnegRestore)
issObject.children[1].position.add(GMnegRestore)
issObject.children[2].position.add(GMnegRestore)
issObject.children[3].position.add(GMnegRestore)
}
function GMRingsFront() {
issObject.children[4].position.fromArray([0,0,20])
issObject.children[5].position.fromArray([0,0,40])
issObject.children[6].position.fromArray([0,0,80])
issObject.children[4].rotation.fromArray([0,0,0])
issObject.children[5].rotation.fromArray([0,0,0])
issObject.children[6].rotation.fromArray([0,0,0])
}
function GMRingsAft() {
issObject.children[4].position.fromArray([0,0,-20])
issObject.children[5].position.fromArray([0,0,-40])
issObject.children[6].position.fromArray([0,0,-80])
issObject.children[4].rotation.fromArray([0,Math.PI,0])
issObject.children[5].rotation.fromArray([0,Math.PI,0])
issObject.children[6].rotation.fromArray([0,Math.PI,0])
}
function GMRingsZenith() {
issObject.children[4].position.fromArray([0,20,0])
issObject.children[5].position.fromArray([0,40,0])
issObject.children[6].position.fromArray([0,80,0])
issObject.children[4].rotation.fromArray([-0.5*Math.PI,0,0])
issObject.children[5].rotation.fromArray([-0.5*Math.PI,0,0])
issObject.children[6].rotation.fromArray([-0.5*Math.PI,0,0])
}
function GMRingsNadir() {
issObject.children[4].position.fromArray([0,-20,0])
issObject.children[5].position.fromArray([0,-40,0])
issObject.children[6].position.fromArray([0,-80,0])
issObject.children[4].rotation.fromArray([0.5*Math.PI,0,0])
issObject.children[5].rotation.fromArray([0.5*Math.PI,0,0])
issObject.children[6].rotation.fromArray([0.5*Math.PI,0,0])
}
function GMToggleDocking() {
GMRestoreISSPos()
GMisDocking = GMisDocking<8 ? GMisDocking+1 : 1
var name = ""
switch (GMisDocking) {
case 1:
GMIssRestoreOffset.copy(GMPosIDA2)
GMIssApproach.copy(GMRotForward)
name = "IDA-2"
GMRingsFront()
break;
case 2:
GMIssRestoreOffset.copy(GMPosHarmonyZt)
GMIssApproach.copy(GMRotZenithUS)
name = "HARMONY ZENITH"
GMRingsZenith()
break;
case 3:
GMIssRestoreOffset.copy(GMPosHarmonyNd)
GMIssApproach.copy(GMRotNadirUS)
name = "HARMONY NADIR"
GMRingsNadir()
break;
case 4:
GMIssRestoreOffset.copy(GMPosPMA3)
GMIssApproach.copy(GMRotPort)
name = "PMA3 ( WATCH RADIATOR! )"
GMRingsNadir()
issObject.children[4].position.x=5
issObject.children[5].position.x=5
issObject.children[6].position.x=5
// rotate radiator to allow approach
issObject.children[3].children[0].children[0].children[6].children[8].children[7].children[5].rotateY(-Math.PI/2)
break;
case 5:
GMIssRestoreOffset.copy(GMPosZvezda)
GMIssApproach.copy(GMRotAft)
name = "ZVEZDA"
GMRingsAft()
issObject.children[3].children[0].children[0].children[6].children[8].children[7].children[5].rotateY(-Math.PI/2)
break;
case 6:
GMIssRestoreOffset.copy(GMPosPoisk)
GMIssApproach.copy(GMRotZenithRU)
name = "POISK"
GMRingsZenith()
break;
case 7:
GMIssRestoreOffset.copy(GMPosPirs)
GMIssApproach.copy(GMRotNadirRU)
name = "PIRS"
GMRingsNadir()
break;
case 8:
GMIssRestoreOffset.copy(GMPosRassvet)
GMIssApproach.copy(GMRotNadirRU)
name = "RASSVET"
GMRingsNadir()
break;
}
GMSetISSPos()
$("#setting-dock span").innerHTML = name
}
$("#setting-dock").addEventListener("click", GMToggleDocking, !1)
$("#setting-dock").addEventListener("touchstart", GMToggleDocking, !1)
// challenge
window.GMisChallenge = 1
$("#option-restart").removeEventListener("click", resetPosition, !1)
$("#option-restart").removeEventListener("touchstart", resetPosition, !1)
window.GMPhase = 127
window.GMPerigee = 190
window.GMApogee = 205
window.GMInclination = Math.PI*1000 // 3000 odd meters ;)
resetPosition = function() {
var px=12,
py=30,
pz=50,
vx=0,
vy=0,
vz=0
if (!GMisChallenge) {
var phi = new THREE.Euler(-GMPhase*Math.PI/180,0,0),
r = GMGravBodyRadius+(GMPerigee*1000),
rref = GMGravBodyCenter.length(),
a = GMGravBodyRadius+((GMPerigee+GMApogee)*500),
pos = new THREE.Vector3(0,0,0).sub(GMGravBodyCenter).applyEuler(phi).setLength(r).add(GMGravBodyCenter),
v = Math.sqrt(GMG*GMMe*((2.0/r)-(1.0/a))),
omega = Math.sqrt((GMG*GMMe)/rref)/rref,
vel = new THREE.Vector3(0,0,v).applyEuler(phi).sub(GMCalcPseudoVelocity(pos,omega)).multiplyScalar(GMFixedStepTime)
px=pos.x + GMInclination // this adds enough challenge by screwing everything just a little bit up
py=pos.y
pz=pos.z
vx=vel.x
vy=vel.y
vz=vel.z
}
isGameOver=!0, resetMovement(), gsap.to(motionVector, 5, {
x: vx,
y: vy,
z: vz,
ease: "expo.out"
}), gsap.to(translationVector, 5, {
x: 0,
y: 0,
z: 0,
ease: "expo.out"
}), gsap.to(camera.position, 5, {
x: px,
y: py,
z: pz,
ease: "expo.out"
}), gsap.to(camera.rotation, 5, {
x: -20 * toRAD,
y: -10 * toRAD,
z: 15 * toRAD,
ease: "expo.out",
onComplete: GMGameStart
})
}
function GMToggleChallenge() {
GMisChallenge ? ((GMisSpaceRestricted && GMToggleSpaceRestricted()), ( GMisOrbitalMechanics==2 || ( GMisOrbitalMechanics=1, toggleGravity() ) ), GMisChallenge = !1, $("#setting-challenge span").innerHTML = "RENDEZVOUS + DOCK", GMDeltaVBudget=175, resetPosition()) : (GMisChallenge = !0, $("#setting-challenge span").innerHTML = "DOCK", GMDeltaVBudget=25, resetPosition() )
}
$("#option-restart").addEventListener("click", resetPosition, !1)
$("#option-restart").addEventListener("touchstart", resetPosition, !1)
$("#setting-challenge").addEventListener("click", GMToggleChallenge, !1)
$("#setting-challenge").addEventListener("touchstart", GMToggleChallenge, !1)
// lots of functionality here for proper orbital mechanics - start with globals
var GMCameraRotationHelper = new THREE.Object3D()
var GMoldTime = 0
window.GMFixedStepTime = 0.05
window.GMCurrentFrameTime = 0.05
window.GMMissionTimer = 0
window.GMGameTimer = 0
// fix motion - ISS-SIM keeps all velocities in a per frame format, even though frame timing is varying
// let's fix that. This function undo's all motion computations and reapply's them with correct timing
// the alternative would be to reimplement the render() function.
function GMfixMotion() {
var k = GMCurrentFrameTime/GMFixedStepTime
// fix translation
!isGameOver && isGravity && (camera.position.y = camera.position.y + gravity)
camera.position.sub(motionVector)
correctedMotion = motionVector.clone().multiplyScalar(k)
camera.position.add(correctedMotion)
!isGameOver && isGravity && (camera.position.y = camera.position.y - (gravity*k))
// fix rotation
camera.rotateZ(-currentRotationZ)
camera.rotateY(-currentRotationY)
camera.rotateX(-currentRotationX)
camera.rotateX(currentRotationX*k)
camera.rotateY(currentRotationY*k)
camera.rotateZ(currentRotationZ*k)
}
window.GMDeltaVUsed = 0
window.GMDeltaVBudget = 25
var GMOldMotionVector = new THREE.Vector3()
var GMOldRotation = new THREE.Vector3()
// calculate gravity - for proper orbital mechanics - needs coordinate system translation
// due to rotating coordinate reference frame used by ISS-SIM
window.GMGravBodyCenter = new THREE.Vector3(0,-6821000 ,0) // hardcode in case earth object is not spawned yet 450 km orbit
window.GMGravBodyRadius = 6371000
window.GMGravBodyAtmosphereRadius = GMGravBodyRadius + 80000
window.GMG = 6.6743015e-11
window.GMMe = 5.9722e+24
// var GMMe = 5.9722e+28 // black hole earth to test orbital mechanics
window.GMIssOrbitAxis = new THREE.Vector3(1,0,0)
// energy is sum of gravitational portential and kinematic
function GMOrbitEnergy(v,p) {
var r = Math.abs(p.distanceTo(GMGravBodyCenter)),
eP = -GMG*GMMe / r,
eV = 0.5 * (v.length()**2)
return eP + eV
}
// function to calculate pseudo velocity coming from rotating reference frame at any point
function GMCalcPseudoVelocity(position,omega) {
var pseudoCenter = GMGravBodyCenter.clone()
// frame rotates around x axis - this makes this easy
pseudoCenter.x = position.x
var radius = Math.abs(position.distanceTo(pseudoCenter))
return pseudoCenter.clone().sub(position).normalize().cross(GMIssOrbitAxis).multiplyScalar(radius*omega)
}
// function to calculate local gravity vector
function GMCalcLocalGravityVector(position) {
var r = Math.abs(position.distanceTo(GMGravBodyCenter))
var grav = GMG*GMMe / (r**2)
var earthVector = GMGravBodyCenter.clone().sub(position).normalize()
return earthVector.clone().multiplyScalar(grav)
}
function GMprintTime(time) {
var res=""
if (time<0) {
res="-"
time=-time
}
res += Math.floor(time/60).toString().padStart(2,"0")
res += ":"
res += Math.floor(time%60).toString().padStart(2,"0")
return res
}
// this function calculates the orbital mechanics creates in a rotating reference frame
// for that we compute a single motion step at orbital velocity with earth gravity acting,
// then transform it back into the rotating ref frame
function GMOrbitalMotion() {
var mu = GMG*GMMe
var GMIssAngularVelocity = Math.sqrt(mu/GMGravBodyCenter.length())/GMGravBodyCenter.length()
// calculating a motion step in rotating reference frame is now in its own class
// - warning hack: in every time step motionVector is added to camera
// but we prefer doing that ourselves so we substract motionVector now so the renderer can re-add it
var p = camera.position.clone().sub(motionVector.clone().multiplyScalar(GMCurrentFrameTime/GMFixedStepTime))
var o = new GMOrbiter(p, motionVector.clone().multiplyScalar(1.0/GMFixedStepTime))
o.advance(GMCurrentFrameTime)
// copy new velocity into motion vector
motionVector.copy(o.v).multiplyScalar(GMFixedStepTime)
// apply new position to camera
camera.position.copy(o.p)
// calculate some orbital parameters for display
var r = Math.abs(camera.position.distanceTo(GMGravBodyCenter))
var v = o.vs.length()
var gammacos = o.vs.clone().normalize().dot(camera.position.clone().sub(GMGravBodyCenter).normalize())
var gamma = Math.acos(gammacos)
var gammasin = Math.sin(gamma)
// semi major axis and eccentricity
var a = 1.0 / ( (2/r) - ((v**2) / mu) )
var e = Math.sqrt( (((r*(v**2)/mu)-1)**2)*gammasin**2 + gammacos**2 )
// periapis and apoapsis
var peri = a*(1-e), apo = a*(1+e)
// phase angle between ISS and Dragon - as seen from earth
var v1 = camera.position.clone().sub(GMGravBodyCenter).normalize()
var v2 = new THREE.Vector3(0,0,0).sub(GMGravBodyCenter).normalize()
var phase = v1.angleTo(v2)
if (v1.clone().cross(v2).dot(GMIssOrbitAxis)<0) phase=-phase
// a2 = r2 - circular ISS orbit
var a2 = GMGravBodyCenter.length()
// v3/a3/e3 hypothetical transfer orbit from current orbit (including gamma) to ISS - solve for DeltaV
var v3 = Math.sqrt(-2.0*a2*mu*((a2 - r)/(((r**3)*(gammasin**2)) - ((a2**2)*r))))
var a3 = 1.0 / ( (2/r) - ((v3**2) / mu) )
var e3 = Math.sqrt( (((r*(v3**2)/mu)-1)**2)*gammasin**2 + gammacos**2 )
// calculate true anomaly of transfer orbit
var phi = Math.acos(((a3*(1.0-e3**2))-r)/(e3*r))
if (gammacos<0) phi=-phi // correct for descending half orbit
// calculate time spent in transfer orbit - using Mean Anomaly M via eccentric anomaly E
var n = Math.sqrt(mu/a3**3)
var E = Math.acos((e3+Math.cos(phi))/(1.0+(e3*Math.cos(phi))))
var M = E-(e3*Math.sin(E))
if (gammacos<0) M=-M // once again correct for descending half
// calculate time of arrival - depends if we make a transfer up (destination at Apogee)
// or down (ISS is at perigee) and current Mean Anomaly
var rtime
if (a2>a3) {
rtime = Math.PI - M
} else {
rtime = (2.0*Math.PI) - M
}
if (rtime<0) rtime+=2.0*Math.PI
if (rtime>2.0*Math.PI) rtime-=2.0*Math.PI
rtime=rtime/n
// calculate periods
var T = 2.0 * Math.PI * Math.sqrt( a**3 / mu )
var T2 = 2.0 * Math.PI * Math.sqrt( a2**3 / mu )
// calculate phase after transfer
var pafter
if (a2>a3) {
pafter = phase+GMIssAngularVelocity*rtime-(Math.PI-phi)
} else {
pafter = phase+GMIssAngularVelocity*rtime-((2.0*Math.PI)-phi)
}
if (pafter < -Math.PI) pafter+=2.0*Math.PI
if (pafter > Math.PI) pafter-=2.0*Math.PI
// burn T=0 is when phase-after-transfer is zero. But rate at which phase is shrinking depends
// on current orbit - this timing is inaccurate, we don't take the current orbits Mean Anomaly into account
// but its "good enough" to give the hobby astronaut a good idea when to fire thrusters
var pdelta = 2.0*Math.PI*(T2-T)/T2
var btime = -T2*(pafter/pdelta)
$("#orbit-vel").innerHTML="Vel: "+v.toFixed(1)+" m/s"
$("#orbit-alt").innerHTML="Alt: "+((r-GMGravBodyRadius)/1000).toFixed(1)+" km"
$("#orbit-apogee").innerHTML="Ap: "+((apo-GMGravBodyRadius)/1000).toFixed(1)+" km"
$("#orbit-perigee").innerHTML="Pe: "+((peri-GMGravBodyRadius)/1000).toFixed(1)+" km"
$("#orbit-period").innerHTML="Prd: "+GMprintTime(T)
$("#rendezvous-phase").innerHTML="Phs: "+(phase*180.0/Math.PI).toFixed(1)+"°"
$("#rendezvous-time").innerHTML="Ht: "+GMprintTime(rtime)+"⇒"+(pafter*180.0/Math.PI).toFixed(1)+"°"
$("#rendezvous-deltav").innerHTML="Dv: "+((v3-v).toFixed(1))+" m/s"
$("#rendezvous-deltat").innerHTML="Dt: "+GMprintTime(btime)
// rotate earth more correctly
earthMesh.rotateY(-1e-4) // undo default rotation
earthMesh.rotateY(((2*Math.PI)/(24*60*60))*GMCurrentFrameTime) // and rotate eastwards (natural earth rotation)
starsObject.rotateX(-GMIssAngularVelocity*GMCurrentFrameTime)
earthObject.rotateOnWorldAxis(GMIssOrbitAxis,-GMIssAngularVelocity*GMCurrentFrameTime) // while perceived rotation is west
lightISS_Primary.position.applyEuler(new THREE.Euler(-GMIssAngularVelocity*GMCurrentFrameTime,0,0)) // rotate sun
// follow sun with ISS with solar panels
issObject.children[3].children[0].children[0].children[6].children[8].children[6].children[6].children[3].rotateX(-GMIssAngularVelocity*GMCurrentFrameTime)
issObject.children[3].children[0].children[0].children[6].children[8].children[7].children[6].children[3].rotateX(-GMIssAngularVelocity*GMCurrentFrameTime)
}
// helper class to accurately calculate orbital motion of any object in ISS rotating reference frame
class GMOrbiter {
constructor(p,v) {
this.p = (new THREE.Vector3()).copy(p)
this.v = (new THREE.Vector3()).copy(v)
this.vs = new THREE.Vector3()
}
clone() {
var c = new GMOrbiter(this.p,this.v)
c.vs.copy(this.vs)
return c
}
advance(t) {
var mu = GMG*GMMe
// velocity of rotating reference frame
var GMIssAngularVelocity = Math.sqrt(mu/GMGravBodyCenter.length())/GMGravBodyCenter.length()
var pseudoCamMotion = GMCalcPseudoVelocity(this.p,GMIssAngularVelocity)
// calculate local gravity vector and magnitude
var gravityVector = GMCalcLocalGravityVector(this.p)
// calculate current effective velocity vector and direction
var pseudoVelocity = this.v.clone().add(pseudoCamMotion)
// calculate orbital energy
var GMEnergy = GMOrbitEnergy(pseudoVelocity, this.p)
// calculate change in position and velocity in stationary coordinate system...
var pseudoNextVel1 = pseudoVelocity.clone().add(gravityVector.clone().multiplyScalar(t))
var averageVel1 = pseudoVelocity.clone().add(pseudoNextVel1).multiplyScalar(0.5)
var pseudoNextPos1 = this.p.clone().add(averageVel1.clone().multiplyScalar(t))
// iterative, 2 step for higher accuracy (that algo has a name, I forgot that name)
var gravityVector2 = GMCalcLocalGravityVector(pseudoNextPos1)
var averageGrav = gravityVector.clone().add(gravityVector2).multiplyScalar(0.5)
var pseudoNextVel = pseudoVelocity.clone().add(averageGrav.clone().multiplyScalar(t))
var averageVel2 = pseudoVelocity.clone().add(pseudoNextVel).multiplyScalar(0.5)
var pseudoNextPos = this.p.clone().add(averageVel2.clone().multiplyScalar(t))
// convert new position back into rotating coordinate frame at time t+1
this.p.copy(pseudoNextPos.clone().sub(GMGravBodyCenter).applyAxisAngle(GMIssOrbitAxis,-GMIssAngularVelocity*t).add(GMGravBodyCenter))
// same with velocity
var newPseudoMotion = GMCalcPseudoVelocity(this.p,GMIssAngularVelocity)
var newPseudoVelocity = pseudoNextVel.clone().applyAxisAngle(GMIssOrbitAxis,-GMIssAngularVelocity*t)
// correct orbital energy to prevent drifting
var velEnergyRequired = GMEnergy - (-mu / Math.abs(this.p.distanceTo(GMGravBodyCenter)))
var velocityRequired = Math.sqrt( 2.0*velEnergyRequired )
this.vs.copy(newPseudoVelocity.clone().normalize().multiplyScalar(velocityRequired))
this.v.copy(this.vs.clone().sub(newPseudoMotion))
}
}
window.GMTrajectoryStep=20.0
// Show expected trajectory
function GMShowTrajectory() {
var o = new GMOrbiter(camera.position, motionVector.clone().multiplyScalar(1.0/GMFixedStepTime))
var timestep = GMTrajectoryStep / (motionVector.length()/GMFixedStepTime) // calculate length of time iteration
var iters=1
if (!(timestep<1.0)) timestep=1.0; // prevent div by zero or extreme accuracy degradation
var ot=o.clone()
ot.advance(timestep)
var l=ot.clone().p.sub(o.p).length()
iters=Math.floor((GMTrajectoryStep/l)+0.5)
if (!(iters<10000)) { // prevent div by zero
GMTrajectoryObject.visible = false
return
}
if (!(iters<100)) iters=100; // prevent processing time explosions
for (var t=0; t<GMTrajectoryLength; t++) {
o.advance(timestep)
for (var t2=1;t2<iters;t2++) o.advance(timestep)
GMTrajectoryObject.children[t].position.copy(o.p)
}
}
// override some functions from ISS SIM with modified versions to implement this functionality
// start with some helper functions
function GMAddReason(a,b) {
return a != "" ? a + ", " + b : b
}
window.GMTimeLapse = 1
window.GMBurn=0
window.GMBurnTimer=0
checkCollision = function() {
// fix time stepping - do not allow big time jumps to ensure smoothness
var GMnewTime=Date.now()
if ( GMoldTime < GMnewTime - 100 ) {
GMoldTime = GMnewTime - 100
}
GMCurrentFrameTime = 0.001 * ( GMnewTime - GMoldTime )
GMoldTime=GMnewTime
GMfixMotion()
GMTrajectoryObject.visible = false
if (!0 !== isGameOver) {
// Main Burn incl. timer
if (GMBurn == 1){
GMBurnTimer+=GMCurrentFrameTime
$("#translate-backward-button").click()
}
$("#burn-onoff").innerHTML = (GMBurn == 1 ? '<span style="color:red">ON&nbsp;' + GMprintTime(GMBurnTimer)+"."+(GMBurnTimer%1.0).toFixed(2).toString().substring(2) + '</span>' : "OFF")
// timing
GMMissionTimer+=GMCurrentFrameTime
GMGameTimer+=GMCurrentFrameTime
$("#stats-mission").innerHTML="Mis: "+GMprintTime(GMMissionTimer)+"."+(GMMissionTimer%1.0).toFixed(2).toString().substring(2)
$("#stats-real").innerHTML="Sim: "+GMprintTime(GMGameTimer)+"."+(GMGameTimer%1.0).toFixed(2).toString().substring(2)
$("#stats-fps").innerHTML=(1.0/GMCurrentFrameTime).toFixed(1)+" fps"
// DeltaV update:
GMDeltaVUsed+=motionVector.clone().sub(GMOldMotionVector).length()/GMFixedStepTime
var rotationDeltaV=Math.abs(GMOldRotation.x-currentRotationX) + Math.abs(GMOldRotation.y-currentRotationY) + Math.abs(GMOldRotation.z-currentRotationZ)
GMDeltaVUsed+=0.02*rotationDeltaV*180/(Math.PI*moveSpeed)
$("#prop-used").innerHTML="Used: "+(GMDeltaVUsed*1.0).toFixed(1)+" m/s"
$("#prop-left").innerHTML="Budg: "+(GMDeltaVBudget-GMDeltaVUsed).toFixed(1)+" m/s"
$("#propellant").style.display="block"
// calculate gravity if applicable
if (GMisOrbitalMechanics==2) {
GMOrbitalMotion()
}
// time lapse
var realFrameTime = GMCurrentFrameTime
GMCurrentFrameTime = GMFixedStepTime // force k factor 1.0 for fast forward frames
var ti
for (ti=1; ti < GMTimeLapse; ti++) {
camera.position.add(motionVector)
!isGameOver && isGravity && (camera.position.y = camera.position.y - (gravity))
camera.rotateX(currentRotationX)
camera.rotateY(currentRotationY)
camera.rotateZ(currentRotationZ)
earthMesh.rotateY(1e-4)
GMMissionTimer+=GMCurrentFrameTime
// calculate gravity if applicable
if (GMisOrbitalMechanics==2) {
GMOrbitalMotion()
}
}
GMCurrentFrameTime = realFrameTime
// save motion vectors for deltaV update
GMOldMotionVector.copy(motionVector)
GMOldRotation.set(currentRotationX,currentRotationY,currentRotationZ)
if (GMisOrbitalMechanics==2 && GMisTrajectoryVisible) {
GMTrajectoryObject.visible = true
GMShowTrajectory()
}
var GMIssTarget = issObject.position.clone()
GMIssTarget.add(GMIssApproach.position)
GMCameraRotationHelper.quaternion.copy(camera.quaternion)
GMCameraRotationHelper.quaternion.multiply(GMIssApproach.quaternion.clone().inverse())
var e = hitRaycaster.intersectObjects(hitArray, !0),
t = Math.abs(camera.position.distanceTo(issObject.position)),
t2 = Math.abs(camera.position.distanceTo(GMIssTarget)),
a = Math.abs(GMCameraRotationHelper.rotation.x / toRAD),
r = Math.abs(GMCameraRotationHelper.rotation.y / toRAD),
o = Math.abs(GMCameraRotationHelper.rotation.z / toRAD),
i = Math.abs(motionVector.x),
n = Math.abs(motionVector.y),
s = Math.abs(motionVector.z),
ar = Math.sqrt(a*a+r*r),
rxy = Math.sqrt(currentRotationX*currentRotationX + currentRotationY*currentRotationY) / toRAD,
rz = Math.abs(currentRotationZ) / toRAD
if (hitDistance = t > .5 ? 1 : .1, hitRaycaster.far = hitDistance, updateRateColor(i >= .02 || n >= .02 || s >= .02 ? "warning" : i > toleranceRate || n > toleranceRate || s > toleranceRate ? "caution" : "normal"), GMisCollisions && e.length > 0 && ($("#fail-message").innerHTML = "You made contact with the International Space Station.", hideInterface("fail")), GMisCollisions && Math.abs(camera.position.distanceTo(GMGravBodyCenter)) < GMGravBodyAtmosphereRadius && ($("#fail-message").innerHTML = "You re-entered earth atmosphere.", hideInterface("fail")), GMisSpaceRestricted && t > 500 && ($("#fail-message").innerHTML = "You are too far away from the International Space Station.", hideInterface("fail")), GMisFuelLimit && GMDeltaVUsed > GMDeltaVBudget && ($("#fail-message").innerHTML = "You used too much propellant.", hideInterface("fail")), t < .2 && t2 < t) {
var GMInLimits = true, l = ""
if (GMisLimitsOriginal) {
if (!(a <= toleranceRotation && r <= toleranceRotation && o <= toleranceRotation)) {
l=GMAddReason(l,"ROTATION ANGLE")
GMInLimits = false
}
if (!(i <= toleranceRate && n <= toleranceRate && s <= toleranceRate)) {
l=GMAddReason(l,"SPEED")
GMInLimits = false
}
} else {
// correct speeds for m/s instead of m/frame
i=i/GMFixedStepTime
n=n/GMFixedStepTime
s=s/GMFixedStepTime
rxy=rxy/GMFixedStepTime
rz=rz/GMFixedStepTime
var dx = Math.abs(camera.position.x-issObject.position.x),
dy = Math.abs(camera.position.y-issObject.position.y),
dz = Math.abs(camera.position.z-issObject.position.z)
if (dz>=0.1) {
l=GMAddReason(l,"OFFSET-X > 0.1")
GMInLimits = false
}
if (dx>=0.1) {
l=GMAddReason(l,"OFFSET-Y > 0.1")
GMInLimits = false
}
if (dy>=0.1) {
l=GMAddReason(l,"OFFSET-Z > 0.1")
GMInLimits = false
}
if (s>=0.1) {
l=GMAddReason(l,"VELOCITY-X > 0.1")
GMInLimits = false
}
if (i>=0.1) {
l=GMAddReason(l,"VELOCITY-Y > 0.1")
GMInLimits = false
}
if (n>=0.1) {
l=GMAddReason(l,"VELOCITY-Z > 0.1")
GMInLimits = false
}
if (ar>=4) {
l=GMAddReason(l,"ANGLE (PITCH/YAW) > 4")
GMInLimits = false
}
if (o>=4) {
l=GMAddReason(l,"ROLL ANGLE > 4")
GMInLimits = false
}
if (rxy>=0.2) {
l=GMAddReason(l,"ROTATION (PITCH/YAW) > 0.2")
GMInLimits = false
}
if (rz>=0.2) {
l=GMAddReason(l,"ROLL RATE > 0.2")
GMInLimits = false
}
}
if (GMInLimits) {
hideInterface("success")
} else {
$("#fail-message").innerHTML = "The following errors occurred: <span class='red'>" + l + "<span>"
hideInterface("fail")
}
}
}
}
renderTesla = function() {
(!GMisTeslaHidden || camera.rotation.y > 2.5 || camera.rotation.y < -2.5 ? (isTeslaCreated || createTesla(), isTeslaLoaded && (teslaMesh.visible = !0)) : isTeslaLoaded && (teslaMesh.visible = !1))
}
// The NAVBall became a bit more complicated, since we would like it to be in Earth frame
// but we are in a rotating reference frame in an inclined earth orbit
// Helper function to calculate rotation between 2 orthogonal vector pairs
function GMRotateVectorPairs(u0, v0, u2, v2) {
var q2 = new THREE.Quaternion().setFromUnitVectors(u0, u2)
var v1 = v2.clone().applyQuaternion(q2.clone().conjugate())
var v0_proj = v0.projectOnPlane(u0)
var v1_proj = v1.projectOnPlane(u0)
var angleInPlane = v0_proj.angleTo(v1_proj)
if (v1_proj.dot(new THREE.Vector3().crossVectors(u0, v0)) < 0) {
angleInPlane *= -1
}
var q1 = new THREE.Quaternion().setFromAxisAngle(u0, angleInPlane)
var q = new THREE.Quaternion().multiplyQuaternions(q2, q1)
return q
}
// override navball rendering to show earth/orbit instead of ISS relative
renderNavball = function() {
if (navballCreated) {
var rotAxis=new THREE.Vector3(0,1,0)
var pAxis=new THREE.Vector3(0,0,1)
var camVector = camera.position.clone().sub(GMGravBodyCenter).normalize()
var earthRotAxis = rotAxis.clone().applyQuaternion(earthObject.quaternion).normalize()
var vectorToEast = camVector.clone().cross(earthRotAxis).normalize()
var q = GMRotateVectorPairs(rotAxis,pAxis,camVector,vectorToEast)
var e = camera.quaternion.clone().conjugate().multiply(q)
navballObject.setRotationFromQuaternion(e)
}
}
renderOrb = function() {
if (isOrbCreated) {
orbYawObject.rotation.y = GMisHUDVisible ? -camera.rotation.y : 0.0, orbPitchObject.rotation.x = GMisHUDVisible ? -camera.rotation.x : 0.0, orbRollObject.rotation.z = GMisHUDVisible ? -camera.rotation.z : 0.0
var e = orbFadeTarget.getWorldPosition(new THREE.Vector3), i
for (i = 0; i < orbSpriteArray.length; i++) {
var t = orbSpriteArray[i].getWorldPosition(new THREE.Vector3),
a = Math.abs(t.distanceTo(e))
orbSpriteArray[i].material.uniforms.u_opacity.value = GMisHUDVisible ? opacityDistanceClamp(a, 5, 2, 1) : 0.0
}
}
}
// this is just a bugfix for rough versus fine rotation control
updatePrecision = function(e) {
"rotation" === e && (rotationPulseSize = .5 === rotationPulseSize ? (rateSpeedSize=2,1) : (rateSpeedSize=1,.5), $("#rotation-controls").classList.toggle("large"), $("#precision-rotation-status").classList.toggle("large"), $("#hud-tips").classList.toggle("rotation-large")), "translation" === e && (translationPulseSize = .001 === translationPulseSize ? .005 : .001, $("#translation-controls").classList.toggle("large"), $("#precision-translation-status").classList.toggle("large"), $("#hud-tips").classList.toggle("translation-large"))
}
// this is overridden to reset some variables each new game/attempt
GMGameStart = function() {
GMDeltaVUsed=0
GMOldMotionVector.copy(motionVector)
GMOldRotation.set(currentRotationX, currentRotationY, currentRotationZ)
GMMissionTimer=0
GMBurnTimer=0
GMBurn=0
GMTimeLapse=1
GMGameTimer=0
isGameOver=!1
}
$("#fail-button").removeEventListener("click", showInterface, !1)
$("#fail-button").removeEventListener("touchstart", showInterface, !1)
$("#success-button").removeEventListener("click", showInterface, !1)
$("#success-button").removeEventListener("touchstart", showInterface, !1)
// the annoying thing is the need to duplicate the entire function
// for the change of a single onComplete property
window.GMshowInterface = function() {
showInterface()
setTimeout(GMGameStart,5000)
}
$("#fail-button").addEventListener("click", GMshowInterface, !1)
$("#fail-button").addEventListener("touchstart", GMshowInterface, !1)
$("#success-button").addEventListener("click", GMshowInterface, !1)
$("#success-button").addEventListener("touchstart", GMshowInterface, !1)
// extra keys - z/y for toggle translation precision - x for toggle rotation precision, c,v,b for time lapse
document.addEventListener("keyup", function(e) {
if (!isGameOver) switch (e.keyCode || e.which) {
case 89:
case 90:
toggleTranslation()
break;
case 88:
toggleRotation()
break;
case 67:
GMTimeLapse=1
break;
case 77: //M key - main burn & timer
GMBurn == 1 ? GMBurn=0 : GMBurn=1
if (GMBurn == 0) GMBurnTimer=0
break;
case 86:
GMTimeLapse>=10 ? GMTimeLapse/=10 : GMTimeLapse=1
break;
case 66:
GMTimeLapse<=100 ? GMTimeLapse*=10 : GMTimeLapse=1000
break;
}
})
// make 30 spheres for trajectory visualization
window.GMTrajectoryObject = new THREE.Object3D()
var GMTrajectoryLength = 30
for (var t=0; t<GMTrajectoryLength; t++) {
var scale=0.25
if (((t+1)%10)==0) {
scale=0.75
}
GMTrajectoryObject.add(new THREE.Mesh(new THREE.SphereGeometry(scale,6,6), new THREE.MeshBasicMaterial({
transparent: true,
opacity: 0.75,
color: 2413309,
})))
}
GMTrajectoryObject.visible=false
scene.add(GMTrajectoryObject)
$("#intro-step1 .animate").innerHTML="<span class=\"red\">ISS-SIM-Customizer</span> user script v 0.000016 by <span class=\"green\">CorvusCorax</span> loaded succesfully - check settings page for details.<br/>Use [x] and [y]/[z] keys to toggle precision thrust for translation/rotation.<br/>Use [b],[v] for time warp +-, [c] resets time warp.<br/>"+$("#intro-step1 .animate").innerHTML
}
// delayed execution after SpaceX sim loads
var GMscript = document.createElement('script')
GMscript.type = "text/javascript"
GMscript.innerHTML = GMInitializer.toString() + "\n" + "window.addEventListener('load', GMInitializer)" + "\n"
document.getElementsByTagName('head')[0].appendChild(GMscript)
//end
@CorvusCorax
Copy link
Author

CorvusCorax commented May 14, 2020

thumbnail

newest_screenshot

intro

Usage:

Install in Firefox via Greasemonkey, or in Chrome via Tampermonkey or directly by drag+dropping the file into chrome://extensions (dev mode)

Configure in the in-game settings (see screenshot)
config

New Key combinations:
y/z - toggle precision mode for translation
x - toggle precision more for rotation
c - reset time-warp
v - decrease time-warp
b - increase time-warp
(useful with GRAVITY set to "PROPER ORBITAL MECHANICS")

Docking to Pirs:
docktoPirs

HUD disabled - docking by sight only:
no_hud

Orbital mechanics:

orbital_mechanics2
reentered

Rendezvous+Docking challenge:

This new feature in v 0.000009 places you in a neat Dragon deployment orbit at 190km by 205 km altitude with approximally 127° phase angle to ISS - corresponding to a DM-2 launch on May 27 2020 - as of v 0.000012 and some minor inclination error. Use the displayed ORBIT and RNDVZ planning data to get to the ISS before you dock
rndvz_planner1

Displayed items:

ORBIT

  • Vel: Current orbital velocity
  • Alt: Altitude above sea level
  • Ap: Apogee - Altitude of the highest point in the orbit.
  • Pe: Perigee - Altitude of the lowest point in the orbit
  • Prd: Orbital period - How long it takes to go once around the earth

RNDVZ:

  • Phs: Current phase angle between Dragon and the ISS.
  • Ht: Hohmann Transfer - time required for a a Hohmann transfer half orbit from the current orbit ⇒ resulting phase angle if a transfer burn were to be initiated right now
  • Dv: DeltaV for the burn into such a transfer orbit.
  • Dt: Time until burn - (displayed time might progress faster or slower than real time if current orbit is eccentric or vehicle is under thrust, but T=0 is always trustworthy)

Usage

  1. The main HUD attitude indicator is relative to ISS, but retrograde. You should first orient dragon at 0° Roll, 0° Pitch and 180° Yaw.
  2. Check the current phase angle and the expected phase angle after the Hohmann transfer. The difference is the phase angle at which the transfer should be initiated. Pitch up by said angle, this will orient Dragon pro-grade in the current orbit at that time. (Since main attitude display is relative to ISS. Note: The small nav ball shows attitude relative to earth north pole, but not relative to current orbit. This is different from KSP!
  3. When the Dt timer approaches zero, burn a lot of thruster pulses pro-grade, until the Dv required counter reaches zero. Counter slows down if you burn before T=0 but speeds up after T=0 so you should do the majority of the burn before T=0. You can alter the trajectory up or down, which increases or decreases the clock to make sure both Dv and Dt are 0 after the burn.
  4. Feel free to use time-warp to reach this point faster.
  5. In flight, remain oriented prograde. When the displayed offset from ISS in Y axis approaches zero, use lateral (sideways) thrusters to arrest relative Y -velocity. This fine-aligns your inclination with ISS.
  6. When you approach ISS, you want to look for it. Initially its in front of you, then above you, then above and behind. Just before you initiate the final burn ISS will again overtake you. Make sure you have sufficient space but not too much.
  7. Make another prograde burn when you get close to the ISS. (Ht time counter gets low) WARNING: The rendezvous planner does not indicate the burn time or needed DeltaV to circularize orbit after transfer in Dt or Dv. If you keep Dt: and Dv: zeroed without slowing down, you will crash right into the ISS at 70 meters per second when Ht: reaches zero.
  8. Then dock.

The same approach works for retrograde burns and negative phase angles. As such you can make an approach with multiple intermediate orbits to keep deltaV for the final burns low. (Or your fingers will hurt from hammering the thrusters ;) )

Video (HUD-less docking):
Video

Video (Trajectory display in Proper Orbital Mechanics mode):
Video

@CorvusCorax
Copy link
Author

CorvusCorax commented May 25, 2020

Change Log:

  • v 0.000001 2020-05-13 Initial Customize ISS-SIM Script
    • Toggle permanent Tesla visibility
    • Toggle 500m distance restriction
    • Toggle HUD Online/Offline
    • Toggle Docking constraints - original versus IDA specs
    • Toggle precision rotation/translation per via keyboard
  • v 0.000002 2020-05-14 Bugfix
    • Bugfix to work with different browsers / extension loaders (Greasemonkey, Chrome native, Tampermonkey, Violent Monkey, ...)
  • v 0.000003 2020-05-14 Docking Ports
    • Support for all the ISS docking ports
    • Improved greeter and intro
  • v 0.000004 2020-05-16 Proper Orbital Mechanics
    • Support for time warp
    • Support for realistic orbital mechanics instead of simplified "gravity" - allows altering orbit around earth arbitrarily
  • v 0.000005 2020-05-17 Bugfix
    • Fixed bug in Tampermonkey/Chrome where some menu entries weren't functional
  • v 0.000006 2020-05-18 Fix the Earth
    • In Proper Orbital Mechanics, ISS is now in correct 51° inclined prograde Orbit, earth rotates correctly around itself
    • Navigation ball displays orientation relative to earth.
  • v 0.000007 2020-05-19 Navball Bugfix
    • Corrected an error in the nav ball orientation
  • v 0.000008 2020-05-19 Follow the sun
    • In Proper Orbital Mechanics mode, the sun no longer orbits with the ISS but stays stationary relative to earth. ISS enters earth shadow
    • ISS Solar panels track and follow the sun
    • ISS radiators move out of the way, when the obstructed PMA-3 docking port is selected
  • v 0.000009 2020-05-20 A New Challenge
    • Orbital rendezvous calculator display added to Proper Orbital Mechanics mode
    • Added new Objective/Spawn position to allow player to begin in deployment LEO and sync to the ISS themselves
    • Bugfix of rotation-precision-mode bug in the original ISS-SIM
  • v 0.000010 2020-05-21 DM-2 Dragon Deploy
    • Dragon now deployed in eccentric 190km x 205km orbit - matching DM-2 deployment orbit
  • v 0.000011 2020-05-23 Eccentric Rendezvous
    • Updated rendezvous calculator to work and predict correctly when Dragon is initially in an arbitrary eccentric orbit (aka any orbit)
  • v 0.000012 2020-05-23 Un-Phased
    • Updated initial orbit of the rendezvous challenge to be phased 1/3 orbit behind ISS - as it would be on May 27 planned DM-2 launch
  • v 0.000013 2020-05-24 Sparse resources
    • Keep track and display used DeltaV and DeltaV budget
    • Added limited propellant to the optional docking constraints. Exceeding the DeltaV budget for the mission phase means GAME OVER.
  • v 0.000014 2020-05-25 It's Time
    • Added mission- and game- elapsed time counter to allow comparison of speed-run challenges
    • Added fps display
    • Fixed velocity and rotational velocity calculation to be correct and independent of system fps
  • v 0.000015 2020-05-26 Trajectory in sight
    • Optional trajectory preview in HUD - only in combo with proper orbital mechanics - displays little blue balls along the expected free drift trajectory

@LeighDarby
Copy link

LeighDarby commented Apr 30, 2021

Nice script - current ISS Dragon Crew-1/2 changeover reminded me ;-)

To avoid finger nail damage I've added a constant Main Burn using M key to toggle on / off - it simply sends click event on each main loop cycle to the translate forward button - also displays the burn time ...

image

image

image

image
...
...
...
image

@CorvusCorax
Copy link
Author

Nice script - current ISS Dragon Crew-1/2 changeover reminded me ;-)

To avoid finger nail damage I've added a constant Main Burn using M key to toggle on / off - it simply sends click event on each main loop cycle to the translate forward button ...

Awesome contribution! I should probably make a new version and add that. That should make TLI burns so much easier :)

@LeighDarby
Copy link

Thanks - can't stop fiddling now - added a burn timer as there was enough space to put it in ....!
Have updated code fragments above.

@CorvusCorax
Copy link
Author

You could fork the gist on your own github, then I can pull the fragments :) It's basically a repository. where each comment or edit is a commit. or we turn it into a proper source code repository for development, what do you think?

@LeighDarby
Copy link

I created a fork and added the code fragments :-
https://gist.github.com/LeighDarby/2bb251105119ff27cdff80a9312cf96a

Was considering a couple of (possibly mad) ideas ...

Add an external camera view of Dragon (track relative and/or fixed point to view docking etc.) - there are quite a few 3D (.glb) models available but the ones with docking nose cone open all require $$ I think ...

Found a decent .glb of Hubble, could put that into orbit and have a different rendezvous option?
Wikipedia seems to have some orbital data but perhaps NASA has better not had a chance to look yet ...

@CorvusCorax
Copy link
Author

CorvusCorax commented May 18, 2021 via email

@CorvusCorax
Copy link
Author

CorvusCorax commented Sep 30, 2021

I created a fork and added the code fragments :- https://gist.github.com/LeighDarby/2bb251105119ff27cdff80a9312cf96a

I pulled your changes and integrated this in the main code. Since Dragon's main thrusters are pointed forward - located under the nose cone - I changed the main thrusters to firing backwards instead of forward. I also added code to reset burn timer and thruster status whenever the simulation is reset to not "spawn" with firing main engine ;)

After playing a bit, I think that the thrust is probably way too much. Dragon has 4 forward draco thrusters with 400 N each. Assuming a mass of aprox 10 tons, that would lead to an acceleration of 1600N / 10000kg = 0.16 m/s² when firing continuously.
When hammering the controls, a user can easily achieve 10 times as much and your "main engine" script in course mode achieves close to 1 g - 100 times the thrust dragon would actually have.

but it is of course quite convenient ;) I think I leave it in ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment