// ==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 ' + 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 |
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
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 :)
Thanks - can't stop fiddling now - added a burn timer as there was enough space to put it in ....!
Have updated code fragments above.
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?
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 ...
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 ;)
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)

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:

HUD disabled - docking by sight only:

Orbital mechanics:
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

Displayed items:
ORBIT
RNDVZ:
Usage
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