/**
* Implements the Skullcode system.
*
* This includes event dispatching, VM initialization, and main loop execution.
* @global
*/
var System = new function() {
var that = this;
/**
* Represents a phase of execution (e.g. init, run...).
* @constructor
* @abstract
*
* Phase life-cycle is as follows:
* begin(oldPhase);
* - called after previous phase ends but before this phase is active.
* wake();
* - called after this phase is activated (i.e. System.oldPhase = this).
* while (System.phase == this) {
* update(dt);
* - called on every main loop iteration, dt = time since last call.
* }
* end();
* - called when phase is ending but before next phase begins.
*/
function Phase() {
/**
* Called when this phase is activating.
*
* @param {Phase} oldPhase - The previous phase (null if none).
*/
this.begin = function(oldPhase) {};
/** Called when this phase is ending, before the next phase activates. */
this.end = function() {};
/** Called when this phase has activated but before the first update. */
this.wake = function() {};
/**
* Default NO-OP global event handler.
* @callback
* @param {InputState} s - The updated input state.
*/
var defaultHandler = function(s) {
return false;
};
/** Handle a 'character typed' event. */
this.charTyped = defaultHandler;
/** Handle a 'mouse scrolled' event. */
this.mouseWheel = defaultHandler;
/** Handle a 'mouse button released' event. */
this.mouseRelease = defaultHandler;
/** Handle a 'mouse button pressed' event. */
this.mousePress = defaultHandler;
/** Handle a 'mouse moved' event. */
this.mouseMove = defaultHandler;
/** Handle a 'key released' event. */
this.keyRelease = defaultHandler;
/** Handle a 'key pressed' event. */
this.keyPress = defaultHandler;
/**
* Called on every main loop iteration whilst this phase is activated.
*/
this.update = function(dt) {
return false;
};
}
this.Phase = Phase;
/**
* @name Vector2
* Represents a 2-vector.
* @constructor
*
* Initialized to (0,0).
*/
/**
* @name Vector2
* Copies a 2-vector.
* @constructor
*
* @param {Vector2} x - The vector to copy coordinates from.
*/
/**
* Represents a 2-vector.
*
* @constructor
* @param {number} x - The initial x-coordinate of the vector.
* @param {number} y - The initial y-coordinate of the vector.
*/
function Vector2(x, y) {
var that = this;
if (arguments.length >= 2) {
this.x = x;
this.y = y;
} else if (arguments.length == 1) {
this.x = x.x;
this.y = x.y;
} else {
this.x = this.y = 0;
}
this.toString = function() {
return "{x:" + that.x + ",y:" + that.y + "}";
};
}
this.Vector2 = Vector2;
/**
* Represents a sample of input states.
* @constructor
*
* @param {InputState} [obj] - The InputState object to copy, if specified.
*
* If no InputState object is given, the state properties are initialized to
* 0/false.
*
* @member {Vector2} pagePos - Position of mouse in page coordinates.
* @member {Vector2} mousePos - Position of mouse in canvas coordinates.
* @member {Vector2} mouseMov - Distance moved since list mousemove event.
* @member {number} mouseWheel - 1 if last scrolled down, -1 if last scrolled up.
* @member {number} mouseButton - Bit-flags indicating currently pressed mouse buttons.
* @member {number} keyCode - key-code of last pressed key (see KeyboardEvent.keyCode).
* @member {number} charCode - char-code of last pressed key (see KeyboardEvent.charCode).
* @member {boolean} ctrlPressed - Flag indicating whether CTRL is pressed.
* @member {boolean} altPressed - Flag indicating whether ALT is pressed.
* @member {boolean} shiftPressed - Flag indicating whether SHIFT is pressed.
*/
function InputState(obj) {
if (arguments.length > 0) {
this.pagePos = new Vector2(obj.pagePos);
this.mousePos = new Vector2(obj.mousePos);
this.mouseMov = new Vector2(obj.mouseMov);
this.mouseWheel = obj.mouseWheel;
this.mouseButton = obj.mouseButton;
this.keyCode = obj.keyCode;
this.charCode = obj.charCode;
this.ctrlPressed = obj.ctrlPressed;
this.altPressed = obj.altPressed;
this.shiftPressed = obj.shiftPressed;
} else {
this.pagePos = new Vector2();
this.mousePos = new Vector2();
this.mouseMov = new Vector2();
this.charCode = this.keyCode = this.mouseWheel = this.mouseButton = 0;
this.shiftPressed = this.altPressed = this.ctrlPressed = false;
}
}
this.InputState = InputState;
/**
* Update the input state with the current modifier key states.
*
* @param {Event} evt - The current DOM event.
*/
function updateModifiers(evt) {
inputState.ctrlPressed = evt.ctrlKey;
inputState.altPressed = evt.altKey;
inputState.shiftPressed = evt.shiftKey;
}
/**
* Initialize input callbacks and start engine.
*/
function init() {
if (canvasLoaded) {
that.phase = new Phase();
oldPhase = null;
// mousewheel handler
var mousewheelHandler = function(evt) {
try {
updateModifiers(evt);
// Get direction of scroll (1 = down, -1 = up)
inputState.mouseWheel = (0 < (evt.detail ? evt.detail : -evt.wheelDelta) ? 1 : -1);
if (that.phase !== null) {
that.phase.mouseWheel(inputState);
}
} catch (err) {}
evt.preventDefault();
};
document.addEventListener("DOMMouseScroll", mousewheelHandler, false);
document.addEventListener("mousewheel", mousewheelHandler, false);
// mousemove handler
window.addEventListener("mousemove", function(evt) {
updateModifiers(evt);
var canvasRect = canvas.getBoundingClientRect();
inputState.mousePos.x = Math.floor((inputState.pagePos.x = evt.pageX) - canvasRect.left);
inputState.mousePos.y = Math.floor((inputState.pagePos.y = evt.pageY) - canvasRect.top);
if (evt.movementX !== 0) {
inputState.mouseMov.x = evt.movementX;
inputState.mouseMov.y = evt.movementY;
} else if (evt.mozMovementX !== 0) {
inputState.mouseMov.x = evt.mozMovementX;
inputState.mouseMov.y = evt.mozMovementY;
} else if (evt.webkitMovementX !== 0) {
inputState.mouseMov.x = evt.webkitMovementX;
inputState.mouseMov.y = evt.webkitMovementY;
}
if (that.phase !== null) {
that.phase.mouseMove(inputState);
}
evt.preventDefault();
}, false);
// mousedown handler
canvas.addEventListener("mousedown", function(evt) {
updateModifiers(evt);
evt.preventDefault();
document.activeElement.blur();
inputState.mouseButton |= 1 << evt.button;
if (that.phase !== null) {
that.phase.mousePress(inputState);
}
}, false);
// contextmenu handler
bodyElement.oncontextmenu = function() {
return contextMenuAllowed;
};
// mouseup handler
window.addEventListener("mouseup", function(evt) {
updateModifiers(evt);
evt.preventDefault();
inputState.mouseButton &= ~(1 << evt.button);
if (that.phase !== null) {
that.phase.mouseRelease(inputState);
}
}, false);
// keydown handler
document.addEventListener("keydown", function(evt) {
updateModifiers(evt);
inputState.keyCode = evt.keyCode;
// Prevent tab/backspace from doing their default actions
if (evt.keyCode === 8 || evt.keyCode === 9) {
evt.preventDefault();
}
if (that.phase !== null) {
that.phase.keyPress(inputState);
}
}, false);
// keypress handler
document.addEventListener("keypress", function(evt) {
updateModifiers(evt);
inputState.keyCode = evt.keyCode;
inputState.charCode = evt.charCode;
if (that.phase !== null) {
that.phase.charTyped(inputState);
}
evt.preventDefault();
}, false);
// keyup handler
document.addEventListener("keyup", function(evt) {
updateModifiers(evt);
inputState.keyCode = evt.keyCode;
if (that.phase !== null) {
that.phase.keyRelease(inputState);
}
evt.preventDefault();
}, false);
postInit();
that.update();
}
}
/**
* Call onInit handlers and prevent further attempts to initialize.
*/
function postInit() {
for (var i = 0; i < initHandlers.length; i++) {
try {
initHandlers[i](systemConfig);
} catch (err) {
that.err("postInit routine #" + i, err);
}
}
initDone = true;
}
/**
* Execute a loop of the engine.
*
* @param {DOMHighResTimeStamp} timestamp - The current time.
* @callback Window.requestAnimationFrame
*/
function mainLoop(timestamp) {
if (realtimeEnabled) {
// Schedule update ASAP
window.requestAnimationFrame(mainLoop);
}
var dt = 0;
if (prevTimestamp != 0) {
dt = timestamp - prevTimestamp;
}
prevTimestamp = timestamp;
try {
if (that.phase !== oldPhase) {
if (oldPhase !== null) {
oldPhase.end();
}
if (that.phase != null) {
that.phase.begin(oldPhase);
oldPhase = that.phase;
that.phase.wake();
}
}
if (that.phase != null) {
that.phase.update(dt);
}
} catch (err) {
that.err("mainLoop()", err);
}
}
/** Skullcode version? */
this.version = 1408337099853;
/** @constant {number} */
this.TAU = 6.283185307179586;
/** @constant {number} */
this.RAD2DEG = 57.2957795131;
/** @constant {number} */
this.DEG2RAD = 0.01745329251;
/** @constant {number} */
this.VIEW2D = 0;
/** @constant {number} */
this.VIEW3D = 1;
/** @constant {number} */
this.NEAREST = 0;
/** @constant {number} */
this.LINEAR = 1;
/** @constant {number} */
this.DRAW = 1;
/** @constant {number} */
this.CACHE = 2;
/** @constant {number} */
this.DRAW_CACHE = this.DRAW | this.CACHE;
/** Currently active phase. */
this.phase = null;
var realtimeEnabled = true,
bodyElement = null,
oldPhase = null,
canvas = null,
initHandlers = [],
initDone = false,
canvasLoaded = false,
systemConfig = null,
intervalID = null,
inputState = new InputState(),
prevTimestamp = 0,
contextMenuAllowed = false;
/**
* Set whether the context menu is enabled.
*
* @param {boolean} enable - If true, allow the context menu to appear.
*/
this.allowContextMenu = function(enable) {
contextMenuAllowed = enable ? true : false;
};
/**
* Log a given message (triggers a window.alert()).
*
* @param {string} msg - The message to log.
*/
this.log = function(msg) {
window.alert(msg);
};
/**
* Log a given error.
*
* @param {string} msg - A string describing the error.
* @param {exception} [exc] - An exception associated with the error.
*/
this.err = function(msg, exc) {
try {
if (arguments.length < 2) {
this.log("ERROR: " + msg);
} else {
this.log("ERROR: " + msg + " : " + exc.toString() + " : line " +
exc.lineNumber + " of file: " + exc.fileName);
}
} catch (err) {}
};
/**
* Log a given warning.
*
* @param {string} msg - A string describing the warning.
* @param {exception} [exc] - An exception associated with the warning.
*/
this.warn = function(msg, exc) {
try {
if (arguments.length < 2) {
this.log("WARNING: " + msg);
} else {
this.log("WARNING: " + msg + " : " + exc.toString() + " : line " +
exc.lineNumber + " of file: " + exc.fileName);
}
} catch (err) {}
};
/**
* Add a callback to the list of onInit handlers.
*
* @param {function(systemConfig)} f - The function to call on initialization.
*/
this.onInit = function(f) {
if (!initDone) {
initHandlers.push(f);
} else {
this.err("Function not added via onInit(), already initialized");
}
};
/**
* Configure and initialize the engine.
*
* @param {dictionary} config - The configuration of the engine.
*
* Supported configuration options are:
* realtime - ?
* canvas - ID/DOM object of canvas to render to.
*/
this.start = function(config) {
if (systemConfig != null) {
this.err("start() called while engine is already running.");
return false;
}
systemConfig = {
realtime: true,
canvas: "viewport"
};
if (typeof config != "undefined" && config !== null) {
try {
systemConfig.realtime = config.realtime ? true : false;
if (typeof config.canvas != "undefined") {
systemConfig.canvas = config.canvas;
}
} catch (err) {
this.err("setup object invalid ", err);
return false;
}
}
return init();
};
/**
* Enable or disable realtime execution.
*
* @param {boolean} enable - If true, VM updates will occur as soon as possible.
*/
this.setRealtime = function(enable) {
if (enable && realtimeEnabled != enable) {
window.requestAnimationFrame(mainLoop);
}
realtimeEnabled = enable;
};
/**
* Enable or disable image smoothing.
*
* @param {CanvasRenderingContext2D} context - The context to set image smoothing state on.
* @param {boolean} enable
*/
this.scaleSmoothing = function(context, enable) {
enable = enable ? true : false;
context.imageSmoothingEnabled = enable;
context.mozImageSmoothingEnabled = enable;
context.webkitImageSmoothingEnabled = enable;
};
/**
* Enter or leave full-screen mode.
*
* @param {boolean} enable
*/
this.setFullScreen = function(enable) {
this.log("Setting screen mode: " + enable);
if (enable) {
if (canvas.requestFullscreen) {
canvas.requestFullscreen();
} else if (canvas.msRequestFullscreen) {
canvas.msRequestFullscreen();
} else if (canvas.mozRequestFullScreen) {
canvas.mozRequestFullScreen();
} else if (canvas.webkitRequestFullscreen) {
canvas.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
} else {
this.log("Fullscreen not supported.");
}
} else {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
} else {
this.log("Fullscreen not supported.");
}
}
};
/**
* Enable or disable pointer lock.
*
* @param {boolean} enable
*/
this.setPointerLock = function(enable) {
this.log("Setting pointer lock: " + enable);
if (enable) {
if (canvas.requestPointerLock) {
canvas.requestPointerLock();
} else if (canvas.mozRequestPointerLock) {
canvas.mozRequestPointerLock();
} else if (canvas.webkitRequestPointerLock) {
canvas.webkitRequestPointerLock();
} else {
this.log("Pointer lock not supported.");
}
} else {
if (canvas.exitPointerLock) {
canvas.exitPointerLock();
} else if (canvas.mozExitPointerLock) {
canvas.mozExitPointerLock();
} else if (canvas.webkitExitPointerLock) {
canvas.webkitExitPointerLock();
} else {
this.log("Pointer lock not supported.");
}
}
};
/**
* Run a single iteration of the main engine loop.
*/
this.update = function() {
window.requestAnimationFrame(mainLoop);
};
// Wait for canvas to load before initializing.
intervalID = window.setInterval(function preInit() {
try {
if (!bodyElement) {
bodyElement = document.getElementsByTagName("body")[0];
}
if (systemConfig !== null && canvas === null) {
if ("string" == typeof systemConfig.canvas) {
canvas = document.getElementById(systemConfig.canvas);
if (canvas) {
systemConfig.canvas = canvas;
}
} else {
canvas = systemConfig.canvas;
}
}
if (bodyElement && canvas) {
window.clearInterval(intervalID);
intervalID = null;
canvasLoaded = true;
if (systemConfig != null) {
// System is configured, make sure it is initialized.
init();
}
}
} catch (err) {
that.err("preInit() failed", err);
}
}, 250);
/**
* Represents a point travelling on a linearly interpolated 1-D trajectory.
* The interpolation equation used is:
* y(t) = t/MaxT
* where t starts at 0 and is clamped to the range [0, maxT].
*
* Effectively, this means the point travels at a constant speed defined by maxT.
*
* @member {number} value - The current position along the trajectory (normalized).
* @param {number} maxT - The end time of the trajectory.
*/
function LinearInterp(maxT) {
var that = this,
t = that.value = 0;
/**
* Advance the point along the trajectory.
*
* @param {number} dt - The time to advance the point by.
*/
that.advance = function(dt) {
t += dt;
// Clamp to [0, maxT]
if (t <= 0) {
that.value = t = 0;
} else if (t >= maxT) {
t = maxT;
that.value = 1;
} else {
that.value = t / maxT;
}
return that.value;
};
/**
* Reset the point to the start of the trajectory.
*
* @param {number} [newMaxT] - A new end time for the trajectory.
*/
that.reset = function(newMaxT) {
t = that.value = 0;
if (arguments.length > 0) {
maxT = newMaxT;
}
};
}
this.LinearInterp = LinearInterp;
/**
* @name Vector3
* Represents a 3-vector.
* @constructor
*
* Initialized to (0,0,0).
*/
/**
* @name Vector3
* Copies a 3-vector.
* @constructor
*
* @param {Vector3} x - The vector to copy coordinates from.
*/
/**
* Represents a 3-vector.
*
* @constructor
* @param {number} x - The initial x-coordinate of the vector.
* @param {number} y - The initial y-coordinate of the vector.
* @param {number} z - The initial z-coordinate of the vector.
*/
function Vector3(x, y, z) {
var that = this;
if (arguments.length >= 2) {
this.x = x;
this.y = y;
this.z = z;
} else if (arguments.length == 1) {
this.x = x.x;
this.y = x.y;
this.z = x.z;
} else {
this.x = this.y = this.z = 0;
}
this.toString = function() {
return "{x:" + that.x + ",y:" + that.y + ",z:" + that.z + "}";
};
}
this.Vector3 = Vector3;
/**
* Represents a linked list of objects.
* @constructor
*
* Note: 'prior' and 'next' properties are added to objects that are
* added to this linked list, and can be used to navigate it.
*/
this.LinkedList = function() {
var _first = null,
_last = null;
/** @returns {object} The first object in the list. */
this.first = function() {
return _first;
};
/** @returns {object} The last object in the list. */
this.last = function() {
return _last;
};
/**
* Add an object to the end of the list.
*
* @param {object} elem - The object to add.
*/
this.add = function(elem) {
if (_first == null) {
_first = elem;
} else {
_last.next = elem;
}
elem.prior = _last;
_last = elem;
};
/**
* Remove an object from the list (next and prior are set to null).
*
* @param {object} elem - The object to remove.
*/
this.unlink = function(elem) {
if (elem.next == null) {
_last = elem.prior;
} else {
elem.next.prior = elem.prior;
}
if (elem.prior == null) {
_first = elem.next;
} else {
elem.prior.next = elem.next;
}
elem.prior = elem.next = null;
};
/**
* Insert an object after an element.
*
* @param {object} elem - The object to insert.
* @param {object} [target] - The object to insert before (defaults to this.first)
*/
this.insert = function(elem, target) {
if (null == target) {
target = _first;
}
if (null == target) {
_last = _first = elem;
} else {
if (target === _first) {
_first = elem;
} else {
target.prior.next = elem;
}
elem.prior = target.prior;
target.prior = elem;
elem.next = target;
}
};
/**
* Clear the contents of this list (note: does not reset elements).
*/
this.empty = function() {
_first = _last = null;
};
/**
* Attach a list to the end of this list.
*
* @param {LinkedList} list - The list to attach (will be emptied afterwards).
*/
this.attach = function(list) {
if (_first == null) {
_first = list.first();
_last = list.last();
} else {
_last.next = list.first();
if (_last.next != null) {
_last.next.prior = _last;
_last = list.last();
}
}
list.empty();
};
};
/**
* Represents a linear-congruential pseudo-random number generator (LCPRNG).
* @constructor
*
* Note: This doesn't actually appear to be an LCPRNG, instead it populates
* a table using the original MINSTD PRNG then uses those numbers to generate
* the output stream according to X_n = X_{n+1} + X_{n+30} (for the default
* tblSize of 32 this is equivalent to X_n = X_{n-31} + X_{n-2}).
*
* @param {number} [seed=1] - Seed value used to populate table.
* @param {number} [tblSize=32] - Size of table.
*/
this.LCPRNG = function(seed, tblSize) {
var that = this,
tbl = [],
b = 0;
/**
* @function seed
* Get the seed value for this PRNG.
* @returns {number} The current PRNG seed.
*/
/**
* Set the seed value for this PRNG and reinitialize it.
*
* @param {number} newSeed - New seed value to use.
* @returns {number} The new PRNG seed.
*/
that.seed = function(newSeed) {
if (arguments.length > 0) {
tbl[0] = seed = newSeed | 0;
for (var g = tblSize - 1, d = 1; d < g; ++d) {
tbl[d] = 16807 * tbl[d - 1] & 0x7FFFFFFF;
}
tbl[g] = tbl[0];
tbl[0] = tbl[1];
tbl[1] = tbl[2];
b = 2;
g = tblSize << 4;
for (d = 0; d < g; ++d) {
that.rand();
}
}
return seed;
};
/**
* Generate a new random number.
* @returns {number} A new random number.
*/
that.rand = function() {
var e = b;
b = (b + 1) % tblSize;
tbl[e] = tbl[b] + tbl[(b + 29) % tblSize] >> 0;
return tbl[e];
};
/**
* Return a string representation of this PRNG.
*
* Note: Actually returns a number.
*
* @returns {number} The seed of the PRNG.
*/
that.toString = function() {
return that.seed();
};
// Set defaults
if (arguments.length < 1) {
seed = 1;
}
if (arguments.length < 2) {
tblSize = 32;
}
// Initialize PRNG.
that.seed(seed);
};
/**
* Represents a point travelling on a sine-interpolated 1-D trajectory.
* The interpolation equation used is:
* y(t) = sin((pi/2) * (t/MaxT))
* where t starts at 0 and is clamped to the range [0, maxT].
*
* Effectively, this means the point starts fast then slows down until it reaches half of maxT,
* before speeding up as it approaches maxT.
*
* @member {number} value - The current position along the trajectory (normalized).
* @param {number} maxT - The end point of the trajectory.
*/
this.SineInterp = function(maxT) {
var that = this;
that.value = 0;
var linearInterp = new LinearInterp(maxT),
c = Math.PI / 2;
/**
* Advance the point along the trajectory.
*
* @param {number} dt - The time to advance the point by.
*/
that.advance = function(dt) {
dt = linearInterp.advance(dt);
if (dt == 1) {
that.value = 1;
} else if (dt == 0) {
that.value = 0;
} else {
that.value = Math.sin(dt * c);
}
return that.value;
};
/**
* Reset the point to the start of the trajectory.
*
* @param {number} [newMaxT] - A new end time for the trajectory.
*/
that.reset = function(newMaxT) {
that.value = 0;
if (arguments.length > 0) {
linearInterp.reset(newMaxT);
} else {
linearInterp.reset();
}
};
};
/**
* Represents a point travelling on a cosine-interpolated 1-D trajectory.
* The interpolation equation used is:
* y(t) = 0.5*(1-cos(pi * (t/MaxT)))
* where t starts at 0 and is clamped to the range [0, maxT].
*
* Effectively, this means the point starts slow and first speeds up
* until it reaches maxT/4, slows down as it approaches maxT/2, speeds
* up again as it reaches 3*maxT/4 and finally slows to a stop at maxT.
*
* @member {number} value - The current position along the trajectory (normalized).
* @param {number} maxT - The end point of the trajectory.
*/
this.CosineInterp = function(maxT) {
var that = this;
that.value = 0;
var linearInterp = new LinearInterp(maxT),
c = Math.PI;
/**
* Advance the point along the trajectory.
*
* @param {number} dt - The time to advance the point by.
*/
that.advance = function(dt) {
dt = linearInterp.advance(dt);
if (dt == 1) {
that.value = 1;
} else if (dt == 0) {
that.value = 0;
} else {
that.value = 0.5 * (1 - Math.cos(dt * c));
}
return that.value;
};
/**
* Reset the point to the start of the trajectory.
*
* @param {number} [newMaxT] - A new end time for the trajectory.
*/
that.reset = function(newMaxT) {
that.value = 0;
if (arguments.length > 0) {
linearInterp.reset(newMaxT);
} else {
linearInterp.reset();
}
};
};
/**
* @name Box3
* Represents a 3D box, defined by two coordinate vectors.
* @constructor
*
* Initialized to {a=(0,0,0), b=(0,0,0)}.
*/
/**
* @name Box3
* Copies a 3D box.
* @constructor
*
* @param {Box3} aX - The vector to copy coordinates from.
*/
/**
* @name Box3
* Represents a 3D box, defined by two coordinate vectors.
*
* @constructor
* @param {Vector3} aX - A vector (a) defining a corner of the box.
* @param {Vector3} aY - A vector (b) defining the corner of the box opposite to a.
*/
/**
* Represents a 3D box, defined by two coordinate vectors.
*
* @constructor
* @param {number} aX - The initial x-coordinate of the a vector.
* @param {number} aY - The initial y-coordinate of the a vector.
* @param {number} aZ - The initial z-coordinate of the a vector.
* @param {number} bX - The initial x-coordinate of the b vector.
* @param {number} bY - The initial y-coordinate of the b vector.
* @param {number} bZ - The initial z-coordinate of the b vector.
*/
this.Box3 = function(aX, aY, aZ, bX, bY, bZ) {
var that = this;
if (arguments.length >= 5) {
// aX-aZ = a coords, bX-bZ = b coords
that.a = new Vector3(aX, aY, aZ);
that.b = new Vector3(bX, bY, bZ);
} else if (arguments.length > 1) {
// aX = Vector3, aY = Vector3
that.a = new Vector3(aX);
that.b = new Vector3(aY);
} else if (arguments.length == 1) {
// aX = Box3
that.a = new Vector3(aX.a);
that.b = new Vector3(aX.b);
} else {
that.a = new Vector3();
that.b = new Vector3();
}
this.toString = function() {
return "{a:" + that.a + ",b:" + that.b + "}";
};
};
/**
* Calculates the cross product of two Vector3 instances.
*
* @param {Vector3} a - The left parameter to the cross product.
* @param {Vector3} b - The right parameter to the cross product.
* @param {Vector3} c - The Vector3 to set to the result.
*/
this.cross = function(a, b, c) {
c.x = a.y * b.z - a.z * b.y;
c.y = a.z * b.x - a.x * b.z;
c.z = a.x * b.y - a.y * b.x;
};
var isLocal = "file:" == window.location.protocol;
/**
* Called when a remote object has been fetched.
*
* @callback System~fetchObjectCallback
* @param {object} obj - The fetched object, or null if an error occurred.
*/
/**
* Fetches a remote script and executes it.
*
* If Skullcode is running remotely, this uses an XMLHttpRequest to
* download the script and evals it to get the fetched object.
*
* If Skullcode is running locally, this uses a script tag to source the
* script and gets the fetched object from the _JSE_ variable.
*
* @param {string} fileName - The filename of the script to load.
* @param {System~fetchObjectCallback} callback - A callback to pass the fetched object to.
*/
function fetchObject(fileName, callback) {
function handleXMLHttpRequest(xhr_param) {
if (!xhr_param.isLoaded) {
xhr_param.isLoaded = true;
try {
var obj = eval(xhr.responseText);
callback(obj);
} catch (err) {
callback(null);
}
}
}
var bodyElement = document.getElementsByTagName("body")[0];
if (!bodyElement) {
return false;
}
if (isLocal) {
_JSE_ = null;
var scriptElement = document.createElement("script");
scriptElement.onload = function() {
callback(_JSE_);
bodyElement.removeChild(scriptElement);
};
scriptElement.onerror = function() {
that.log("fetchObj() failed to load: " + fileName);
bodyElement.removeChild(scriptElement);
callback(null);
};
scriptElement.src = fileName;
bodyElement.appendChild(scriptElement);
} else {
var xhr = new XMLHttpRequest();
xhr.isLoaded = false;
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
handleXMLHttpRequest(xhr);
}
};
xhr.onload = function() {
handleXMLHttpRequest(xhr);
};
xhr.ontimeout = function() {
that.log("fetchObj() failed to download: " + fileName);
callback(null);
};
xhr.open("GET", fileName, true);
xhr.send(null);
}
}
this.fetchObject = fetchObject;
/**
* Called when a queued image has been loaded.
*
* @callback ImageLoader~imageLoadedCallback
* @param {Image} img - The fetched image.
* @param {boolean} loaded - True if the image loaded, false if an error occurred.
* @param {string} filename - The filename of the fetched image.
* @param {object} param - The parameter passed to ImageLoader~queue.
*/
/**
* Loads images, asynchronously.
* @constructor
*
* @param {string} [basePath=""] - A base path to prepend to image filenames.
* @param {ImageLoader~imageLoadedCallback} [callback] - A callback to pass the loaded image to.
* @member {number} loaded - The number of images which have successfully loaded.
* @member {number} fails - The number of images which have failed to load.
* @member {number} count - The number of images that have been queued to load.
*/
this.ImageLoader = function(basePath, callback) {
this.fails = this.loaded = this.count = 0;
/**
* Queues an image to be loaded.
* Upon completion, the callback supplied to the class
* constructor will be called with the image filename and parameter.
*
* @param {string} filename - The image filename.
* @param {object} param - The parameter to pass to the callback.
*/
this.queue = function(filename, param) {
var img = new Image();
filename = basePath + filename;
img.onload = function() {
var fn = filename;
try {
that.loaded++;
if (callback) {
callback(img, true, fn, param);
}
} catch (err) {}
};
img.onerror = function() {
var fn = filename;
try {
that.fails++;
if (callback) {
callback(img, false, fn, param);
}
} catch (err) {}
};
img.src = filename;
imageQueue[imageQueue.length] = img;
that.count = imageQueue.length;
};
var that = this,
imageQueue = [];
// Default to empty basePath
if (basePath == 0) {
basePath = "";
}
};
/**
* Represents an 'asset'.
*
* Note if no asset ID is supplied, one will be generated automatically
* and bits 0 and 2 of Asset~flag will be set.
*
* Asset~flag is a bitmap, defined as follows:
* bit 0: set if asset is loaded
* bit 1: set if asset is stowed
* bit 2: set if asset has been updated.
*
* Assets are automatically added to the 'assets' free variable.
*
* @param {string} [type="unknown"] - Type of asset.
* @param {string} [name=""] - Name of asset.
* @param {string} [path=""] - Path to asset.
* @param {number} [flag=0] - Asset flags.
* @param {number} [id] - Unique asset ID.
* @param {number} [rev=0] - Asset revision?
*/
this.Asset = function(type, name, path, flag, id, rev) {
/**
* Gets/sets the value of a set of flags.
*
* @param {boolean|null} val - The value to set the flags to (if null, change nothing).
* @param {number} mask - A bitmask indicating which flags to read/write.
* @returns {boolean} True if any of the masked flags were set.
*/
function setflag(val, mask) {
if (val != null) {
if (val) {
that.flag = that.flag | mask;
} else {
that.flag = that.flag & ~mask;
}
}
if (that.flag & mask > 0) {
return true;
} else {
return false;
}
}
var that = this;
/** Type of asset. */
that.type = type ? type : "unknown";
/** Name of asset. */
that.name = name ? name : "";
/** Path to asset. */
that.path = path ? path : "";
/** Asset flags. */
that.flag = flag ? flag : 0;
/** Asset revision? */
that.rev = rev ? rev : 0;
if (arguments.length >= 5) {
/** Asset ID */
that.id = id;
} else {
that.id = assets.length;
that.flag |= 6;
}
assets[that.id] = that;
/**
* Get/set the asset loaded flag.
*
* @param {boolean|null} val - The value to set the flag to (if null, change nothing).
* @returns {boolean} True if the flag was set, otherwise false.
*/
that.loaded = function(val) {
return setflag(1 > arguments.length ? null : val, 1);
};
/**
* Get/set the asset stowed flag.
*
* @param {boolean|null} val - The value to set the flag to (if null, change nothing).
* @returns {boolean} True if the flag was set, otherwise false.
*/
that.stow = function(val) {
return setflag(1 > arguments.length ? null : val, 2);
};
/**
* Get/set the asset updated flag.
*
* @param {boolean|null} val - The value to set the flag to (if null, change nothing).
* @returns {boolean} True if the flag was set, otherwise false.
*/
that.updated = function(val) {
return setflag(1 > arguments.length ? null : val, 4);
};
/**
* Unknown, toString implied that this returns null or a string
* suitable for insertion in a JSON object?
*/
that.customJSE = function() {
return null;
};
/**
* Serialize the Asset to a JSON string.
*
* @returns {string} A JSON representation of this Asset suitable.
*/
that.toString = function() {
var str = that.customJSE(),
prevFlags = that.flags;
that.loaded(false);
that.updated(false);
str = "{\tid :\t" + that.id +
",\n\trev :\t" + that.rev +
",\n\tflag :\t" + that.flag +
",\n\ttype :\t" + JSON.stringify(that.type) +
",\n\tname :\t" + JSON.stringify(that.name) +
",\n\tpath :\t" + JSON.stringify(that.path) +
(null == str ? "" : ",\n\t" + str) + "\n}";
that.flags = prevFlags;
return str;
};
};
var sys = this; // Shortcut to allow AssetManager to log to System.log
/**
* Manages storage and retrieval of Assets.
*
* @param {string} [assetPath=""] - Base path for remotely loaded assets
*/
this.AssetManager = function(assetPath) {
var that = this,
managedAssets = {},
fetchWaiters = {},
assetConstructors = {};
assetPath = arguments.length < 1 ? "" : assetPath.toString();
/**
* Called to create an Asset from it's stored representation.
*
* @callback AssetManager~assetConstructor
* @param {object} data - The JSON object to reconstruct the asset from.
* @param {string} path - The path to the asset.
*/
/**
* Register a constructor for a type of Asset.
*
* @param {string} type - The type to register the constructor under.
* @param {AssetManager~assetConstructor} cons - The constructor to register.
*/
that.register = function(id, cons) {
assetConstructors[id] = cons;
};
/**
* Store all managed Assets to local or remote storage.
*
* @param {boolean} partial - True if only assets with the 'updated' flag set should be stored.
* @param {AssetManager~storeCallback} callback - The callback to call after each store completes.
* @param {boolean} remote - If true, the assets will be stored remotely, otherwise localStorage will be used.
*/
that.storeAll = function(partial, callback, remote) {
var count = 0,
id;
for (id in managedAssets) {
var data = managedAssets[id];
/*
* This block replaces the following expression:
* null == h || b && "function" == typeof h.updated && !h.updated() || (++f, a.store(h, c, d))
* The original expression is rather confusing, so the process
* of deobfuscation has been recorded below to help detect any mistakes.
*
* Logical AND has higher precedence than OR, so an equivalent expression is:
* null == h || (b && "function" == typeof h.updated && !h.updated()) || (++f, a.store(h, c, d))
* Logical operators are left-associative:
* (null == h || (b && "function" == typeof h.updated && !h.updated())) || (++f, a.store(h, c, d))
* Short-circuit evaluation means we can convert this to an 'if' statement:
* if (!(null == h || (b && "function" == typeof h.updated && !h.updated()))) {
* (++f, a.store(h, c, d))
* }
* Applying De Morgan's law:
* if (null != h && !(b && "function" == typeof h.updated && !h.updated())) {
* (++f, a.store(h, c, d))
* }
* Applying short-circuit evaluation again:
* if (null != h) {
* if (!(b && "function" == typeof h.updated && !h.updated())) {
* (++f, a.store(h, c, d))
* }
* }
* Applying De Morgan's law again:
* if (null != h) {
* if (!b || "function" != typeof h.updated || h.updated()) {
* (++f, a.store(h, c, d))
* }
* }
* Assuming typeof has no side-effects (since h != null), we
* can group the latter two sub-expressions:
* if (null != h) {
* if (!b || ("function" != typeof h.updated || h.updated())) {
* (++f, a.store(h, c, d))
* }
* }
* Applying De Morgan's law one last time:
* if (null != h) {
* if (!b || ("function" == typeof h.updated && h.updated())) {
* (++f, a.store(h, c, d))
* }
* }
* This can then be rearranged to get the final expression below.
*/
if (data != null) {
if (!partial || ("function" == typeof data.updated && data.updated())) {
count++;
that.store(data, callback, remote);
}
}
}
return count;
};
/**
* Called when an Asset has been stored.
*
* @callback AssetManager~storeCallback
* @param {Asset} data - The stored asset.
* @param {number} status - 1 on success, 0 on failure.
*/
/**
* Store an Asset to local or remote storage.
*
* Note: Remote storage is unimplemented.
* Local storage stores assets according to their path fields.
*
* Assets are serialized using the toString function.
*
* @param {Asset} data - The asset to store.
* @param {AssetManager~storeCallback} callback - The callback to call after the store completes.
* @param {boolean} remote - If true, the asset will be stored remotely, otherwise localStorage will be used.
*/
that.store = function(data, callback, remote) {
if (remote) {
sys.log("Error: AssetManager.store(): Remote storage unimplemented.", "#911");
try {
callback(data, 0);
} catch (err) {
sys.log("Error: AssetManager.store() callback(data,0): " + err);
}
} else if (localStorage) {
try {
localStorage.setItem(data.path, "_JSE_=" + data.toString());
try {
callback(data, 1);
} catch (err) {
sys.log("Error: AssetManager.store() callback(data,1): " + err);
}
} catch (err) {
sys.log("Error: AssetManager.store() localStorage.setItem(): " + err);
try {
callback(data, 0);
} catch (err2) {
sys.log("Error: AssetManager.store() callback(data,0): " + err2);
}
}
} else {
sys.log("Error: AssetManager.store(): No localStorage");
try {
callback(data, 0);
} catch (err2) {
sys.log("Error: AssetManager.store() callback(data,0): " + err2);
}
}
};
/**
* Called when a fetched asset becomes available.
*
* @callback AssetManager~fetchCallback
* @param {object} obj - The fetched asset, or null if the fetch failed.
* @param {string} path - The path to the fetched asset.
*/
/**
* Fetch an asset.
*
* This function searches for the asset in the following places, ordered by search order:
* 1) Currently loaded/managed assets
* 2) Browser LocalStorage
* 3) Remote host (via fetchObject)
*
* @param {string} path - Path to asset.
* @param {AssetManager~fetchCallback} callback - The callback to call when the asset is available.
* @param {boolean} [asap=false] - If true, prevent the callback from being queued after other callbacks.
*
* @returns {number}
* -1 if callback triggered an error
* 0 if asset was already loaded (callback was called)
* 1 if asset retrieved from local storage (callback was called)
* 2 if asset is being fetched (callback will be called when asset is available)
* 3 if callback was queued (callback will be called after a previous call to fetch for this asset resolves).
* The value of asap, if callback would have been queued but asap was true (callback will not be called).
*
* If asap is true, the callback will not be queued if a fetch is in progress. This ensures that the callback
* will either be called as soon as the asset is available (immediately if it is already loaded or before any
* other callbacks if it is being fetched asynchronously) or not at all.
*/
that.fetch = function(path, callback, asap) {
if (arguments.length < 3) {
asap = false;
}
var asset = managedAssets[path];
// Fetch asset from cache
if (asset) {
try {
sys.log("AM: already loaded: " + path);
callback(asset, path);
} catch (err) {
sys.log("AssetManager callback error '" + assetPath + path + "': " + err, "#911");
return -1;
}
return 0;
}
// Check for existing waiters (lists of callbacks) for this path
var callbackList = fetchWaiters[path];
if (callbackList) {
if (asap) {
return asap;
} else {
callbackList.push(func); // possible bug, should be callback?
return 3;
}
}
var callbackList = fetchWaiters[path] = [callback],
am_callback = function(obj) {
var i;
if (obj == null) {
// Object load failed
for (i in callbackList) {
try {
callbackList[i](null, path);
} catch (err) {
sys.log("AssetManager callback error '" + assetPath + path + "': " + err, "#911");
}
}
} else {
// If the object is an asset with a constructor, run the constructor first.
if (obj.type) {
var assetConstructor = assetConstructors[obj.type];
if (assetConstructor) {
try {
obj = assetConstructor(obj, path);
} catch (err) {
sys.log("AssetManager creation error '" + assetPath + path + "': " + err, "#911");
for (i in callbackList) {
try {
var cb = callbackList[i];
if (cb) {
cb(null, path);
}
} catch (err2) {
sys.log("AssetManager callback error '" + assetPath + path + "': " + err2, "#911");
}
}
delete fetchWaiters[path];
return;
}
}
}
// Add object to list of managed assets and call callbacks
managedAssets[path] = obj;
for (i in callbackList) {
try {
cb = callbackList[i];
if (cb) {
cb(obj, path);
}
} catch (err) {
sys.log("AssetManager callback error '" + assetPath + path + "': " + err, "#911");
}
}
}
delete fetchWaiters[path];
};
// Fetch asset from local storage
if (localStorage) {
asset = localStorage.getItem(path);
if (asset != null) {
try {
am_callback(eval(asset));
return 1;
} catch (err) {
sys.log("AssetManager localStorage eval() error '" + path + "': " + err + "\n" + asset.toString(), "#911");
}
}
}
// Fetch asset from remote host
fetchObject(assetPath + path, am_callback);
return 2;
};
/**
* Retrieve a managed asset.
*
* @param {string} path - The path to the asset.
* @returns {object} - The requested asset, or null if the asset is not loaded.
*/
that.getAsset = function(path) {
path = managedAssets[path];
if (typeof path != 0) {
return path;
} else {
return null;
}
};
/**
* Write an asset to a path.
*
* @param {string} path - The path to the asset.
* @param {object} obj - The asset to write, or null to delete the asset.
*/
that.setAsset = function(path, obj) {
if (obj === null) {
delete managedAssets[path];
} else {
managedAssets[path] = obj;
}
};
};
/**
* Retrieve an asset by it's ID.
*
* This function will retrieve any asset, even if it is not managed by an
* AssetManager. Unlike AssetManager~getAsset, this function addresses
* assets by ID, not their path.
*
* @returns {object} - The requested asset, or null if the asset was not found.
*/
this.getAsset = function(id) {
id = assets[id];
if (typeof id == 0) {
return null;
} else {
return id;
}
};
}();
var M = "/* snip */";
new function() {
function h(a) {
this.data = null;
this.direct = false;
this.size = this.pos = this.id = this.host = null;
0 < arguments.length && (this.data = a.data, this.direct = a.direct, this.host = a.host, this.id = a.id, this.pos = a.pos, this.size = a.size)
}
function g() {
this.skull = this.id = null;
this.bind = function(a, e) {
return null
};
this.connect = function(a, e, c) {
k.log("sc.connect( " + a + ", " + e + ", " + c + " )");
return true
}
}
var k = System,
f = M;
M = "";
k.Protocol = h;
k.Machine = g;
Math.imul || (Math.imul = function(a, e) {
var c = a & 65535,
b = e & 65535;
return c * b + ((a >>> 16 & 65535) * b + c * (e >>> 16 & 65535) << 16 >>> 0) | 0
});
Math.fround || (Math.fround = function() {
var a = new Float32Array(1);
return function(e) {
a[0] = +e;
return a[0]
}
}());
k.Environment = function() {
function a(a) {
if ("number" != typeof a) try {
return a.toString()
} catch (b) {
a = v
}
a |= 0;
return 0 > a || a >= m.length ? "Unknown Error." : m[a]
}
function e(a) {
throw a >>> 0;
}
function c() {
var a;
do a = (+new Date ^ 65536 * Math.random() ^ 65536 * Math.random() << 16) >>> 0 | 0; while (void 0 !== w[a]);
return a
}
function b() {
var a;
do a = (+new Date ^ 65536 * Math.random() ^ 65536 * Math.random() << 16) >>> 0 | 0; while (void 0 !== C[a]);
return a
}
function l(b) {
if ("number" == typeof b) return 0 < b ? (k.log("sc error " + b + ", 0x" + b.toString(16) + ": " + a(b)), b) : b - 1;
k.log("sc error ?: " + a(b));
k.log(b);
return A
}
function n(a) {
k.log("sc.logs: " + a)
}
function d(a) {
k.log("sc.logv: " + (a >>> 0).toString(16) + " : " + a)
}
function p(a) {
a |= 0;
0 > a && (a = 0);
throw -a;
}
function r() {
return Date.now()
}
function u(a) {
this.inherit = g;
this.inherit();
a = (a | 0) >>> 13;
++D;
this.id = c();
a <<= 13;
var b = new ArrayBuffer(a);
new Uint8Array(b);
var l = new Uint32Array(b);
this.connect = function(a, b, d) {
return false
};
this.bind = function(a, b) {
return l.subarray(a >> 2, (a >> 2) + (b >> 2))
};
this.brain = (new Function("std", "foriegn", "heap", f))(window, {
error: e,
imul: Math.imul,
logs: n,
logv: d,
wait: p,
time: r
}, b);
this.size = a;
this.heap = b
}
var v = 6,
A = 7,
m = "OK.;Permission Required.;Out of memory.;Invalid operation.;Invalid memory access.;Invalid access alignment.;Unknown error.;Host environment exception.;Halted.;Operation not supported.".split(";"),
s = void 0;
this.LSB = s;
var D = 0,
w = [],
C = [];
this.explain = a;
this.cycle = function(a, b) {
try {
return w[a].brain.cycle(b | 0)
} catch (d) {
return l(d)
}
};
this.init = function(a, b, d, c, e, f, g) {
try {
w[a].brain.init(w[a].size - 1, b, d, c, e, f, g)
} catch (h) {
return l(h)
}
};
this.load = function(b, d, c) {
try {
if ("string" == typeof d) throw a(9);
var e = w[b].bind(c, d.length << 2);
for (b = 0; b < d.length && b < e.length; ++b) e[b] = d[b];
k.log("Loaded " + b + " words.")
} catch (f) {
return l(f)
}
};
Date.now || (Date.now = function() {
return (new Date).getTime()
});
try {
s = function() {
var a = new ArrayBuffer(4);
(new DataView(a)).setUint32(0, 1718303319, true);
switch ((new Int32Array(a))[0]) {
case 1463446374:
return false;
case 1718303319:
return true;
default:
return null
}
}()
} catch (y) {
k.log("sc: can't test LSB: " + y)
}
this.LSB = s;
this.build = function(a) {
a = new u(a);
if (!a) throw 3;
w[a.id] = a;
return a.id
};
this.dump = function(a) {
return w[a].heap
};
this.support = function(a) {
var d = new h(a);
d.id = b();
C[d.id] = d;
return a.id = d.id
};
this.register = function(a) {
var b = c();
w[b] = a;
return b
};
this.connect = function(a, d, c) {
var e = w[a];
c = w[c];
var f = C[d];
if (!e || !c || !f) throw 3;
try {
if (f = new h(f), f.host = e, f.client = c, f.data = e.bind(f.pos, f.size), f.id = b(), f.direct) try {
C[f.id] = f, c.connect(a, d, f.data)
} catch (g) {
throw delete C[f.id], A;
} else C[f.id] = f
} catch (l) {
throw A;
}
return f.id
}
}
};
new function() {
var h = System,
g = "#000 #00a #0a0 #0aa #a00 #a0a #a50 #aaa #555 #55f #5f5 #5ff #f55 #f5f #ff5 #fff".split(" "),
k = "#000000 #000084 #008400 #008080 #840000 #800080 #804000 #808080 #404040 #4040ff #38ff38 #40ffff #ff4040 #ff40ff #ffff40 #ffffff".split(" "),
f = "#000000 #000099 #009900 #009292 #9b0000 #940094 #964b00 #939393 #4b4b4b #4c4ce0 #39e339 #40e2df #e34b4b #e34ae3 #e0e048 #e2e2e2".split(" ");
h.InputDevice = function(a) {
function e(b) {
c[g] = (b.shiftPressed ? 1 : 0) | (b.ctrlPressed ? 2 : 0) | (b.altPressed ? 4 : 0);
a(-1)
}
this.inherit = h.Phase;
this.inherit();
this.inherit = h.Machine;
this.inherit();
var c = null,
b = false,
f = 1,
g = 2;
this.setScale = function(a) {
f = 0 + a
};
this.mouseMove = function(a) {
c[5] = a.mousePos.x * f;
c[6] = a.mousePos.y * f;
c[9] += a.mouseMov.x * f;
c[10] += a.mouseMov.y * f;
c[0] |= 1;
e(a)
};
this.mousePress = function(a) {
c[7] = a.mouseButton;
c[0] |= 2;
e(a)
};
this.mouseRelease = function(a) {
c[7] = a.mouseButton;
c[0] |= 4;
e(a)
};
this.mouseWheel = function(a) {
c[8] = a.mouseWheel | 0;
c[0] |= 8;
e(a)
};
this.keyPress = function(a) {
c[0] |= 16;
c[3] = a.keyCode;
e(a)
};
this.keyRelease = function(a) {
c[3] = a.keyCode;
c[0] |= 32;
e(a)
};
this.charTyped = function(a) {
c[0] |= 64;
c[4] = a.charCode;
e(a)
};
this.begin = function() {};
this.end = function() {};
this.update = function(b) {
a(b);
return true
};
this.connect = function(a, e, f) {
if (b) return false;
c = f;
return b = true
}
};
h.TextDisplay = function(a, e, c, b, l) {
function n(a, b) {
var c = b.length;
if (null == N) {
N = Array(c);
for (var d = c; 0 <= --d;) N[d] = document.createElement("canvas")
}
for (d = c; 0 <= --d;) canvas = N[d], canvas.width = a.width, canvas.height = a.height, c = canvas.getContext("2d"), c.globalAlpha = 1, c.globalCompositeOperation = "source-over", c.drawImage(a, 0, 0), c.globalCompositeOperation = "source-atop", c.fillStyle = b[d], c.fillRect(0, 0, canvas.width, canvas.height)
}
var d = this;
d.inherit = h.Machine;
d.inherit();
l = l ? true : false;
b |= 0;
e |= 0;
c |= 0;
var p = e * b,
r = c * b;
a.width = p;
a.height = r;
var u = null,
v = 0,
A = 0,
m = a.getContext("2d"),
s = m,
D = 1 < b && l ? k : g;
1 < b && (u = document.createElement("canvas"), u.width = e, u.height = c, s = u.getContext("2d"), u.ctx = s);
s.fillStyle = D[0];
s.fillRect(0, 0, e, c);
var w = Math.floor(e / 9),
C = c >>> 4,
y = C * w,
G = 4,
T = G + (y >> 1) - 1,
y = new ArrayBuffer(y << 2),
R = new Uint32Array(y),
U = false,
q = 0,
H, L, I, J, U = false,
E = 0,
B = null,
Z = false,
F, V, W, Q;
d.connect = function(a, b, c) {
if (Z) return false;
B = c;
V = +new Date;
W = 666;
Q = 0;
F = false;
G = 4;
L = 13;
H = 1;
I = 7;
J = 2;
E = 0;
q = E << 16 | H << 12 | L << 8 | I << 4 | J | 134217728;
l && (q |= 268435456);
B[0] = q;
B[2] = C << 16 | w;
B[1] = 0;
B[3] = G;
return Z = true
};
d.setScanLines = function(a) {
l = a ? true : false;
n(K[1], l ? k : g);
l && 1 < b ? (viewport.parentElement.style.backgroundColor = f[E], D = k) : (viewport.parentElement.style.backgroundColor = g[E], D = g);
s = 1 < b ? u.ctx : m;
for (a = 0; a < R.length; ++a) R[a] = 0
};
d.render = function() {
if (U) {
h.scaleSmoothing(s, false);
q = B[0];
0 == (q & 2147483648) && (B[0] = q |= 2147483648, B[2] = C << 16 | w);
var k = B[1],
n = k >>> 16 & 65535,
k = k & 65535;
if (0 == (q & 134217728)) F && (q |= 536870912), F = false;
else if (W) {
var t = +new Date;
Q += t - V;
Q >= W && (Q = 0, F = !F, q |= 536870912);
V = t
} else F || (q |= 536870912), F = true;
if (0 != (q & 1610612736)) {
q &= -536870913;
t = q >>> 16 & 15;
H = q >>> 12 & 15;
L = q >>> 8 & 15;
I = q >>> 4 & 15;
J = q & 15;
v = (k + n * w >> 1) + G;
0 != (q & 268435456) != l ? (E = t, d.setScanLines(q & 268435456)) : t != E && (E = t, viewport.parentElement.style.backgroundColor = l ? f[E] : g[E]);
s.globalCompositeOperation = "source-over";
s.globalAlpha = 1;
for (var z = t = 0, y = 0, x = 0, S = 0, O = 0, P = 0, K = 0, y = G; y <= T; ++y) {
x = B[y];
if (x == R[K] && y != v && y != A) {
if (++K, t += 9, t >= e && (z += 16, t = 0, z >= c)) break
} else {
R[K++] = x;
O = x >> 8 & 15;
S = x >> 12 & 15;
P = x & 255;
chx = P & 31;
chy = P >> 5 & 7;
s.fillStyle = D[S];
s.fillRect(t, z, 9, 16);
s.drawImage(N[O], 2 + 12 * chx, 2 + 19 * chy, 9, 16, t, z, 9, 16);
t += 9;
if (t >= e && (z += 16, t = 0, z >= c)) break;
x >>>= 16;
O = x >> 8 & 15;
S = x >> 12 & 15;
P = x & 255;
chx = P & 31;
chy = P >> 5 & 7;
s.fillStyle = D[S];
s.fillRect(t, z, 9, 16);
s.drawImage(N[O], 2 + 12 * chx, 2 + 19 * chy, 9, 16, t, z, 9, 16)
}
t += 9;
if (t >= e && (z += 16, t = 0, z >= c)) break
}
A = v;
F && (k < w && n < C) && (x = B[v], k & 1 && (x >>>= 16), O = x >> 8 & 15, 9 > H && (0 < J && 0 < I) && (9 < H + I && (I = 9 - H), 16 < L + J && (J = 16 - L), s.fillStyle = D[O], s.fillRect(9 * k + H, (n << 4) + L, I, J)));
1 < b && (h.scaleSmoothing(m, false), m.globalAlpha = 1, m.globalCompositeOperation = "source-over", m.drawImage(u, 0, 0, e, c, 0, 0, p, r), l && null != X && (m.fillStyle = X, m.fillRect(0, 0, p, r), m.globalCompositeOperation = "lighter", h.scaleSmoothing(m, true), m.globalAlpha = 0.5, m.drawImage(a, 0, 0, p, r, 1.5, 0, p, r), m.drawImage(a, 0, 0, p, r, -0.25, 1, p, r)));
B[0] = q
}
}
};
var K = [],
N = null,
X = null,
Y = new h.ImageLoader("img/", function(a, b, c, d) {
if (b) {
if (h.log("Loaded texture #" + d + " from: " + c), K[d] = a, Y.count == Y.loaded) {
b = document.createElement("canvas");
K[0] = b;
b.width = 64;
b.height = 64;
c = b.getContext("2d");
c.fillStyle = "#000";
c.globalAlpha = 1;
h.scaleSmoothing(c, false);
for (d = 1; 64 > d; d += 2) c.fillRect(0, d, 64, 1);
X = c.createPattern(b, "repeat");
n(a, l ? k : g);
h.log("Display ready.");
U = true
}
} else h.log("Could not load texture #" + d + " from: " + c)
});
Y.queue("sc-font-9x16.png", 1);
viewport.parentElement.style.backgroundColor = l && 1 < b ? f[E] : g[E];
d.toString = function() {
return "TextDisplay"
}
}
};
new function() {
var h = System;
h.onInit(function() {
try {
h.log = function(a) {
console.log(a)
};
var g = new h.Environment,
k = g.build(2097152),
f = new h.Protocol;
f.pos = 0;
f.size = 64;
f.direct = true;
g.support(f);
var a = new h.Protocol;
a.pos = f.size;
a.size = 16384;
a.direct = true;
g.support(a);
var e = document.getElementById("viewport"),
c = true,
b = new h.InputDevice(function(a) {
c || (0 <= a && (cycleDelay = -g.cycle(k, 32768), l.render()), 0 > cycleDelay && (c = true))
}),
l;
1440 <= e.offsetParent.offsetWidth + 4 && 800 <= e.offsetParent.offsetHeight + 4 ? (l = new h.TextDisplay(e, 720, 400, 2, false), b.setScale(1)) : (l = new h.TextDisplay(e, 720, 400, 1, false), b.setScale(2));
window.setScanLines = l.setScanLines;
var n = g.register(l);
g.connect(k, a.id, n);
var d = g.register(b);
g.connect(k, f.id, d);
var p = a.pos + a.size + 1023 & 268434432,
f = [/* snip */];
g.load(k, f, p);
g.init(k, p, p + (f.length << 2), 0, 0, 0, 0);
c = false
} catch (r) {
if ("number" == typeof r) throw g.explain(r);
throw "boot: " + r.toString() + ": line " + r.lineNumber + ": " + r.fileName;
}
h.phase = b
});
h.start({
canvas: "viewport",
realtime: true
})
};