Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
<!DOCTYPE html>
<!-- Taken from https://khanacademy.org/cs/i/5157014494511104, from pamela, from DillingerLee -->
<html>
<head>
<title>Processing.JS inside Webpages: Template</title>
</head>
<body>
<p align="center">
<!--This draws the Canvas on the webpage -->
<canvas id="mycanvas"></canvas>
</p>
</body>
<!-- Run all the JavaScript stuff -->
<!-- Include the processing.js library -->
<script src="processing.js"></script>
<script>
var sketchProc = function(processingInstance) {
with (processingInstance) {
size(400, 400);
frameRate(30);
// Program Code Goes Here
fill(255, 255, 0);
ellipse(200, 200, 200, 200);
noFill();
stroke(0, 0, 0);
strokeWeight(2);
arc(200, 200, 150, 100, 0, PI);
fill(0, 0, 0);
ellipse(250, 200, 10, 10);
ellipse(153, 200, 10, 10);
}};
// Get the canvas that Processing-js will use
var canvas = document.getElementById("mycanvas");
// Pass the function sketchProc (defined in myCode.js) to Processing's constructor.
var processingInstance = new Processing(canvas, sketchProc);
</script>
</html>
// From https://github.com/Khan/processing-js/blob/66bec3a3ae88262fcb8e420f7fa581b46f91a052/processing.js
(function(window, document, Math, undef) {
var nop = function(){};
var debug = (function() {
if ("console" in window) {
return function(msg) {
window.console.log('Processing.js: ' + msg);
};
}
return nop();
}());
var ajax = function(url) {
var xhr = new XMLHttpRequest();
xhr.open("GET", url, false);
if (xhr.overrideMimeType) {
xhr.overrideMimeType("text/plain");
}
xhr.setRequestHeader("If-Modified-Since", "Fri, 01 Jan 1960 00:00:00 GMT");
xhr.send(null);
// failed request?
if (xhr.status !== 200 && xhr.status !== 0) { throw ("XMLHttpRequest failed, status code " + xhr.status); }
return xhr.responseText;
};
var isDOMPresent = ("document" in this) && !("fake" in this.document);
// document.head polyfill for the benefit of Firefox 3.6
document.head = document.head || document.getElementsByTagName('head')[0];
// Typed Arrays: fallback to WebGL arrays or Native JS arrays if unavailable
function setupTypedArray(name, fallback) {
// Check if TypedArray exists, and use if so.
if (name in window) {
return window[name];
}
// Check if WebGLArray exists
if (typeof window[fallback] === "function") {
return window[fallback];
}
// Use Native JS array
return function(obj) {
if (obj instanceof Array) {
return obj;
}
if (typeof obj === "number") {
var arr = [];
arr.length = obj;
return arr;
}
};
}
var Float32Array = setupTypedArray("Float32Array", "WebGLFloatArray"),
Int32Array = setupTypedArray("Int32Array", "WebGLIntArray"),
Uint16Array = setupTypedArray("Uint16Array", "WebGLUnsignedShortArray"),
Uint8Array = setupTypedArray("Uint8Array", "WebGLUnsignedByteArray");
/* Browsers fixes end */
/**
* NOTE: in releases we replace symbolic PConstants.* names with their values.
* Using PConstants.* in code below is fine. See tools/rewrite-pconstants.js.
*/
var PConstants = {
// NOTE(jeresig): Disable some constants as they were confusing users.
//X: 0,
//Y: 1,
//Z: 2,
//R: 3,
//G: 4,
//B: 5,
//A: 6,
//U: 7,
//V: 8,
NX: 9,
NY: 10,
NZ: 11,
EDGE: 12,
// Stroke
SR: 13,
SG: 14,
SB: 15,
SA: 16,
SW: 17,
// Transformations (2D and 3D)
TX: 18,
TY: 19,
TZ: 20,
VX: 21,
VY: 22,
VZ: 23,
VW: 24,
// Material properties
AR: 25,
AG: 26,
AB: 27,
DR: 3,
DG: 4,
DB: 5,
DA: 6,
SPR: 28,
SPG: 29,
SPB: 30,
SHINE: 31,
ER: 32,
EG: 33,
EB: 34,
BEEN_LIT: 35,
VERTEX_FIELD_COUNT: 36,
// Renderers
P2D: 1,
JAVA2D: 1,
WEBGL: 2,
P3D: 2,
OPENGL: 2,
PDF: 0,
DXF: 0,
// Platform IDs
OTHER: 0,
WINDOWS: 1,
MAXOSX: 2,
LINUX: 3,
EPSILON: 0.0001,
MAX_FLOAT: 3.4028235e+38,
MIN_FLOAT: -3.4028235e+38,
MAX_INT: 2147483647,
MIN_INT: -2147483648,
PI: Math.PI,
TWO_PI: 2 * Math.PI,
HALF_PI: Math.PI / 2,
THIRD_PI: Math.PI / 3,
QUARTER_PI: Math.PI / 4,
TAU: 2 * Math.PI,
DEG_TO_RAD: Math.PI / 180,
RAD_TO_DEG: 180 / Math.PI,
WHITESPACE: " \t\n\r\f\u00A0",
// Color modes
RGB: 1,
ARGB: 2,
HSB: 3,
ALPHA: 4,
CMYK: 5,
// Image file types
TIFF: 0,
TARGA: 1,
JPEG: 2,
GIF: 3,
// Filter/convert types
BLUR: 11,
GRAY: 12,
INVERT: 13,
OPAQUE: 14,
POSTERIZE: 15,
THRESHOLD: 16,
ERODE: 17,
DILATE: 18,
// Blend modes
REPLACE: 0,
BLEND: 1 << 0,
ADD: 1 << 1,
SUBTRACT: 1 << 2,
LIGHTEST: 1 << 3,
DARKEST: 1 << 4,
DIFFERENCE: 1 << 5,
EXCLUSION: 1 << 6,
MULTIPLY: 1 << 7,
SCREEN: 1 << 8,
OVERLAY: 1 << 9,
HARD_LIGHT: 1 << 10,
SOFT_LIGHT: 1 << 11,
DODGE: 1 << 12,
BURN: 1 << 13,
// Color component bit masks
ALPHA_MASK: 0xff000000,
RED_MASK: 0x00ff0000,
GREEN_MASK: 0x0000ff00,
BLUE_MASK: 0x000000ff,
// Projection matrices
CUSTOM: 0,
ORTHOGRAPHIC: 2,
PERSPECTIVE: 3,
// Shapes
POINT: 2,
POINTS: 2,
LINE: 4,
LINES: 4,
TRIANGLE: 8,
TRIANGLES: 9,
TRIANGLE_STRIP: 10,
TRIANGLE_FAN: 11,
QUAD: 16,
QUADS: 16,
QUAD_STRIP: 17,
POLYGON: 20,
PATH: 21,
RECT: 30,
ELLIPSE: 31,
ARC: 32,
SPHERE: 40,
BOX: 41,
GROUP: 0,
PRIMITIVE: 1,
//PATH: 21, // shared with Shape PATH
GEOMETRY: 3,
// Shape Vertex
VERTEX: 0,
BEZIER_VERTEX: 1,
CURVE_VERTEX: 2,
BREAK: 3,
CLOSESHAPE: 4,
// Shape closing modes
OPEN: 1,
CLOSE: 2,
// Shape drawing modes
CORNER: 0, // Draw mode convention to use (x, y) to (width, height)
CORNERS: 1, // Draw mode convention to use (x1, y1) to (x2, y2) coordinates
RADIUS: 2, // Draw mode from the center, and using the radius
CENTER_RADIUS: 2, // Deprecated! Use RADIUS instead
CENTER: 3, // Draw from the center, using second pair of values as the diameter
DIAMETER: 3, // Synonym for the CENTER constant. Draw from the center
CENTER_DIAMETER: 3, // Deprecated! Use DIAMETER instead
// Text vertical alignment modes
BASELINE: 0, // Default vertical alignment for text placement
TOP: 101, // Align text to the top
BOTTOM: 102, // Align text from the bottom, using the baseline
// UV Texture coordinate modes
NORMAL: 1,
NORMALIZED: 1,
IMAGE: 2,
// Text placement modes
MODEL: 4,
SHAPE: 5,
// Stroke modes
SQUARE: 'butt',
ROUND: 'round',
PROJECT: 'square',
MITER: 'miter',
BEVEL: 'bevel',
// Lighting modes
AMBIENT: 0,
DIRECTIONAL: 1,
//POINT: 2, Shared with Shape constant
SPOT: 3,
// Key constants
// Both key and keyCode will be equal to these values
BACKSPACE: 8,
TAB: 9,
ENTER: 10,
RETURN: 13,
ESC: 27,
DELETE: 127,
CODED: 0xffff,
// p.key will be CODED and p.keyCode will be this value
SHIFT: 16,
CONTROL: 17,
ALT: 18,
CAPSLK: 20,
PGUP: 33,
PGDN: 34,
END: 35,
HOME: 36,
LEFT: 37,
UP: 38,
RIGHT: 39,
DOWN: 40,
F1: 112,
F2: 113,
F3: 114,
F4: 115,
F5: 116,
F6: 117,
F7: 118,
F8: 119,
F9: 120,
F10: 121,
F11: 122,
F12: 123,
NUMLK: 144,
META: 157,
INSERT: 155,
// Cursor types
ARROW: 'default',
CROSS: 'crosshair',
HAND: 'pointer',
MOVE: 'move',
TEXT: 'text',
WAIT: 'wait',
NOCURSOR: "url('data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=='), auto",
// Hints
DISABLE_OPENGL_2X_SMOOTH: 1,
ENABLE_OPENGL_2X_SMOOTH: -1,
ENABLE_OPENGL_4X_SMOOTH: 2,
ENABLE_NATIVE_FONTS: 3,
DISABLE_DEPTH_TEST: 4,
ENABLE_DEPTH_TEST: -4,
ENABLE_DEPTH_SORT: 5,
DISABLE_DEPTH_SORT: -5,
DISABLE_OPENGL_ERROR_REPORT: 6,
ENABLE_OPENGL_ERROR_REPORT: -6,
ENABLE_ACCURATE_TEXTURES: 7,
DISABLE_ACCURATE_TEXTURES: -7,
HINT_COUNT: 10,
// PJS defined constants
SINCOS_LENGTH: 720, // every half degree
PRECISIONB: 15, // fixed point precision is limited to 15 bits!!
PRECISIONF: 1 << 15,
PREC_MAXVAL: (1 << 15) - 1,
PREC_ALPHA_SHIFT: 24 - 15,
PREC_RED_SHIFT: 16 - 15,
NORMAL_MODE_AUTO: 0,
NORMAL_MODE_SHAPE: 1,
NORMAL_MODE_VERTEX: 2,
MAX_LIGHTS: 8
};
/**
* Returns Java hashCode() result for the object. If the object has the "hashCode" function,
* it preforms the call of this function. Otherwise it uses/creates the "$id" property,
* which is used as the hashCode.
*
* @param {Object} obj The object.
* @returns {int} The object's hash code.
*/
function virtHashCode(obj) {
if (typeof(obj) === "string") {
var hash = 0;
for (var i = 0; i < obj.length; ++i) {
hash = (hash * 31 + obj.charCodeAt(i)) & 0xFFFFFFFF;
}
return hash;
}
if (typeof(obj) !== "object") {
return obj & 0xFFFFFFFF;
}
if (obj.hashCode instanceof Function) {
return obj.hashCode();
}
if (obj.$id === undef) {
obj.$id = ((Math.floor(Math.random() * 0x10000) - 0x8000) << 16) | Math.floor(Math.random() * 0x10000);
}
return obj.$id;
}
/**
* Returns Java equals() result for two objects. If the first object
* has the "equals" function, it preforms the call of this function.
* Otherwise the method uses the JavaScript === operator.
*
* @param {Object} obj The first object.
* @param {Object} other The second object.
*
* @returns {boolean} true if the objects are equal.
*/
function virtEquals(obj, other) {
if (obj === null || other === null) {
return (obj === null) && (other === null);
}
if (typeof (obj) === "string") {
return obj === other;
}
if (typeof(obj) !== "object") {
return obj === other;
}
if (obj.equals instanceof Function) {
return obj.equals(other);
}
return obj === other;
}
/**
* A ObjectIterator is an iterator wrapper for objects. If passed object contains
* the iterator method, the object instance will be replaced by the result returned by
* this method call. If passed object is an array, the ObjectIterator instance iterates
* through its items.
*
* @param {Object} obj The object to be iterated.
*/
var ObjectIterator = function(obj) {
if (obj.iterator instanceof Function) {
return obj.iterator();
}
if (obj instanceof Array) {
// iterate through array items
var index = -1;
this.hasNext = function() {
return ++index < obj.length;
};
this.next = function() {
return obj[index];
};
} else {
throw "Unable to iterate: " + obj;
}
};
/**
* An ArrayList stores a variable number of objects.
*
* @param {int} initialCapacity optional defines the initial capacity of the list, it's empty by default
*
* @returns {ArrayList} new ArrayList object
*/
var ArrayList = (function() {
function Iterator(array) {
var index = 0;
this.hasNext = function() {
return index < array.length;
};
this.next = function() {
return array[index++];
};
this.remove = function() {
array.splice(index, 1);
};
}
function ArrayList() {
var array;
if (arguments.length === 0) {
array = [];
} else if (arguments.length > 0 && typeof arguments[0] !== 'number') {
array = arguments[0].toArray();
} else {
array = [];
array.length = 0 | arguments[0];
}
/**
* @member ArrayList
* ArrayList.get() Returns the element at the specified position in this list.
*
* @param {int} i index of element to return
*
* @returns {Object} the element at the specified position in this list.
*/
this.get = function(i) {
return array[i];
};
/**
* @member ArrayList
* ArrayList.contains() Returns true if this list contains the specified element.
*
* @param {Object} item element whose presence in this List is to be tested.
*
* @returns {boolean} true if the specified element is present; false otherwise.
*/
this.contains = function(item) {
return this.indexOf(item)>-1;
};
/**
* @member ArrayList
* ArrayList.indexOf() Returns the position this element takes in the list, or -1 if the element is not found.
*
* @param {Object} item element whose position in this List is to be tested.
*
* @returns {int} the list position that the first match for this element holds in the list, or -1 if it is not in the list.
*/
this.indexOf = function(item) {
for (var i = 0, len = array.length; i < len; ++i) {
if (virtEquals(item, array[i])) {
return i;
}
}
return -1;
};
/**
* @member ArrayList
* ArrayList.add() Adds the specified element to this list.
*
* @param {int} index optional index at which the specified element is to be inserted
* @param {Object} object element to be added to the list
*/
this.add = function() {
if (arguments.length === 1) {
array.push(arguments[0]); // for add(Object)
} else if (arguments.length === 2) {
var arg0 = arguments[0];
if (typeof arg0 === 'number') {
if (arg0 >= 0 && arg0 <= array.length) {
array.splice(arg0, 0, arguments[1]); // for add(i, Object)
} else {
throw(arg0 + " is not a valid index");
}
} else {
throw(typeof arg0 + " is not a number");
}
} else {
throw("Please use the proper number of parameters.");
}
};
/**
* @member ArrayList
* ArrayList.addAll(collection) appends all of the elements in the specified
* Collection to the end of this list, in the order that they are returned by
* the specified Collection's Iterator.
*
* When called as addAll(index, collection) the elements are inserted into
* this list at the position indicated by index.
*
* @param {index} Optional; specifies the position the colletion should be inserted at
* @param {collection} Any iterable object (ArrayList, HashMap.keySet(), etc.)
* @throws out of bounds error for negative index, or index greater than list size.
*/
this.addAll = function(arg1, arg2) {
// addAll(int, Collection)
var it;
if (typeof arg1 === "number") {
if (arg1 < 0 || arg1 > array.length) {
throw("Index out of bounds for addAll: " + arg1 + " greater or equal than " + array.length);
}
it = new ObjectIterator(arg2);
while (it.hasNext()) {
array.splice(arg1++, 0, it.next());
}
}
// addAll(Collection)
else {
it = new ObjectIterator(arg1);
while (it.hasNext()) {
array.push(it.next());
}
}
};
/**
* @member ArrayList
* ArrayList.set() Replaces the element at the specified position in this list with the specified element.
*
* @param {int} index index of element to replace
* @param {Object} object element to be stored at the specified position
*/
this.set = function() {
if (arguments.length === 2) {
var arg0 = arguments[0];
if (typeof arg0 === 'number') {
if (arg0 >= 0 && arg0 < array.length) {
array.splice(arg0, 1, arguments[1]);
} else {
throw(arg0 + " is not a valid index.");
}
} else {
throw(typeof arg0 + " is not a number");
}
} else {
throw("Please use the proper number of parameters.");
}
};
/**
* @member ArrayList
* ArrayList.size() Returns the number of elements in this list.
*
* @returns {int} the number of elements in this list
*/
this.size = function() {
return array.length;
};
/**
* @member ArrayList
* ArrayList.clear() Removes all of the elements from this list. The list will be empty after this call returns.
*/
this.clear = function() {
array.length = 0;
};
/**
* @member ArrayList
* ArrayList.remove() Removes an element either based on index, if the argument is a number, or
* by equality check, if the argument is an object.
*
* @param {int|Object} item either the index of the element to be removed, or the element itself.
*
* @returns {Object|boolean} If removal is by index, the element that was removed, or null if nothing was removed. If removal is by object, true if removal occurred, otherwise false.
*/
this.remove = function(item) {
if (typeof item === 'number') {
return array.splice(item, 1)[0];
}
item = this.indexOf(item);
if (item > -1) {
array.splice(item, 1);
return true;
}
return false;
};
/**
* @member ArrayList
* ArrayList.isEmpty() Tests if this list has no elements.
*
* @returns {boolean} true if this list has no elements; false otherwise
*/
this.isEmpty = function() {
return !array.length;
};
/**
* @member ArrayList
* ArrayList.clone() Returns a shallow copy of this ArrayList instance. (The elements themselves are not copied.)
*
* @returns {ArrayList} a clone of this ArrayList instance
*/
this.clone = function() {
return new ArrayList(this);
};
/**
* @member ArrayList
* ArrayList.toArray() Returns an array containing all of the elements in this list in the correct order.
*
* @returns {Object[]} Returns an array containing all of the elements in this list in the correct order
*/
this.toArray = function() {
return array.slice(0);
};
this.iterator = function() {
return new Iterator(array);
};
}
return ArrayList;
}());
/**
* A HashMap stores a collection of objects, each referenced by a key. This is similar to an Array, only
* instead of accessing elements with a numeric index, a String is used. (If you are familiar with
* associative arrays from other languages, this is the same idea.)
*
* @param {int} initialCapacity defines the initial capacity of the map, it's 16 by default
* @param {float} loadFactor the load factor for the map, the default is 0.75
* @param {Map} m gives the new HashMap the same mappings as this Map
*/
var HashMap = (function() {
/**
* @member HashMap
* A HashMap stores a collection of objects, each referenced by a key. This is similar to an Array, only
* instead of accessing elements with a numeric index, a String is used. (If you are familiar with
* associative arrays from other languages, this is the same idea.)
*
* @param {int} initialCapacity defines the initial capacity of the map, it's 16 by default
* @param {float} loadFactor the load factor for the map, the default is 0.75
* @param {Map} m gives the new HashMap the same mappings as this Map
*/
function HashMap() {
if (arguments.length === 1 && arguments[0] instanceof HashMap) {
return arguments[0].clone();
}
var initialCapacity = arguments.length > 0 ? arguments[0] : 16;
var loadFactor = arguments.length > 1 ? arguments[1] : 0.75;
var buckets = [];
buckets.length = initialCapacity;
var count = 0;
var hashMap = this;
function getBucketIndex(key) {
var index = virtHashCode(key) % buckets.length;
return index < 0 ? buckets.length + index : index;
}
function ensureLoad() {
if (count <= loadFactor * buckets.length) {
return;
}
var allEntries = [];
for (var i = 0; i < buckets.length; ++i) {
if (buckets[i] !== undef) {
allEntries = allEntries.concat(buckets[i]);
}
}
var newBucketsLength = buckets.length * 2;
buckets = [];
buckets.length = newBucketsLength;
for (var j = 0; j < allEntries.length; ++j) {
var index = getBucketIndex(allEntries[j].key);
var bucket = buckets[index];
if (bucket === undef) {
buckets[index] = bucket = [];
}
bucket.push(allEntries[j]);
}
}
function Iterator(conversion, removeItem) {
var bucketIndex = 0;
var itemIndex = -1;
var endOfBuckets = false;
function findNext() {
while (!endOfBuckets) {
++itemIndex;
if (bucketIndex >= buckets.length) {
endOfBuckets = true;
} else if (buckets[bucketIndex] === undef || itemIndex >= buckets[bucketIndex].length) {
itemIndex = -1;
++bucketIndex;
} else {
return;
}
}
}
/*
* @member Iterator
* Checks if the Iterator has more items
*/
this.hasNext = function() {
return !endOfBuckets;
};
/*
* @member Iterator
* Return the next Item
*/
this.next = function() {
var result = conversion(buckets[bucketIndex][itemIndex]);
findNext();
return result;
};
/*
* @member Iterator
* Remove the current item
*/
this.remove = function() {
removeItem(this.next());
--itemIndex;
};
findNext();
}
function Set(conversion, isIn, removeItem) {
this.clear = function() {
hashMap.clear();
};
this.contains = function(o) {
return isIn(o);
};
this.containsAll = function(o) {
var it = o.iterator();
while (it.hasNext()) {
if (!this.contains(it.next())) {
return false;
}
}
return true;
};
this.isEmpty = function() {
return hashMap.isEmpty();
};
this.iterator = function() {
return new Iterator(conversion, removeItem);
};
this.remove = function(o) {
if (this.contains(o)) {
removeItem(o);
return true;
}
return false;
};
this.removeAll = function(c) {
var it = c.iterator();
var changed = false;
while (it.hasNext()) {
var item = it.next();
if (this.contains(item)) {
removeItem(item);
changed = true;
}
}
return true;
};
this.retainAll = function(c) {
var it = this.iterator();
var toRemove = [];
while (it.hasNext()) {
var entry = it.next();
if (!c.contains(entry)) {
toRemove.push(entry);
}
}
for (var i = 0; i < toRemove.length; ++i) {
removeItem(toRemove[i]);
}
return toRemove.length > 0;
};
this.size = function() {
return hashMap.size();
};
this.toArray = function() {
var result = [];
var it = this.iterator();
while (it.hasNext()) {
result.push(it.next());
}
return result;
};
}
function Entry(pair) {
this._isIn = function(map) {
return map === hashMap && (pair.removed === undef);
};
this.equals = function(o) {
return virtEquals(pair.key, o.getKey());
};
this.getKey = function() {
return pair.key;
};
this.getValue = function() {
return pair.value;
};
this.hashCode = function(o) {
return virtHashCode(pair.key);
};
this.setValue = function(value) {
var old = pair.value;
pair.value = value;
return old;
};
}
this.clear = function() {
count = 0;
buckets = [];
buckets.length = initialCapacity;
};
this.clone = function() {
var map = new HashMap();
map.putAll(this);
return map;
};
this.containsKey = function(key) {
var index = getBucketIndex(key);
var bucket = buckets[index];
if (bucket === undef) {
return false;
}
for (var i = 0; i < bucket.length; ++i) {
if (virtEquals(bucket[i].key, key)) {
return true;
}
}
return false;
};
this.containsValue = function(value) {
for (var i = 0; i < buckets.length; ++i) {
var bucket = buckets[i];
if (bucket === undef) {
continue;
}
for (var j = 0; j < bucket.length; ++j) {
if (virtEquals(bucket[j].value, value)) {
return true;
}
}
}
return false;
};
this.entrySet = function() {
return new Set(
function(pair) {
return new Entry(pair);
},
function(pair) {
return (pair instanceof Entry) && pair._isIn(hashMap);
},
function(pair) {
return hashMap.remove(pair.getKey());
});
};
this.get = function(key) {
var index = getBucketIndex(key);
var bucket = buckets[index];
if (bucket === undef) {
return null;
}
for (var i = 0; i < bucket.length; ++i) {
if (virtEquals(bucket[i].key, key)) {
return bucket[i].value;
}
}
return null;
};
this.isEmpty = function() {
return count === 0;
};
this.keySet = function() {
return new Set(
// get key from pair
function(pair) {
return pair.key;
},
// is-in test
function(key) {
return hashMap.containsKey(key);
},
// remove from hashmap by key
function(key) {
return hashMap.remove(key);
}
);
};
this.values = function() {
return new Set(
// get value from pair
function(pair) {
return pair.value;
},
// is-in test
function(value) {
return hashMap.containsValue(value);
},
// remove from hashmap by value
function(value) {
return hashMap.removeByValue(value);
}
);
};
this.put = function(key, value) {
var index = getBucketIndex(key);
var bucket = buckets[index];
if (bucket === undef) {
++count;
buckets[index] = [{
key: key,
value: value
}];
ensureLoad();
return null;
}
for (var i = 0; i < bucket.length; ++i) {
if (virtEquals(bucket[i].key, key)) {
var previous = bucket[i].value;
bucket[i].value = value;
return previous;
}
}
++count;
bucket.push({
key: key,
value: value
});
ensureLoad();
return null;
};
this.putAll = function(m) {
var it = m.entrySet().iterator();
while (it.hasNext()) {
var entry = it.next();
this.put(entry.getKey(), entry.getValue());
}
};
this.remove = function(key) {
var index = getBucketIndex(key);
var bucket = buckets[index];
if (bucket === undef) {
return null;
}
for (var i = 0; i < bucket.length; ++i) {
if (virtEquals(bucket[i].key, key)) {
--count;
var previous = bucket[i].value;
bucket[i].removed = true;
if (bucket.length > 1) {
bucket.splice(i, 1);
} else {
buckets[index] = undef;
}
return previous;
}
}
return null;
};
this.removeByValue = function(value) {
var bucket, i, ilen, pair;
for (bucket in buckets) {
if (buckets.hasOwnProperty(bucket)) {
for (i = 0, ilen = buckets[bucket].length; i < ilen; i++) {
pair = buckets[bucket][i];
// removal on values is based on identity, not equality
if (pair.value === value) {
buckets[bucket].splice(i, 1);
return true;
}
}
}
}
return false;
};
this.size = function() {
return count;
};
}
return HashMap;
}());
// Building defaultScope. Changing of the prototype protects
// internal Processing code from the changes in defaultScope
function DefaultScope() {}
DefaultScope.prototype = PConstants;
var defaultScope = new DefaultScope();
defaultScope.ArrayList = ArrayList;
defaultScope.HashMap = HashMap;
defaultScope.ObjectIterator = ObjectIterator;
defaultScope.PConstants = PConstants;
//defaultScope.PImage = PImage; // TODO
//defaultScope.PShape = PShape; // TODO
//defaultScope.PShapeSVG = PShapeSVG; // TODO
////////////////////////////////////////////////////////////////////////////
// Class inheritance helper methods
////////////////////////////////////////////////////////////////////////////
defaultScope.defineProperty = function(obj, name, desc) {
if("defineProperty" in Object) {
Object.defineProperty(obj, name, desc);
} else {
if (desc.hasOwnProperty("get")) {
obj.__defineGetter__(name, desc.get);
}
if (desc.hasOwnProperty("set")) {
obj.__defineSetter__(name, desc.set);
}
}
};
function extendClass(subClass, baseClass) {
function extendGetterSetter(propertyName) {
defaultScope.defineProperty(subClass, propertyName, {
get: function() {
return baseClass[propertyName];
},
set: function(v) {
baseClass[propertyName]=v;
},
enumerable: true
});
}
var properties = [];
for (var propertyName in baseClass) {
if (typeof baseClass[propertyName] === 'function') {
// Overriding all non-overriden functions
if (!subClass.hasOwnProperty(propertyName)) {
subClass[propertyName] = baseClass[propertyName];
}
} else if(propertyName.charAt(0) !== "$" && !(propertyName in subClass)) {
// Delaying the properties extension due to the IE9 bug (see #918).
properties.push(propertyName);
}
}
while (properties.length > 0) {
extendGetterSetter(properties.shift());
}
}
defaultScope.extendClassChain = function(base) {
var path = [base];
for (var self = base.$upcast; self; self = self.$upcast) {
extendClass(self, base);
path.push(self);
base = self;
}
while (path.length > 0) {
path.pop().$self=base;
}
};
defaultScope.extendStaticMembers = function(derived, base) {
extendClass(derived, base);
};
defaultScope.extendInterfaceMembers = function(derived, base) {
extendClass(derived, base);
};
defaultScope.addMethod = function(object, name, fn, superAccessor) {
if (object[name]) {
var args = fn.length,
oldfn = object[name];
object[name] = function() {
if (arguments.length === args) {
return fn.apply(this, arguments);
}
return oldfn.apply(this, arguments);
};
} else {
object[name] = fn;
}
};
defaultScope.createJavaArray = function(type, bounds) {
var result = null;
if (typeof bounds[0] === 'number') {
var itemsCount = 0 | bounds[0];
if (bounds.length <= 1) {
result = [];
result.length = itemsCount;
for (var i = 0; i < itemsCount; ++i) {
result[i] = 0;
}
} else {
result = [];
var newBounds = bounds.slice(1);
for (var j = 0; j < itemsCount; ++j) {
result.push(defaultScope.createJavaArray(type, newBounds));
}
}
}
return result;
};
var colors = {
aliceblue: "#f0f8ff",
antiquewhite: "#faebd7",
aqua: "#00ffff",
aquamarine: "#7fffd4",
azure: "#f0ffff",
beige: "#f5f5dc",
bisque: "#ffe4c4",
black: "#000000",
blanchedalmond: "#ffebcd",
blue: "#0000ff",
blueviolet: "#8a2be2",
brown: "#a52a2a",
burlywood: "#deb887",
cadetblue: "#5f9ea0",
chartreuse: "#7fff00",
chocolate: "#d2691e",
coral: "#ff7f50",
cornflowerblue: "#6495ed",
cornsilk: "#fff8dc",
crimson: "#dc143c",
cyan: "#00ffff",
darkblue: "#00008b",
darkcyan: "#008b8b",
darkgoldenrod: "#b8860b",
darkgray: "#a9a9a9",
darkgreen: "#006400",
darkkhaki: "#bdb76b",
darkmagenta: "#8b008b",
darkolivegreen: "#556b2f",
darkorange: "#ff8c00",
darkorchid: "#9932cc",
darkred: "#8b0000",
darksalmon: "#e9967a",
darkseagreen: "#8fbc8f",
darkslateblue: "#483d8b",
darkslategray: "#2f4f4f",
darkturquoise: "#00ced1",
darkviolet: "#9400d3",
deeppink: "#ff1493",
deepskyblue: "#00bfff",
dimgray: "#696969",
dodgerblue: "#1e90ff",
firebrick: "#b22222",
floralwhite: "#fffaf0",
forestgreen: "#228b22",
fuchsia: "#ff00ff",
gainsboro: "#dcdcdc",
ghostwhite: "#f8f8ff",
gold: "#ffd700",
goldenrod: "#daa520",
gray: "#808080",
green: "#008000",
greenyellow: "#adff2f",
honeydew: "#f0fff0",
hotpink: "#ff69b4",
indianred: "#cd5c5c",
indigo: "#4b0082",
ivory: "#fffff0",
khaki: "#f0e68c",
lavender: "#e6e6fa",
lavenderblush: "#fff0f5",
lawngreen: "#7cfc00",
lemonchiffon: "#fffacd",
lightblue: "#add8e6",
lightcoral: "#f08080",
lightcyan: "#e0ffff",
lightgoldenrodyellow: "#fafad2",
lightgrey: "#d3d3d3",
lightgreen: "#90ee90",
lightpink: "#ffb6c1",
lightsalmon: "#ffa07a",
lightseagreen: "#20b2aa",
lightskyblue: "#87cefa",
lightslategray: "#778899",
lightsteelblue: "#b0c4de",
lightyellow: "#ffffe0",
lime: "#00ff00",
limegreen: "#32cd32",
linen: "#faf0e6",
magenta: "#ff00ff",
maroon: "#800000",
mediumaquamarine: "#66cdaa",
mediumblue: "#0000cd",
mediumorchid: "#ba55d3",
mediumpurple: "#9370d8",
mediumseagreen: "#3cb371",
mediumslateblue: "#7b68ee",
mediumspringgreen: "#00fa9a",
mediumturquoise: "#48d1cc",
mediumvioletred: "#c71585",
midnightblue: "#191970",
mintcream: "#f5fffa",
mistyrose: "#ffe4e1",
moccasin: "#ffe4b5",
navajowhite: "#ffdead",
navy: "#000080",
oldlace: "#fdf5e6",
olive: "#808000",
olivedrab: "#6b8e23",
orange: "#ffa500",
orangered: "#ff4500",
orchid: "#da70d6",
palegoldenrod: "#eee8aa",
palegreen: "#98fb98",
paleturquoise: "#afeeee",
palevioletred: "#d87093",
papayawhip: "#ffefd5",
peachpuff: "#ffdab9",
peru: "#cd853f",
pink: "#ffc0cb",
plum: "#dda0dd",
powderblue: "#b0e0e6",
purple: "#800080",
red: "#ff0000",
rosybrown: "#bc8f8f",
royalblue: "#4169e1",
saddlebrown: "#8b4513",
salmon: "#fa8072",
sandybrown: "#f4a460",
seagreen: "#2e8b57",
seashell: "#fff5ee",
sienna: "#a0522d",
silver: "#c0c0c0",
skyblue: "#87ceeb",
slateblue: "#6a5acd",
slategray: "#708090",
snow: "#fffafa",
springgreen: "#00ff7f",
steelblue: "#4682b4",
tan: "#d2b48c",
teal: "#008080",
thistle: "#d8bfd8",
tomato: "#ff6347",
turquoise: "#40e0d0",
violet: "#ee82ee",
wheat: "#f5deb3",
white: "#ffffff",
whitesmoke: "#f5f5f5",
yellow: "#ffff00",
yellowgreen: "#9acd32"
};
// Unsupported Processing File and I/O operations.
(function(Processing) {
var unsupportedP5 = ("open() createOutput() createInput() BufferedReader selectFolder() " +
"dataPath() createWriter() selectOutput() beginRecord() " +
"saveStream() endRecord() selectInput() saveBytes() createReader() " +
"beginRaw() endRaw() PrintWriter delay()").split(" "),
count = unsupportedP5.length,
prettyName,
p5Name;
function createUnsupportedFunc(n) {
return function() {
throw "Processing.js does not support " + n + ".";
};
}
while (count--) {
prettyName = unsupportedP5[count];
p5Name = prettyName.replace("()", "");
Processing[p5Name] = createUnsupportedFunc(prettyName);
}
}(defaultScope));
// screenWidth and screenHeight are shared by all instances.
// and return the width/height of the browser's viewport.
defaultScope.defineProperty(defaultScope, 'screenWidth',
{ get: function() { return window.innerWidth; } });
defaultScope.defineProperty(defaultScope, 'screenHeight',
{ get: function() { return window.innerHeight; } });
// Manage multiple Processing instances
var processingInstances = [];
var processingInstanceIds = {};
var removeInstance = function(id) {
processingInstances.splice(processingInstanceIds[id], 1);
delete processingInstanceIds[id];
};
var addInstance = function(processing) {
if (processing.externals.canvas.id === undef || !processing.externals.canvas.id.length) {
processing.externals.canvas.id = "__processing" + processingInstances.length;
}
processingInstanceIds[processing.externals.canvas.id] = processingInstances.length;
processingInstances.push(processing);
};
////////////////////////////////////////////////////////////////////////////
// LRUCache.JS START
////////////////////////////////////////////////////////////////////////////
/**
* This is a Least Recently Used Cache
*
* When the max size is reached, then the least recently used item is dropped.
*
* This is tracked by having a "use index", which is a number indicating how
* recently a given item was accessed. The closer the "use index" is to
* "mostRecent", the more recently is was used.
*
* When an item is accessed (via .get()) it's "use index" gets updated to be
* the new "most recent".
*/
function LRUCache(maxSize) {
this.maxSize = maxSize;
this.size = 0;
this.cache = {}; // key => val
this.useIndex = {}; // use index => key
this.useReverse = {}; // key => use index
// this will be incremented to 0 for the first item added, making
// leastRecent === mostRecent
this.mostRecent = -1;
this.leastRecent = 0;
}
/**
* Get a value from the cache, returning undefined for an unknown key
*/
LRUCache.prototype.get = function(key) {
key = key + '';
if (!this.cache[key]) {
return;
}
this._makeMostRecent(key);
return this.cache[key];
};
/**
* Set a value in the cache. If the max size is reached, the least recently
* used item will be popped off.
*/
LRUCache.prototype.set = function(key, val) {
key = key + '';
if (!this.cache[key]) {
this.size += 1;
}
this.cache[key] = val;
this._makeMostRecent(key);
if (this.size > this.maxSize) {
this._pop();
}
};
LRUCache.prototype._makeMostRecent = function (key) {
var current = this.useReverse[key];
if (current === this.mostRecent) {
return;
} else if (current) {
delete this.useIndex[current];
}
this.mostRecent += 1;
var newIndex = this.mostRecent;
this.useIndex[newIndex] = key;
this.useReverse[key] = newIndex;
}
LRUCache.prototype._pop = function () {
while (this.leastRecent < this.mostRecent) {
var oldKey = this.useIndex[this.leastRecent];
if (!oldKey) {
this.leastRecent += 1;
continue;
}
delete this.useIndex[this.leastRecent];
delete this.useReverse[oldKey];
delete this.cache[oldKey];
this.leastRecent += 1;
this.size -= 1;
return;
}
}
////////////////////////////////////////////////////////////////////////////
// PFONT.JS START
////////////////////////////////////////////////////////////////////////////
/**
* [internal function] computeFontMetrics() calculates various metrics for text
* placement. Currently this function computes the ascent, descent and leading
* (from "lead", used for vertical space) values for the currently active font.
*/
function computeFontMetrics(pfont) {
var emQuad = 250,
correctionFactor = pfont.size / emQuad,
canvas = document.createElement("canvas");
canvas.width = 2*emQuad;
canvas.height = 2*emQuad;
canvas.style.opacity = 0;
var cfmFont = pfont.getCSSDefinition(emQuad+"px", "normal"),
ctx = canvas.getContext("2d");
ctx.font = cfmFont;
pfont.context2d = ctx;
// Size the canvas using a string with common max-ascent and max-descent letters.
// Changing the canvas dimensions resets the context, so we must reset the font.
var protrusions = "dbflkhyjqpg";
canvas.width = ctx.measureText(protrusions).width;
ctx.font = cfmFont;
// for text lead values, we meaure a multiline text container.
var leadDiv = document.createElement("div");
leadDiv.style.position = "absolute";
leadDiv.style.opacity = 0;
leadDiv.style.fontFamily = '"' + pfont.name + '"';
leadDiv.style.fontSize = emQuad + "px";
leadDiv.innerHTML = protrusions + "<br/>" + protrusions;
document.body.appendChild(leadDiv);
var w = canvas.width,
h = canvas.height,
baseline = h/2;
// Set all canvas pixeldata values to 255, with all the content
// data being 0. This lets us scan for data[i] != 255.
ctx.fillStyle = "white";
ctx.fillRect(0, 0, w, h);
ctx.fillStyle = "black";
ctx.fillText(protrusions, 0, baseline);
var pixelData = ctx.getImageData(0, 0, w, h).data;
// canvas pixel data is w*4 by h*4, because R, G, B and A are separate,
// consecutive values in the array, rather than stored as 32 bit ints.
var i = 0,
w4 = w * 4,
len = pixelData.length;
// Finding the ascent uses a normal, forward scanline
while (++i < len && pixelData[i] === 255) {
nop();
}
var ascent = Math.round(i / w4);
// Finding the descent uses a reverse scanline
i = len - 1;
while (--i > 0 && pixelData[i] === 255) {
nop();
}
var descent = Math.round(i / w4);
// set font metrics
pfont.ascent = correctionFactor * (baseline - ascent);
pfont.descent = correctionFactor * (descent - baseline);
// Then we try to get the real value from the browser
if (document.defaultView.getComputedStyle) {
var leadDivHeight = document.defaultView.getComputedStyle(leadDiv,null).getPropertyValue("height");
leadDivHeight = correctionFactor * leadDivHeight.replace("px","");
if (leadDivHeight >= pfont.size * 2) {
pfont.leading = Math.round(leadDivHeight/2);
}
}
document.body.removeChild(leadDiv);
}
// Defines system (non-SVG) font.
function PFont(name, size) {
// according to the P5 API, new PFont() is legal (albeit completely useless)
if (name === undef) {
name = "";
}
this.name = name;
if (size === undef) {
size = 0;
}
this.size = size;
this.glyph = false;
this.ascent = 0;
this.descent = 0;
// For leading, the "safe" value uses the standard TEX ratio
this.leading = 1.2 * size;
// Note that an italic, bold font must used "... Bold Italic"
// in P5. "... Italic Bold" is treated as normal/normal.
var illegalIndicator = name.indexOf(" Italic Bold");
if (illegalIndicator !== -1) {
name = name.substring(0, illegalIndicator);
}
// determine font style
this.style = "normal";
var italicsIndicator = name.indexOf(" Italic");
if (italicsIndicator !== -1) {
name = name.substring(0, italicsIndicator);
this.style = "italic";
}
// determine font weight
this.weight = "normal";
var boldIndicator = name.indexOf(" Bold");
if (boldIndicator !== -1) {
name = name.substring(0, boldIndicator);
this.weight = "bold";
}
// determine font-family name
this.family = "sans-serif";
if (name !== undef) {
switch(name) {
case "sans-serif":
case "serif":
case "monospace":
case "fantasy":
case "cursive":
this.family = name;
break;
default:
this.family = '"' + name + '", sans-serif';
break;
}
}
// Calculate the ascent/descent/leading value based on
// how the browser renders this font.
this.context2d = null;
computeFontMetrics(this);
this.css = this.getCSSDefinition();
this.context2d.font = this.css;
}
/**
* This function generates the CSS "font" string for this PFont
*/
PFont.prototype.getCSSDefinition = function(fontSize, lineHeight) {
if(fontSize===undef) {
fontSize = this.size + "px";
}
if(lineHeight===undef) {
lineHeight = this.leading + "px";
}
// CSS "font" definition: font-style font-variant font-weight font-size/line-height font-family
var components = [this.style, "normal", this.weight, fontSize + "/" + lineHeight, this.family];
return components.join(" ");
};
/**
* We cannot rely on there being a 2d context available,
* because we support OPENGL sketches, and canvas3d has
* no "measureText" function in the API.
*/
PFont.prototype.measureTextWidth = function(string) {
return this.context2d.measureText(string).width;
};
/**
* Global "loaded fonts" list, internal to PFont
*/
PFont.PFontCache = new LRUCache(100);
/**
* This function acts as single access point for getting and caching
* fonts across all sketches handled by an instance of Processing.js
*/
PFont.get = function(fontName, fontSize) {
var cache = PFont.PFontCache;
var idx = fontName+"/"+fontSize;
var val = cache.get(idx);
if (!val) {
val = new PFont(fontName, fontSize);
cache.set(idx, val);
}
return val;
};
/**
* Lists all standard fonts. Due to browser limitations, this list is
* not the system font list, like in P5, but the CSS "genre" list.
*/
PFont.list = function() {
return ["sans-serif", "serif", "monospace", "fantasy", "cursive"];
};
/**
* Loading external fonts through @font-face rules is handled by PFont,
* to ensure fonts loaded in this way are globally available.
*/
PFont.preloading = {
// template element used to compare font sizes
template: {},
// indicates whether or not the reference tiny font has been loaded
initialized: false,
// load the reference tiny font via a css @font-face rule
initialize: function() {
var generateTinyFont = function() {
var encoded = "#E3KAI2wAgT1MvMg7Eo3VmNtYX7ABi3CxnbHlm" +
"7Abw3kaGVhZ7ACs3OGhoZWE7A53CRobXR47AY3" +
"AGbG9jYQ7G03Bm1heH7ABC3CBuYW1l7Ae3AgcG" +
"9zd7AI3AE#B3AQ2kgTY18PPPUACwAg3ALSRoo3" +
"#yld0xg32QAB77#E777773B#E3C#I#Q77773E#" +
"Q7777777772CMAIw7AB77732B#M#Q3wAB#g3B#" +
"E#E2BB//82BB////w#B7#gAEg3E77x2B32B#E#" +
"Q#MTcBAQ32gAe#M#QQJ#E32M#QQJ#I#g32Q77#";
var expand = function(input) {
return "AAAAAAAA".substr(~~input ? 7-input : 6);
};
return encoded.replace(/[#237]/g, expand);
};
var fontface = document.createElement("style");
fontface.setAttribute("type","text/css");
fontface.innerHTML = "@font-face {\n" +
' font-family: "PjsEmptyFont";' + "\n" +
" src: url('data:application/x-font-ttf;base64,"+generateTinyFont()+"')\n" +
" format('truetype');\n" +
"}";
document.head.appendChild(fontface);
// set up the template element
var element = document.createElement("span");
element.style.cssText = 'position: absolute; top: 0; left: 0; opacity: 0; font-family: "PjsEmptyFont", fantasy; pointer-events: none;';
element.innerHTML = "AAAAAAAA";
document.body.appendChild(element);
this.template = element;
this.initialized = true;
},
// Shorthand function to get the computed width for an element.
getElementWidth: function(element) {
return document.defaultView.getComputedStyle(element,"").getPropertyValue("width");
},
// time taken so far in attempting to load a font
timeAttempted: 0,
// returns false if no fonts are pending load, or true otherwise.
pending: function(intervallength) {
if (!this.initialized) {
this.initialize();
}
var element,
computedWidthFont,
computedWidthRef = this.getElementWidth(this.template);
for (var i = 0; i < this.fontList.length; i++) {
// compares size of text in pixels. if equal, custom font is not yet loaded
element = this.fontList[i];
computedWidthFont = this.getElementWidth(element);
if (this.timeAttempted < 4000 && computedWidthFont === computedWidthRef) {
this.timeAttempted += intervallength;
return true;
} else {
document.body.removeChild(element);
this.fontList.splice(i--, 1);
this.timeAttempted = 0;
}
}
// if there are no more fonts to load, pending is false
if (this.fontList.length === 0) {
return false;
}
// We should have already returned before getting here.
// But, if we do get here, length!=0 so fonts are pending.
return true;
},
// fontList contains elements to compare font sizes against a template
fontList: [],
// addedList contains the fontnames of all the fonts loaded via @font-face
addedList: {},
// adds a font to the font cache
// creates an element using the font, to start loading the font,
// and compare against a default font to see if the custom font is loaded
add: function(fontSrc) {
if (!this.initialized) {
this.initialize();
}
// fontSrc can be a string or a javascript object
// acceptable fonts are .ttf, .otf, and data uri
var fontName = (typeof fontSrc === 'object' ? fontSrc.fontFace : fontSrc),
fontUrl = (typeof fontSrc === 'object' ? fontSrc.url : fontSrc);
// check whether we already created the @font-face rule for this font
if (this.addedList[fontName]) {
return;
}
// if we didn't, create the @font-face rule
var style = document.createElement("style");
style.setAttribute("type","text/css");
style.innerHTML = "@font-face{\n font-family: '" + fontName + "';\n src: url('" + fontUrl + "');\n}\n";
document.head.appendChild(style);
this.addedList[fontName] = true;
// also create the element to load and compare the new font
var element = document.createElement("span");
element.style.cssText = "position: absolute; top: 0; left: 0; opacity: 0; pointer-events: none;";
element.style.fontFamily = '"' + fontName + '", "PjsEmptyFont", fantasy';
element.innerHTML = "AAAAAAAA";
document.body.appendChild(element);
this.fontList.push(element);
}
};
// add to the default scope
defaultScope.PFont = PFont;
////////////////////////////////////////////////////////////////////////////
// PFONT.JS END
////////////////////////////////////////////////////////////////////////////
var Processing = this.Processing = function(aCanvas, aCode) {
// Previously we allowed calling Processing as a func instead of ctor, but no longer.
if (!(this instanceof Processing)) {
throw("called Processing constructor as if it were a function: missing 'new'.");
}
var curElement,
pgraphicsMode = (aCanvas === undef && aCode === undef);
if (pgraphicsMode) {
curElement = document.createElement("canvas");
} else {
// We'll take a canvas element or a string for a canvas element's id
curElement = typeof aCanvas === "string" ? document.getElementById(aCanvas) : aCanvas;
}
if (!(curElement instanceof HTMLCanvasElement)) {
throw("called Processing constructor without passing canvas element reference or id.");
}
function unimplemented(s) {
Processing.debug('Unimplemented - ' + s);
}
// When something new is added to "p." it must also be added to the "names" array.
// The names array contains the names of everything that is inside "p."
var p = this;
// PJS specific (non-p5) methods and properties to externalize
p.externals = {
canvas: curElement,
context: undef,
sketch: undef
};
p.name = 'Processing.js Instance'; // Set Processing defaults / environment variables
p.use3DContext = false; // default '2d' canvas context
/**
* Confirms if a Processing program is "focused", meaning that it is
* active and will accept input from mouse or keyboard. This variable
* is "true" if it is focused and "false" if not. This variable is
* often used when you want to warn people they need to click on the
* browser before it will work.
*/
p.focused = false;
p.breakShape = false;
// Glyph path storage for textFonts
p.glyphTable = {};
// Global vars for tracking mouse position
p.pmouseX = 0;
p.pmouseY = 0;
p.mouseX = 0;
p.mouseY = 0;
p.mouseButton = 0;
p.mouseScroll = 0;
// Undefined event handlers to be replaced by user when needed
p.mouseClicked = undef;
p.mouseDragged = undef;
p.mouseMoved = undef;
p.mousePressed = undef;
p.mouseReleased = undef;
p.mouseScrolled = undef;
p.mouseOver = undef;
p.mouseOut = undef;
p.touchStart = undef;
p.touchEnd = undef;
p.touchMove = undef;
p.touchCancel = undef;
p.key = undef;
p.keyCode = undef;
p.keyPressed = nop; // needed to remove function checks
p.keyReleased = nop;
p.keyTyped = nop;
p.draw = undef;
p.setup = undef;
// Remapped vars
p.__mousePressed = false;
p.__keyPressed = false;
p.__frameRate = 60;
// XXX(jeresig): Added mouseIsPressed/keyIsPressed
p.mouseIsPressed = false;
p.keyIsPressed = false;
// The current animation frame
p.frameCount = 0;
// The height/width of the canvas
p.width = 100;
p.height = 100;
// XXX(jeresig)
p.angleMode = "radians";
var PVector = p.PVector = (function() {
function PVector(x, y, z) {
this.x = x || 0;
this.y = y || 0;
this.z = z || 0;
}
PVector.fromAngle = function(angle, v) {
if (v === undef || v === null) {
v = new PVector();
}
// XXX(jeresig)
v.x = p.cos(angle);
v.y = p.sin(angle);
return v;
};
PVector.random2D = function(v) {
return PVector.fromAngle(Math.random() * 360, v);
};
PVector.random3D = function(v) {
var angle = Math.random() * 360;
var vz = Math.random() * 2 - 1;
var mult = Math.sqrt(1 - vz * vz);
// XXX(jeresig)
var vx = mult * p.cos(angle);
var vy = mult * p.sin(angle);
if (v === undef || v === null) {
v = new PVector(vx, vy, vz);
} else {
v.set(vx, vy, vz);
}
return v;
};
PVector.dist = function(v1, v2) {
return v1.dist(v2);
};
PVector.dot = function(v1, v2) {
return v1.dot(v2);
};
PVector.cross = function(v1, v2) {
return v1.cross(v2);
};
PVector.sub = function(v1, v2) {
return new PVector(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z);
};
PVector.angleBetween = function(v1, v2) {
// XXX(jeresig)
return p.acos(v1.dot(v2) / (v1.mag() * v2.mag()));
};
PVector.lerp = function(v1, v2, amt) {
// non-static lerp mutates object, but this version returns a new vector
var retval = new PVector(v1.x, v1.y, v1.z);
retval.lerp(v2, amt);
return retval;
};
// Common vector operations for PVector
PVector.prototype = {
set: function(v, y, z) {
if (arguments.length === 1) {
this.set(v.x || v[0] || 0,
v.y || v[1] || 0,
v.z || v[2] || 0);
} else {
this.x = v || 0;
this.y = y || 0;
this.z = z || 0;
}
},
get: function() {
return new PVector(this.x, this.y, this.z);
},
mag: function() {
var x = this.x,
y = this.y,
z = this.z;
return Math.sqrt(x * x + y * y + z * z);
},
magSq: function() {
var x = this.x,
y = this.y,
z = this.z;
return (x * x + y * y + z * z);
},
setMag: function(v_or_len, len) {
if (len === undef) {
len = v_or_len;
this.normalize();
this.mult(len);
} else {
var v = v_or_len;
v.normalize();
v.mult(len);
return v;
}
},
add: function(v, y, z) {
if (arguments.length === 1) {
this.x += v.x;
this.y += v.y;
this.z += v.z;
} else {
this.x += v;
this.y += y;
this.z += z;
}
},
sub: function(v, y, z) {
if (arguments.length === 1) {
this.x -= v.x;
this.y -= v.y;
this.z -= v.z;
} else {
this.x -= v;
this.y -= y;
this.z -= z;
}
},
mult: function(v) {
if (typeof v === 'number') {
this.x *= v;
this.y *= v;
this.z *= v;
} else {
this.x *= v.x;
this.y *= v.y;
this.z *= v.z;
}
},
div: function(v) {
if (typeof v === 'number') {
this.x /= v;
this.y /= v;
this.z /= v;
} else {
this.x /= v.x;
this.y /= v.y;
this.z /= v.z;
}
},
rotate: function(angle) {
var prev_x = this.x;
var c = p.cos(angle);
var s = p.sin(angle);
this.x = c * this.x - s * this.y;
this.y = s * prev_x + c * this.y;
},
dist: function(v) {
var dx = this.x - v.x,
dy = this.y - v.y,
dz = this.z - v.z;
return Math.sqrt(dx * dx + dy * dy + dz * dz);
},
dot: function(v, y, z) {
if (arguments.length === 1) {
return (this.x * v.x + this.y * v.y + this.z * v.z);
}
return (this.x * v + this.y * y + this.z * z);
},
cross: function(v) {
var x = this.x,
y = this.y,
z = this.z;
return new PVector(y * v.z - v.y * z,
z * v.x - v.z * x,
x * v.y - v.x * y);
},
lerp: function(v_or_x, amt_or_y, z, amt) {
var lerp_val = function(start, stop, amt) {
return start + (stop - start) * amt;
};
var x, y;
if (arguments.length === 2) {
// given vector and amt
amt = amt_or_y;
x = v_or_x.x;
y = v_or_x.y;
z = v_or_x.z;
} else {
// given x, y, z and amt
x = v_or_x;
y = amt_or_y;
}
this.x = lerp_val(this.x, x, amt);
this.y = lerp_val(this.y, y, amt);
this.z = lerp_val(this.z, z, amt);
},
normalize: function() {
var m = this.mag();
if (m > 0) {
this.div(m);
}
},
limit: function(high) {
if (this.mag() > high) {
this.normalize();
this.mult(high);
}
},
heading: function() {
// XXX(jeresig)
return -p.atan2(-this.y, this.x);
},
heading2D: function() {
return this.heading();
},
toString: function() {
return "[" + this.x + ", " + this.y + ", " + this.z + "]";
},
array: function() {
return [this.x, this.y, this.z];
}
};
function createPVectorMethod(method) {
return function(v1, v2) {
var v = v1.get();
v[method](v2);
return v;
};
}
// Create the static methods of PVector automatically
// We don't do toString because it causes a TypeError
// when attempting to stringify PVector
for (var method in PVector.prototype) {
if (PVector.prototype.hasOwnProperty(method) && !PVector.hasOwnProperty(method) &&
method !== "toString") {
PVector[method] = createPVectorMethod(method);
}
}
return PVector;
}());
// "Private" variables used to maintain state
var curContext,
curSketch,
drawing, // hold a Drawing2D or Drawing3D object
online = true,
doFill = true,
fillStyle = [1.0, 1.0, 1.0, 1.0],
currentFillColor = 0xFFFFFFFF,
isFillDirty = true,
doStroke = true,
strokeStyle = [0.0, 0.0, 0.0, 1.0],
currentStrokeColor = 0xFF000000,
isStrokeDirty = true,
lineWidth = 1,
loopStarted = false,
renderSmooth = false,
doLoop = true,
looping = 0,
curRectMode = PConstants.CORNER,
curEllipseMode = PConstants.CENTER,
normalX = 0,
normalY = 0,
normalZ = 0,
normalMode = PConstants.NORMAL_MODE_AUTO,
curFrameRate = 60,
curMsPerFrame = 1000/curFrameRate,
curCursor = PConstants.ARROW,
oldCursor = curElement.style.cursor,
curShape = PConstants.POLYGON,
curShapeCount = 0,
curvePoints = [],
curTightness = 0,
curveDet = 20,
curveInited = false,
backgroundObj = -3355444, // rgb(204, 204, 204) is the default gray background colour
bezDetail = 20,
colorModeA = 255,
colorModeX = 255,
colorModeY = 255,
colorModeZ = 255,
pathOpen = false,
mouseDragging = false,
pmouseXLastFrame = 0,
pmouseYLastFrame = 0,
curColorMode = PConstants.RGB,
curTint = null,
curTint3d = null,
getLoaded = false,
start = Date.now(),
timeSinceLastFPS = start,
framesSinceLastFPS = 0,
textcanvas,
curveBasisMatrix,
curveToBezierMatrix,
curveDrawMatrix,
bezierDrawMatrix,
bezierBasisInverse,
bezierBasisMatrix,
curContextCache = { attributes: {}, locations: {} },
// Shaders
programObject3D,
programObject2D,
programObjectUnlitShape,
boxBuffer,
boxNormBuffer,
boxOutlineBuffer,
rectBuffer,
rectNormBuffer,
sphereBuffer,
lineBuffer,
fillBuffer,
fillColorBuffer,
strokeColorBuffer,
pointBuffer,
shapeTexVBO,
canTex, // texture for createGraphics
textTex, // texture for 3d tex
curTexture = {width:0,height:0},
curTextureMode = PConstants.IMAGE,
usingTexture = false,
textBuffer,
textureBuffer,
indexBuffer,
// Text alignment
horizontalTextAlignment = PConstants.LEFT,
verticalTextAlignment = PConstants.BASELINE,
textMode = PConstants.MODEL,
// Font state
curFontName = "Arial",
curTextSize = 12,
curTextAscent = 9,
curTextDescent = 2,
curTextLeading = 14,
curTextFont = PFont.get(curFontName, curTextSize),
// Pixels cache
originalContext,
proxyContext = null,
isContextReplaced = false,
setPixelsCached,
maxPixelsCached = 1000,
pressedKeysMap = [],
lastPressedKeyCode = null,
codedKeys = [ PConstants.SHIFT, PConstants.CONTROL, PConstants.ALT, PConstants.CAPSLK, PConstants.PGUP, PConstants.PGDN,
PConstants.END, PConstants.HOME, PConstants.LEFT, PConstants.UP, PConstants.RIGHT, PConstants.DOWN, PConstants.NUMLK,
PConstants.INSERT, PConstants.F1, PConstants.F2, PConstants.F3, PConstants.F4, PConstants.F5, PConstants.F6, PConstants.F7,
PConstants.F8, PConstants.F9, PConstants.F10, PConstants.F11, PConstants.F12, PConstants.META ];
// Get padding and border style widths for mouse offsets
var stylePaddingLeft, stylePaddingTop, styleBorderLeft, styleBorderTop;
if (document.defaultView && document.defaultView.getComputedStyle) {
stylePaddingLeft = parseInt(document.defaultView.getComputedStyle(curElement, null)['paddingLeft'], 10) || 0;
stylePaddingTop = parseInt(document.defaultView.getComputedStyle(curElement, null)['paddingTop'], 10) || 0;
styleBorderLeft = parseInt(document.defaultView.getComputedStyle(curElement, null)['borderLeftWidth'], 10) || 0;
styleBorderTop = parseInt(document.defaultView.getComputedStyle(curElement, null)['borderTopWidth'], 10) || 0;
}
// User can only have MAX_LIGHTS lights
var lightCount = 0;
//sphere stuff
var sphereDetailV = 0,
sphereDetailU = 0,
sphereX = [],
sphereY = [],
sphereZ = [],
sinLUT = new Float32Array(PConstants.SINCOS_LENGTH),
cosLUT = new Float32Array(PConstants.SINCOS_LENGTH),
sphereVerts,
sphereNorms;
// Camera defaults and settings
var cam,
cameraInv,
modelView,
modelViewInv,
userMatrixStack,
userReverseMatrixStack,
inverseCopy,
projection,
manipulatingCamera = false,
frustumMode = false,
cameraFOV = 60 * (Math.PI / 180),
cameraX = p.width / 2,
cameraY = p.height / 2,
cameraZ = cameraY / Math.tan(cameraFOV / 2),
cameraNear = cameraZ / 10,
cameraFar = cameraZ * 10,
cameraAspect = p.width / p.height;
var vertArray = [],
curveVertArray = [],
curveVertCount = 0,
isCurve = false,
isBezier = false,
firstVert = true;
//PShape stuff
var curShapeMode = PConstants.CORNER;
// Stores states for pushStyle() and popStyle().
var styleArray = [];
// Vertices are specified in a counter-clockwise order
// triangles are in this order: back, front, right, bottom, left, top
var boxVerts = new Float32Array([
0.5, 0.5, -0.5, 0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5,
0.5, 0.5, 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5,
0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5,
0.5, -0.5, -0.5, 0.5, -0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, -0.5, 0.5, -0.5, -0.5,
-0.5, -0.5, -0.5, -0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, -0.5, -0.5,
0.5, 0.5, 0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5, 0.5, 0.5]);
var boxOutlineVerts = new Float32Array([
0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5,
-0.5, 0.5, -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5,
0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5,
-0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, 0.5,
0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, -0.5, -0.5, -0.5,
-0.5, -0.5, -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5]);
var boxNorms = new Float32Array([
0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1,
0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,
0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0,
-1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0,
0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0]);
// These verts are used for the fill and stroke using TRIANGLE_FAN and LINE_LOOP
var rectVerts = new Float32Array([0,0,0, 0,1,0, 1,1,0, 1,0,0]);
var rectNorms = new Float32Array([0,0,1, 0,0,1, 0,0,1, 0,0,1]);
// Shader for points and lines in begin/endShape
var vShaderSrcUnlitShape =
"varying vec4 frontColor;" +
"attribute vec3 aVertex;" +
"attribute vec4 aColor;" +
"uniform mat4 uView;" +
"uniform mat4 uProjection;" +
"uniform float pointSize;" +
"void main(void) {" +
" frontColor = aColor;" +
" gl_PointSize = pointSize;" +
" gl_Position = uProjection * uView * vec4(aVertex, 1.0);" +
"}";
var fShaderSrcUnlitShape =
"#ifdef GL_ES\n" +
"precision highp float;\n" +
"#endif\n" +
"varying vec4 frontColor;" +
"void main(void){" +
" gl_FragColor = frontColor;" +
"}";
// Shader for rect, text, box outlines, sphere outlines, point() and line()
var vertexShaderSource2D =
"varying vec4 frontColor;" +
"attribute vec3 Vertex;" +
"attribute vec2 aTextureCoord;" +
"uniform vec4 color;" +
"uniform mat4 model;" +
"uniform mat4 view;" +
"uniform mat4 projection;" +
"uniform float pointSize;" +
"varying vec2 vTextureCoord;"+
"void main(void) {" +
" gl_PointSize = pointSize;" +
" frontColor = color;" +
" gl_Position = projection * view * model * vec4(Vertex, 1.0);" +
" vTextureCoord = aTextureCoord;" +
"}";
var fragmentShaderSource2D =
"#ifdef GL_ES\n" +
"precision highp float;\n" +
"#endif\n" +
"varying vec4 frontColor;" +
"varying vec2 vTextureCoord;"+
"uniform sampler2D uSampler;"+
"uniform int picktype;"+
"void main(void){" +
" if(picktype == 0){"+
" gl_FragColor = frontColor;" +
" }" +
" else if(picktype == 1){"+
" float alpha = texture2D(uSampler, vTextureCoord).a;"+
" gl_FragColor = vec4(frontColor.rgb*alpha, alpha);\n"+
" }"+
"}";
var webglMaxTempsWorkaround = /Windows/.test(navigator.userAgent);
// Vertex shader for boxes and spheres
var vertexShaderSource3D =
"varying vec4 frontColor;" +
"attribute vec3 Vertex;" +
"attribute vec3 Normal;" +
"attribute vec4 aColor;" +
"attribute vec2 aTexture;" +
"varying vec2 vTexture;" +
"uniform vec4 color;" +
"uniform bool usingMat;" +
"uniform vec3 specular;" +
"uniform vec3 mat_emissive;" +
"uniform vec3 mat_ambient;" +
"uniform vec3 mat_specular;" +
"uniform float shininess;" +
"uniform mat4 model;" +
"uniform mat4 view;" +
"uniform mat4 projection;" +
"uniform mat4 normalTransform;" +
"uniform int lightCount;" +
"uniform vec3 falloff;" +
// careful changing the order of these fields. Some cards
// have issues with memory alignment
"struct Light {" +
" int type;" +
" vec3 color;" +
" vec3 position;" +
" vec3 direction;" +
" float angle;" +
" vec3 halfVector;" +
" float concentration;" +
"};" +
// nVidia cards have issues with arrays of structures
// so instead we create 8 instances of Light
"uniform Light lights0;" +
"uniform Light lights1;" +
"uniform Light lights2;" +
"uniform Light lights3;" +
"uniform Light lights4;" +
"uniform Light lights5;" +
"uniform Light lights6;" +
"uniform Light lights7;" +
// GLSL does not support switch
"Light getLight(int index){" +
" if(index == 0) return lights0;" +
" if(index == 1) return lights1;" +
" if(index == 2) return lights2;" +
" if(index == 3) return lights3;" +
" if(index == 4) return lights4;" +
" if(index == 5) return lights5;" +
" if(index == 6) return lights6;" +
// Do not use a conditional for the last return statement
// because some video cards will fail and complain that
// "not all paths return"
" return lights7;" +
"}" +
"void AmbientLight( inout vec3 totalAmbient, in vec3 ecPos, in Light light ) {" +
// Get the vector from the light to the vertex
// Get the distance from the current vector to the light position
" float d = length( light.position - ecPos );" +
" float attenuation = 1.0 / ( falloff[0] + ( falloff[1] * d ) + ( falloff[2] * d * d ));" +
" totalAmbient += light.color * attenuation;" +
"}" +
"void DirectionalLight( inout vec3 col, inout vec3 spec, in vec3 vertNormal, in vec3 ecPos, in Light light ) {" +
" float powerfactor = 0.0;" +
" float nDotVP = max(0.0, dot( vertNormal, normalize(-light.position) ));" +
" float nDotVH = max(0.0, dot( vertNormal, normalize(-light.position-normalize(ecPos) )));" +
" if( nDotVP != 0.0 ){" +
" powerfactor = pow( nDotVH, shininess );" +
" }" +
" col += light.color * nDotVP;" +
" spec += specular * powerfactor;" +
"}" +
"void PointLight( inout vec3 col, inout vec3 spec, in vec3 vertNormal, in vec3 ecPos, in Light light ) {" +
" float powerfactor;" +
// Get the vector from the light to the vertex
" vec3 VP = light.position - ecPos;" +
// Get the distance from the current vector to the light position
" float d = length( VP ); " +
// Normalize the light ray so it can be used in the dot product operation.
" VP = normalize( VP );" +
" float attenuation = 1.0 / ( falloff[0] + ( falloff[1] * d ) + ( falloff[2] * d * d ));" +
" float nDotVP = max( 0.0, dot( vertNormal, VP ));" +
" vec3 halfVector = normalize( VP - normalize(ecPos) );" +
" float nDotHV = max( 0.0, dot( vertNormal, halfVector ));" +
" if( nDotVP == 0.0) {" +
" powerfactor = 0.0;" +
" }" +
" else{" +
" powerfactor = pow( nDotHV, shininess );" +
" }" +
" spec += specular * powerfactor * attenuation;" +
" col += light.color * nDotVP * attenuation;" +
"}" +
/*
*/
"void SpotLight( inout vec3 col, inout vec3 spec, in vec3 vertNormal, in vec3 ecPos, in Light light ) {" +
" float spotAttenuation;" +
" float powerfactor;" +
// calculate the vector from the current vertex to the light.
" vec3 VP = light.position - ecPos; " +
" vec3 ldir = normalize( -light.direction );" +
// get the distance from the spotlight and the vertex
" float d = length( VP );" +
" VP = normalize( VP );" +
" float attenuation = 1.0 / ( falloff[0] + ( falloff[1] * d ) + ( falloff[2] * d * d ) );" +
// dot product of the vector from vertex to light and light direction.
" float spotDot = dot( VP, ldir );" +
// if the vertex falls inside the cone
(webglMaxTempsWorkaround ? // Windows reports max temps error if light.angle is used
" spotAttenuation = 1.0; " :
" if( spotDot > cos( light.angle ) ) {" +
" spotAttenuation = pow( spotDot, light.concentration );" +
" }" +
" else{" +
" spotAttenuation = 0.0;" +
" }" +
" attenuation *= spotAttenuation;" +
"") +
" float nDotVP = max( 0.0, dot( vertNormal, VP ));" +
" vec3 halfVector = normalize( VP - normalize(ecPos) );" +
" float nDotHV = max( 0.0, dot( vertNormal, halfVector ));" +
" if( nDotVP == 0.0 ) {" +
" powerfactor = 0.0;" +
" }" +
" else {" +
" powerfactor = pow( nDotHV, shininess );" +
" }" +
" spec += specular * powerfactor * attenuation;" +
" col += light.color * nDotVP * attenuation;" +
"}" +
"void main(void) {" +
" vec3 finalAmbient = vec3( 0.0, 0.0, 0.0 );" +
" vec3 finalDiffuse = vec3( 0.0, 0.0, 0.0 );" +
" vec3 finalSpecular = vec3( 0.0, 0.0, 0.0 );" +
" vec4 col = color;" +
" if(color[0] == -1.0){" +
" col = aColor;" +
" }" +
// We use the sphere vertices as the normals when we create the sphere buffer.
// But this only works if the sphere vertices are unit length, so we
// have to normalize the normals here. Since this is only required for spheres
// we could consider placing this in a conditional later on.
" vec3 norm = normalize(vec3( normalTransform * vec4( Normal, 0.0 ) ));" +
" vec4 ecPos4 = view * model * vec4(Vertex,1.0);" +
" vec3 ecPos = (vec3(ecPos4))/ecPos4.w;" +
// If there were no lights this draw call, just use the
// assigned fill color of the shape and the specular value
" if( lightCount == 0 ) {" +
" frontColor = col + vec4(mat_specular,1.0);" +
" }" +
" else {" +
// WebGL forces us to iterate over a constant value
// so we can't iterate using lightCount
" for( int i = 0; i < 8; i++ ) {" +
" Light l = getLight(i);" +
// We can stop iterating if we know we have gone past
// the number of lights which are on
" if( i >= lightCount ){" +
" break;" +
" }" +
" if( l.type == 0 ) {" +
" AmbientLight( finalAmbient, ecPos, l );" +
" }" +
" else if( l.type == 1 ) {" +
" DirectionalLight( finalDiffuse, finalSpecular, norm, ecPos, l );" +
" }" +
" else if( l.type == 2 ) {" +
" PointLight( finalDiffuse, finalSpecular, norm, ecPos, l );" +
" }" +
" else {" +
" SpotLight( finalDiffuse, finalSpecular, norm, ecPos, l );" +
" }" +
" }" +
" if( usingMat == false ) {" +
" frontColor = vec4(" +
" vec3(col) * finalAmbient +" +
" vec3(col) * finalDiffuse +" +
" vec3(col) * finalSpecular," +
" col[3] );" +
" }" +
" else{" +
" frontColor = vec4( " +
" mat_emissive + " +
" (vec3(col) * mat_ambient * finalAmbient) + " +
" (vec3(col) * finalDiffuse) + " +
" (mat_specular * finalSpecular), " +
" col[3] );" +
" }" +
" }" +
" vTexture.xy = aTexture.xy;" +
" gl_Position = projection * view * model * vec4( Vertex, 1.0 );" +
"}";
var fragmentShaderSource3D =
"#ifdef GL_ES\n" +
"precision highp float;\n" +
"#endif\n" +
"varying vec4 frontColor;" +