So in dragon.clj there's a case statement to execute commands in the translate function such that the command to go down or left in an abstract calculated sense is implemented as hitting a virtual down or left keyboard key.
What happens if you have to go down AND left?
I was just thinking why not pass a Clojure list to the translate function and when list contains a down it hits the virtual s key AND if the list contains a left command it hits a virtual a key, then you could wiggle "down-left" at the same time.
I mean, obviously it works and sometimes architectural decisions have no technical reason and are arbitrary, which would be perfectly OK. I was just idly curious.
Clojure is cool, like programmer catnip, so I had to look at it.
I would like to see a real autopilot written in javascript that you can paste into the console.
[0]: https://gist.github.com/ForestKatsch/01069f29e8316df11774025...
let a = setInterval(() => {
// retrieve values from simulation
let roll = fixedRotationZ;
let pitch = fixedRotationX;
let yaw = fixedRotationY;
let rpy = new THREE.Vector3(roll, pitch, yaw);
let rollRate = -rateRotationZ/10.0;
let pitchRate = -rateRotationX/10.0;
let yawRate = -rateRotationY/10.0;
let rpyRate = new THREE.Vector3(rollRate, pitchRate, yawRate);
let pos = camera.position.clone();
let posRate = motionVector.clone();
let targetPos = issObject.position.clone();
let targetRpy = new THREE.Vector3(0,0,0);
let dRpy = targetRpy.clone().sub(rpy);
// P-controller controls target roll,pitch,yaw-rate (targetRpyRate) to reach target orientation
let targetRpyRate = new THREE.Vector3(
dRpy.x/1.0,
dRpy.y/1.0,
dRpy.z/1.0
);
let minRpyRate = new THREE.Vector3(-1.5, -1.5, -1.5);
let maxRpyRate = new THREE.Vector3(+1.5, +1.5, +1.5);
targetRpyRate.clamp(minRpyRate, maxRpyRate)
let dRpyRate = targetRpyRate.clone().sub(rpyRate);
// don't calculate with displayed x,y,z but the internal coordinates
// (z-axis is forward and backward)
let dPos = targetPos.clone().sub(pos);
let d = dPos.length();
// P-controller controls target motion (targetPosRate) to reach target position
let targetPosRate = new THREE.Vector3(
dPos.x / 100.0,
dPos.y / 100.0,
dPos.z / 100.0
);
let minPosRate = new THREE.Vector3(-0.1, -0.1, -0.01);
let maxPosRate = new THREE.Vector3(+0.1, +0.1, +0.01);
// thresholds for bang contRol
let dRpyRateControlThreshold = new THREE.Vector3(0.0, 0.0, 0.0);
let dPosRateControlThreshold = new THREE.Vector3(0.001, 0.001, 0.001);
// define some phases where we want to have different movement behaviour
if (dRpyRate.length() > 0.1)
{
// not correctly oriented, stop movement
targetPosRate.x = 0;
targetPosRate.y = 0;
targetPosRate.z = 0;
}
else if (Math.abs(dPos.z) < 20)
{
// we are very close, slow approach, larger P-control in lateral
// directions to correct remaining errors
minPosRate.z = -0.005;
maxPosRate.z = +0.005;
dPosRateControlThreshold.x = 0.01;
dPosRateControlThreshold.y = 0.01;
targetPosRate.x = dPos.x;
targetPosRate.y = dPos.y;
}
else if (Math.abs(dPos.z) < 50)
{
// we are getting, close, slow down
minPosRate.z = -0.01;
maxPosRate.z = +0.01;
minPosRate.x = -0.01;
minPosRate.y = -0.01;
maxPosRate.x = +0.01;
maxPosRate.y = +0.01;
}
else if (Math.abs(dPos.z) < 100)
{
// slow down
minPosRate.z = -0.1;
maxPosRate.z = +0.1;
}
else if ((Math.abs(dPos.x) < 0.05) && (Math.abs(dPos.y) < 0.05))
{
// lateral position correct, use maximum approaching speed
minPosRate.z = -0.2;
maxPosRate.z = +0.2;
}
targetPosRate.clamp(minPosRate, maxPosRate);
let dPosRate = targetPosRate.clone().sub(posRate);
// bang control to reach target roll pitch and yaw rate
if (dRpyRate.x < -dRpyRateControlThreshold.x)
rollRight();
else if (dRpyRate.x > +dRpyRateControlThreshold.x)
rollLeft();
if (dRpyRate.y < -dRpyRateControlThreshold.y)
pitchDown();
else if (dRpyRate.y > +dRpyRateControlThreshold.y)
pitchUp();
if (dRpyRate.z < -dRpyRateControlThreshold.z)
yawRight();
else if (dRpyRate.z > +dRpyRateControlThreshold.z)
yawLeft();
// bang control to reach target motion
if (dPosRate.x < -dPosRateControlThreshold.x)
translateLeft();
else if (dPosRate.x > +dPosRateControlThreshold.x)
translateRight();
if (dPosRate.y < -dPosRateControlThreshold.y)
translateDown();
else if (dPosRate.y > +dPosRateControlThreshold.y)
translateUp();
if (dPosRate.z < -dPosRateControlThreshold.z)
translateForward();
else if (dPosRate.z > +dPosRateControlThreshold.z)
translateBackward();
},100);Source code:
https://gist.github.com/ggerganov/092b86a59fa34926998953701a...
Perhaps better would be to make pure functions to take current telemetry as an argument and return actions as outputs, perhaps all at once or more likely individually for each control. You could then capture all of this inside core.async go-loops to manage the control flow and timeouts, but you could also probably build something simple with one or more agents wrapping the current state and updating a set of commands to be executed later. Another argument for core.async would be to make it easier to funnel all commands into a single channel because I’m not entirely convinced a WebDriver is multithreaded, but perhaps the library manages that for you.
Anyway, cool stuff!
That makes it sound you approach this from a haughty presumptuous, uncharitable angle.
It sounds like you exclude the possibility that the author did not try it "your way" first, for example, but found a simpler way to implement it.
"That's not the most obvious to do it IMO" or "I would have preferred to do it like this..." are much better ways to approach this. Programs should be beautiful and understandable, but "idiomatic" sounds it excludes lots of simple beautiful solutions to a problem out of pure dogma.
Nowadays I write web applications like everyone else on the planet and use Java, JS and/or Clojure (when I can get away with it).
But cool little project, nonetheless.
It crashed for me right as it docked with:
Syntax error (ExceptionInfo) compiling at (/private/var/folders/8f/wlmmp1rs38966nkmrs_slnjd1vkfkk/T/form-
init8037093313867620508.clj:1:125).
throw+: {:type :etaoin/timeout, :message "Wait for {:css \"#success > h2\"} element is visible", :timeout 300, :interval 0.33, :times 910, :predicate #object[etaoin.api$wait_visible$fn__4868 0x173b24c4 "etaoin.api$wait_visible$fn__4868@173b24c4"]}https://news.ycombinator.com/item?id=23172281
... and pull the appropriate Interstellar music up in a separate tab.
KSP was also 100% the reason I managed dock with the simulator on the first try, because in a way it was more like the 50th-100th try, just using a different interface.