var zoom;
var state;
var frameCount;
var clockContext;
var clockImageData;
var clockWidth, clockHeight;
var renderWidth, renderHeight;
var frameCount = 0;
var rule; // an array of states to transition to, ordered
var moon;
var runJupiterClock = [false, false, false, false, false];
// moon rendering info
var timeScale = 1.0;
var moonRenderZoom = 1.0;
var moonMassRenderScale = 1.0;
function Moon() {
this.name = "moon";
this.radius = 10;
this.period = Math.pow(2, -11);
this.mass = 100;
this.x = 0;
this.y = 0;
this.updatePosition = updatePosition;
}
function suspend() {
pause = true;
update();
}
function resume() {
pause = false;
update();
}
function updatePosition(time) {
var angle = timeScale * (time / this.period) * Math.PI * 2;
this.x = this.radius * Math.cos(angle);
this.y = this.radius * Math.sin(angle);
}
function setupJupiterClock(targetDiv) {
// get which div number we're in
var id = targetDiv.id;
var divIndex = parseInt(id.charAt(id.length - 1));
runJupiterClock[divIndex] = true;
// file our cleanup function
divCleanupFunction[divIndex] = function() {
console.log("cleanup function run");
console.log("erasing contents of parent div: " + targetDiv.id);
runJupiterClock[divIndex] = false;
};
var paddingDiv = document.createElement("div");
paddingDiv.className = "subpanel_padding";
targetDiv.appendChild(paddingDiv);
var timeOutput2 = document.createElement("p");
timeOutput2.id = "timeOutput2";
timeOutput2.style.margin = "0px";
paddingDiv.appendChild(timeOutput2);
a
var clockCanvas = document.createElement("canvas");
zoom = 2;
clockCanvas.id = "clock canvas";
clockCanvas.width = 144 * zoom;
clockCanvas.height = 64 * zoom;
paddingDiv.appendChild(clockCanvas);
var newline = document.createElement("br");
paddingDiv.appendChild(newline);
var ruleCanvas = document.createElement("canvas");
ruleCanvas.id = "ruleIndex";
ruleCanvas.width = 144 * zoom;
ruleCanvas.height = 4;
paddingDiv.appendChild(ruleCanvas);
var ruleField = document.createElement("p");
ruleField.id = "ruleField";
paddingDiv.appendChild(ruleField);
var ruleOutput = document.createElement("p");
ruleOutput.id = "ruleOutput";
paddingDiv.appendChild(ruleOutput);
initializeJupiterClock("clock canvas");
updateJupiterClock(divIndex); // update calls itself; this starts the chain reaction
// we can make it so update stops calling itself by checking some condition or other
}
// function for stopping clock
//var stopClock(targetDiv) { // pass in div?
function stopClock() {
runJupiterClock[0] = false;
}
// the update function
function updateJupiterClock(divIndex) {
var date = new Date();
var time = date.getTime();
updateMoons(time); // use actual time
drawResonanceReadout(time);
drawLine();
frameCount ++;
clearClockImage();
drawStatesToImageData();
redrawJupiterClock();
requestAnimationFrame(function() {
if (runJupiterClock[divIndex])
updateJupiterClock(divIndex);
});
}
function updateMoons(time) {
//var outputString = "time: " + time;
//document.getElementById("timeOutput").innerHTML = outputString;
var date = new Date();
var minuteString = date.getMinutes();
if (date.getMinutes() < 10)
minuteString = "0" + minuteString;
var secondString = date.getSeconds();
if (date.getSeconds() < 10)
secondString = "0" + secondString;
var outputString2 = "on the side table is an orange device with a slanted LCD screen. \nthe clock says " + date.getHours() + ":" + minuteString + ":" + secondString + "
";
document.getElementById("timeOutput2").innerHTML = outputString2;
for (var i in moon) {
var currentMoon = moon[i];
currentMoon.updatePosition(time);
}
//ADD: draw moons to cell grid? or as characters or something
//document.getElementById("moonOutput").innerHTML = "";
//clearStates();
for (var i in moon) {
drawMoon(moon[i]);
}
// draw jupiter
var angle;
var radius = Math.random() * 69991 * moonRenderZoom / 4;
for (var i = 0; i < 2; i ++) {
angle = Math.random() * 2 * Math.PI;
addToState(512 / 4 / 2 + radius * Math.cos(angle), clockHeight / 2 + radius * Math.sin(angle), Math.random() * 32);
}
}
function drawResonanceReadout(time) {
for (var i = 0; i < clockHeight; i ++) {
state[128][i] = 128;
}
// sine waves
for (var i = 0; i < moon.length - 1; i ++) {
drawMoonResonance(moon[i], time);
}
}
function drawMoon(moon) {
var output = moon.name;
//for (var i = 0; i < 100; i ++)
var centerX = 128 / 2;
addToState(
centerX + (moonRenderZoom * moon.x + random(-0.5, 0.5)) / 4,
clockHeight / 2 + 0.5 * moonRenderZoom * moon.y / 4 + random(-0.5, 0.5), Math.random() * moon.mass * moonMassRenderScale);
//document.getElementById("moonOutput").innerHTML += output + " x = " + moon.x + ", y = " + moon.y + "
";
}
function drawMoonResonance(moon, time) {
var sineWaveXOffset = clockWidth - 7.5;
var sineWaveAmplitude = 24;
var drawHeight = 64;
var drawY = clockHeight / 2 + Math.floor(time / 64) % drawHeight - drawHeight / 2;
//console.log(time);
addToState(
sineWaveXOffset + sineWaveAmplitude * moon.x / moon.radius / 4 + random(-0.5, 0.5),
drawY,
Math.random() * moon.mass * moonMassRenderScale);
}
function setState(x, y, value) {
if ((x >= 0)
&& (x < clockWidth)
&& (y >= 0)
&& (y < clockHeight))
state[Math.floor(x)][Math.floor(y)] = value;
}
function addToState(x, y, value) {
if ((x >= 0)
&& (x < clockWidth)
&& (y >= 0)
&& (y < clockHeight))
state[Math.floor(x)][Math.floor(y)] += value;
}
function drawLine() {
var y = frameCount % clockHeight;
var left, center, right
var ruleLookup;
var value;
for (x = 0; x < clockWidth; x ++) {
value = 0;
left = (state[(x - 1 + clockWidth) % clockWidth][y] > 0) ? 1 : 0;
value += state[(x - 1 + clockWidth) % clockWidth][y];
center = (state[x][y] > 0) ? 1 : 0;
value += state[x][y];
right = (state[(x + 1) % clockWidth][y] > 0) ? 1 : 0;
value += state[(x + 1) % clockWidth][y];
ruleLookup = left << 2 | center << 1 | right;
state[x][y] = 48 * rule[ruleLookup];
if (Math.random() < 0.0001)
state[x][y] = 1 - state[x][y];
//setClockPixel(x, y, value, 0, value);
}
}
// pass in the canvas to draw to
function initializeJupiterClock(name) {
frameCount = 0;
var milisecondsPerDay = 1000 * 60 * 60 * 24;
moonRenderZoom = 0.0001;
timeScale = 100000.0;
moonMassRenderScale = 0.000000000000000000001;
moon = [];
// larger moons
var io = new Moon();
io.period = 1.769137786 * milisecondsPerDay;
io.radius = 421700; // (420000 + 423400) / 2
io.mass = 1.075 * Math.pow(10, 23);
moon.push(io);
var europa = new Moon();
europa.period = 3.551181 * milisecondsPerDay;
europa.radius = 670900; // (664862 + 676938) / 2;
europa.mass = 4.799844 * Math.pow(10, 22);
moon.push(europa);
var ganymede = new Moon();
ganymede.period = 7.15455296 * milisecondsPerDay
ganymede.radius = (1069200 + 1071600) / 2; // using periapsis and apoapsis averaged, circular for now
ganymede.mass = 1.4819 * Math.pow(10, 23);
moon.push(ganymede);
var callisto = new Moon();
callisto.period = 16.6890184 * milisecondsPerDay;
callisto.radius = 1882700; // (1869000 + 1897000) / 2;
callisto.mass = 1.075938 * Math.pow(10, 23);
moon.push(callisto);
// smaller moons
if (true) {
var metis = new Moon();
metis.period = 0.294780 * milisecondsPerDay;
metis.radius = 128000;
metis.mass = 3.6 * Math.pow(10, 16);
moon.push(metis);
var adrastea = new Moon();
adrastea.period = 0.29826 * milisecondsPerDay;
adrastea.radius = 129000;
adrastea.mass = 2 * Math.pow(10, 15);
moon.push(adrastea);
var amalthea = new Moon();
amalthea.period = 0.49817943 * milisecondsPerDay;
amalthea.radius = 181365.84;
amalthea.mass = 2.08 * Math.pow(10, 18);
moon.push(amalthea);
var thebe = new Moon();
thebe.period = 0.674536 * milisecondsPerDay;
thebe.radius = 221889;
thebe.mass = 4.3 * Math.pow(10, 17);
moon.push(thebe);
var themisto = new Moon();
themisto.period = 129.82761 * milisecondsPerDay;
themisto.radius = 7391650;
themisto.mass = 6.89 * Math.pow(10, 14);
moon.push(themisto);
var leda = new Moon();
leda.period = 240.92 * milisecondsPerDay;
leda.radius = 11160000;
leda.mass = 1.1 * Math.pow(10, 16);
moon.push(leda);
}
// seed the generator with the input
seedDeterministicRandomNumberGenerator(110);
rule = [1, 0, 0, 1, 0, 0, 0, 1];
updateRuleReadouts();
//randomizeRule();
// get size from the canvas
zoom = 2;
var clockCanvas = document.getElementById(name);
renderWidth = clockCanvas.width;
renderHeight = clockCanvas.height;
clockWidth = Math.floor(clockCanvas.width / zoom);
clockHeight = Math.floor(clockCanvas.height / zoom);
console.log("canvas size is " + renderWidth + ", " + renderHeight + "\n");
// set up canvas and create imageData object for drawing pixels directly
clockContext = clockCanvas.getContext("2d");
clockImageData = clockContext.getImageData(0, 0, renderWidth, renderHeight);
// set up 2d array
var cellCount = 0;
state = new Array(clockWidth);
for (x = 0; x < clockWidth; x ++) {
state[x] = new Array(clockHeight);
}
// initialize the array
for (y = 0; y < clockHeight; y ++) {
for (x = 0; x < clockWidth; x ++) {
state[x][y] = 0;
cellCount ++;
}
}
console.log(cellCount + " cells created (square root is " + Math.sqrt(cellCount) + ")");
clearClockImage();
randomizeFirstLine();
redrawJupiterClock();
console.log("done initializing\n");
}
function randomizeRule() {
rule = [];
for (var i = 0; i < 8; i ++) {
rule[i] = parseInt(Math.random() * 2);
}
updateRuleReadouts();
}
function mutateRule() {
var digit = parseInt(Math.random() * 8);
rule[digit] = 1 - rule[digit];
updateRuleReadouts();
}
function updateRuleReadouts() {
// draw the indices for the rule
renderRuleIndex("ruleIndex");
// print rule to text field
var output = ""
for (var i = 0; i < 8; i ++)
output = output + rule[i];
console.log(output);
document.getElementById("ruleField").innerHTML = output;
}
//----------------------------------------------------------------------------//
// drawing functions
//----------------------------------------------------------------------------//
function randomizeFirstLine() {
var i;
var currentValue;
var output = "";
var y = 0;
//for (y = 0; y < clockHeight; y ++) {
for (x = 0; x < clockWidth; x ++) {
if (Math.random() > 0.5)
state[x][y] = Math.floor(Math.random() * 64);
else
state[x][y] = 0;
output = output + state[x][y];
}
output = output + "\n";
//}
console.log(output);
}
function setClockPixel(x, y, r, g, b) {
var index = (y * renderWidth + x) * 4;
clockImageData.data[index] = r;
clockImageData.data[index + 1] = g;
clockImageData.data[index + 2] = b;
}
/*
important note!
there was a really hard to find bug which caused half of the image not to be drawn
it turns out it was /here/ of all places. the clear function turned out to be
vital because it was initializing the alpha to opaque. the height in this function
was wrong, so it was only initializing the top half.
*/
function clearClockImage() {
var i;
var currentValue;
for (y = 0; y < renderHeight; y ++) {
for (x = 0; x < renderWidth; x ++) {
i = (y * renderWidth + x) * 4;
currentValue = 0;
clockImageData.data[i] = currentValue;
clockImageData.data[i + 1] = currentValue;
clockImageData.data[i + 2] = currentValue;
clockImageData.data[i + 3] = 255;
}
}
redrawJupiterClock();
}
function clearStates() {
for (y = 0; y < clockHeight; y ++) {
for (x = 0; x < width; x ++) {
state[x][y] = 0;
}
}
}
function redrawJupiterClock() {
//console.log(clockImageData);
clockContext.putImageData(clockImageData, 0, 0);
}
function drawStatesToImageData() {
var r, g, b;
for (var y = 0; y < clockHeight; y ++) {
for (var x = 0; x < clockWidth; x ++) {
if (state[x][y] == 0) {
r = g = b = 255;
}
else if (state[x][y] > 0) {
r = Math.max(0, 255 - state[x][y]);
g = Math.max(0, 255 - state[x][y]);
b = Math.max(0, 255 - state[x][y]);
}
for (y2 = 0; y2 < zoom; y2 ++) {
for (x2 = 0; x2 < zoom; x2 ++) {
setClockPixel(zoom * x + x2, zoom * y + y2, r, g, b);
}
}
}
}
var x = 1;
var y = clockHeight / 2 - 2;
r = 255;
g = 0;
b = 255;
for (y2 = 0; y2 < zoom; y2 ++) {
for (x2 = 0; x2 < zoom; x2 ++) {
setClockPixel(zoom * x + x2, zoom * y + y2, r, g, b);
}
}
}
function renderRuleIndex(name) {
var indexCanvas = document.getElementById(name);
// set up canvas and create imageData object for drawing pixels directly
var indexContext = indexCanvas.getContext("2d");
var rectSize = 2;
var left = 0;
for (var i = 0; i < 8; i ++) {
indexContext.fillStyle = "#000000";
console.log("i = " + i);
indexContext.fillRect(left, 0, 3 * rectSize + 2, rectSize + 2);
for (var i2 = 0; i2 < 3; i2 ++) {
var shift = 2 - i2;
var digit = i >> shift & 0x1;
console.log("i2 = " + i2 + " shift = " + shift + " digit = " + digit);
if (digit == 1)
indexContext.fillStyle = "#ffffff";
else
indexContext.fillStyle = "#000000";
indexContext.fillRect(left + 1 + i2 * 2, 1, 2, 2);
}
left += 3 * rectSize + 4;
}
}
//----------------------------------------------------------------------------//
// deterministic random number generator
//----------------------------------------------------------------------------//
// helper function which returns an integer from 0 to spread - 1
// for 4 it would be 0, 1, 2, or 3 (not 4)
function randomInt(spread) {
return (deterministicRandom() % spread);
}
function seedDeterministicRandomNumberGenerator(newSeed) {
seed = newSeed;
console.log("seed = " + seed + "\n");
}
function deterministicRandom() {
// define the recurrence relationship
seed = parseInt(a * seed + c) % 982451497;
// return an integer
// Could return a float in (0, 1) by dividing by m
return seed;
}
// other random functions
function random(min, max) {
return min + (max - min) * Math.random();
}