-
-
Save CorvusCorax/d6e6e98c946b8bfad56563b514df23a9 to your computer and use it in GitHub Desktop.
| // ==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 |
CorvusCorax
commented
May 18, 2021
via email
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 ;)