Team:TU Eindhoven/the game/processing.js
From 2014.igem.org
- (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports
- {}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
// build script for generating processing.js
var Browser = {
isDomPresent: true, navigator: navigator, window: window, document: document, 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; }
};
window.Processing = require('./src/')(Browser);
},{"./src/":27}],2:[function(require,module,exports){ module.exports={
"name": "Processing.js", "version": "1.4.8", "dependencies": { "argv": "~0.0.2", "browserify": "~2.18.1", "express": "~3.3.3", "node-minify": "~0.7.3", "nunjucks": "~0.1.9", "open": "0.0.3" }, "devDependencies": { "grunt": "~0.4.1", "grunt-cli": "~0.1.8", "grunt-contrib-jshint": "~0.4.3" }
}
},{}],3:[function(require,module,exports){ /**
- 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.
- /
module.exports = function ObjectIterator(obj) {
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 if (obj.iterator instanceof Function) { return obj.iterator(); } else { throw "Unable to iterate: " + obj; }
};
},{}],4:[function(require,module,exports){ /**
* Processing.js environment constants */
module.exports = {
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, TAU: 2 * Math.PI, HALF_PI: Math.PI / 2, THIRD_PI: Math.PI / 3, QUARTER_PI: Math.PI / 4,
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
};
},{}],5:[function(require,module,exports){ /**
* Processing.js default scope */
module.exports = function(options) {
// Building defaultScope. Changing of the prototype protects // internal Processing code from the changes in defaultScope function DefaultScope() {} DefaultScope.prototype = options.PConstants;
var defaultScope = new DefaultScope();
// copy over all known Object types and helper objects Object.keys(options).forEach(function(prop) { defaultScope[prop] = options[prop]; });
//////////////////////////////////////////////////////////////////////////// // 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); } } };
/** * class overloading, part 1 */ function overloadBaseClassFunction(object, name, basefn) { if (!object.hasOwnProperty(name) || typeof object[name] !== 'function') { // object method is not a function or just inherited from Object.prototype object[name] = basefn; return; } var fn = object[name]; if ("$overloads" in fn) { // the object method already overloaded (see defaultScope.addMethod) // let's just change a fallback method fn.$defaultOverload = basefn; return; } if (!("$overloads" in basefn) && fn.length === basefn.length) { // special case when we just overriding the method return; } var overloads, defaultOverload; if ("$overloads" in basefn) { // let's inherit base class overloads to speed up things overloads = basefn.$overloads.slice(0); overloads[fn.length] = fn; defaultOverload = basefn.$defaultOverload; } else { overloads = []; overloads[basefn.length] = basefn; overloads[fn.length] = fn; defaultOverload = fn; } var hubfn = function() { var fn = hubfn.$overloads[arguments.length] || ("$methodArgsIndex" in hubfn && arguments.length > hubfn.$methodArgsIndex ? hubfn.$overloads[hubfn.$methodArgsIndex] : null) || hubfn.$defaultOverload; return fn.apply(this, arguments); }; hubfn.$overloads = overloads; if ("$methodArgsIndex" in basefn) { hubfn.$methodArgsIndex = basefn.$methodArgsIndex; } hubfn.$defaultOverload = defaultOverload; hubfn.name = name; object[name] = hubfn; }
/** * class overloading, part 2 */
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') { overloadBaseClassFunction(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()); }
subClass.$super = baseClass; }
/** * class overloading, part 3 */ 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; } };
// static defaultScope.extendStaticMembers = function(derived, base) { extendClass(derived, base); };
// interface defaultScope.extendInterfaceMembers = function(derived, base) { extendClass(derived, base); };
/** * Java methods and JavaScript functions differ enough that * we need a special function to make sure it all links up * as classical hierarchical class chains. */ defaultScope.addMethod = function(object, name, fn, hasMethodArgs) { var existingfn = object[name]; if (existingfn || hasMethodArgs) { var args = fn.length; // builds the overload methods table if ("$overloads" in existingfn) { existingfn.$overloads[args] = fn; } else { var hubfn = function() { var fn = hubfn.$overloads[arguments.length] || ("$methodArgsIndex" in hubfn && arguments.length > hubfn.$methodArgsIndex ? hubfn.$overloads[hubfn.$methodArgsIndex] : null) || hubfn.$defaultOverload; return fn.apply(this, arguments); }; var overloads = []; if (existingfn) { overloads[existingfn.length] = existingfn; } overloads[args] = fn; hubfn.$overloads = overloads; hubfn.$defaultOverload = existingfn || fn; if (hasMethodArgs) { hubfn.$methodArgsIndex = args; } hubfn.name = name; object[name] = hubfn; } } else { object[name] = fn; } };
// internal helper function function isNumericalJavaType(type) { if (typeof type !== "string") { return false; } return ["byte", "int", "char", "color", "float", "long", "double"].indexOf(type) !== -1; }
/** * Java's arrays are pre-filled when declared with * an initial size, but no content. JS arrays are not. */ defaultScope.createJavaArray = function(type, bounds) { var result = null, defaultValue = null; if (typeof type === "string") { if (type === "boolean") { defaultValue = false; } else if (isNumericalJavaType(type)) { defaultValue = 0; } } 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] = defaultValue; } } else { result = []; var newBounds = bounds.slice(1); for (var j = 0; j < itemsCount; ++j) { result.push(defaultScope.createJavaArray(type, newBounds)); } } } return result; };
// 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; } });
return defaultScope;
};
},{}],6:[function(require,module,exports){ /**
* Finalise the Processing.js object. */
module.exports = function finalizeProcessing(Processing, options) {
// unpack options var window = options.window, document = options.document, XMLHttpRequest = window.XMLHttpRequest, noop = options.noop, isDOMPresent = options.isDOMPresent, version = options.version, undef;
// versioning Processing.version = (version ? version : "@DEV-VERSION@");
// Share lib space Processing.lib = {};
/** * External libraries can be added to the global Processing * objects with the `registerLibrary` function. */ Processing.registerLibrary = function(name, library) { Processing.lib[name] = library; if(library.hasOwnProperty("init")) { library.init(defaultScope); } };
/** * This is the object that acts as our version of PApplet. * This can be called as Processing.Sketch() or as * Processing.Sketch(function) in which case the function * must be an already-compiled-to-JS sketch function. */ Processing.Sketch = function(attachFunction) { this.attachFunction = attachFunction; this.options = { pauseOnBlur: false, globalKeyEvents: false };
/* Optional Sketch event hooks: * onLoad - parsing/preloading is done, before sketch starts * onSetup - setup() has been called, before first draw() * onPause - noLoop() has been called, pausing draw loop * onLoop - loop() has been called, resuming draw loop * onFrameStart - draw() loop about to begin * onFrameEnd - draw() loop finished * onExit - exit() done being called */ this.onLoad = noop; this.onSetup = noop; this.onPause = noop; this.onLoop = noop; this.onFrameStart = noop; this.onFrameEnd = noop; this.onExit = noop;
this.params = {}; this.imageCache = { pending: 0, images: {}, // Opera requires special administration for preloading operaCache: {}, // Specify an optional img arg if the image is already loaded in the DOM, // otherwise href will get loaded. add: function(href, img) { // Prevent muliple loads for an image, in case it gets // preloaded more than once, or is added via JS and then preloaded. if (this.images[href]) { return; }
if (!isDOMPresent) { this.images[href] = null; }
// No image in the DOM, kick-off a background load if (!img) { img = new Image(); img.onload = (function(owner) { return function() { owner.pending--; }; }(this)); this.pending++; img.src = href; }
this.images[href] = img;
// Opera will not load images until they are inserted into the DOM. if (window.opera) { var div = document.createElement("div"); div.appendChild(img); // we can't use "display: none", since that makes it invisible, and thus not load div.style.position = "absolute"; div.style.opacity = 0; div.style.width = "1px"; div.style.height= "1px"; if (!this.operaCache[href]) { document.body.appendChild(div); this.operaCache[href] = div; } } } };
this.sourceCode = undefined; this.attach = function(processing) { // either attachFunction or sourceCode must be present on attach if(typeof this.attachFunction === "function") { this.attachFunction(processing); } else if(this.sourceCode) { var func = ((new Function("return (" + this.sourceCode + ");"))()); func(processing); this.attachFunction = func; } else { throw "Unable to attach sketch to the processing instance"; } };
this.toString = function() { var i; var code = "((function(Sketch) {\n"; code += "var sketch = new Sketch(\n" + this.sourceCode + ");\n"; for(i in this.options) { if(this.options.hasOwnProperty(i)) { var value = this.options[i]; code += "sketch.options." + i + " = " + (typeof value === 'string' ? '\"' + value + '\"' : "" + value) + ";\n"; } } for(i in this.imageCache) { if(this.options.hasOwnProperty(i)) { code += "sketch.imageCache.add(\"" + i + "\");\n"; } } // TODO serialize fonts code += "return sketch;\n})(Processing.Sketch))"; return code; }; };
/** * aggregate all source code into a single file, then rewrite that * source and bind to canvas via new Processing(canvas, sourcestring). * @param {CANVAS} canvas The html canvas element to bind to * @param {String[]} source The array of files that must be loaded */ var loadSketchFromSources = Processing.loadSketchFromSources = function(canvas, sources) { var code = [], errors = [], sourcesCount = sources.length, loaded = 0;
function ajaxAsync(url, callback) { var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if (xhr.readyState === 4) { var error; if (xhr.status !== 200 && xhr.status !== 0) { error = "Invalid XHR status " + xhr.status; } else if (xhr.responseText === "") { // Give a hint when loading fails due to same-origin issues on file:/// urls if ( ("withCredentials" in new XMLHttpRequest()) && (new XMLHttpRequest()).withCredentials === false && window.location.protocol === "file:" ) { error = "XMLHttpRequest failure, possibly due to a same-origin policy violation. You can try loading this page in another browser, or load it from http://localhost using a local webserver. See the Processing.js README for a more detailed explanation of this problem and solutions."; } else { error = "File is empty."; } }
callback(xhr.responseText, error); } }; xhr.open("GET", url, true); if (xhr.overrideMimeType) { xhr.overrideMimeType("application/json"); } xhr.setRequestHeader("If-Modified-Since", "Fri, 01 Jan 1960 00:00:00 GMT"); // no cache xhr.send(null); }
function loadBlock(index, filename) { function callback(block, error) { code[index] = block; ++loaded; if (error) { errors.push(filename + " ==> " + error); } if (loaded === sourcesCount) { if (errors.length === 0) { try { return new Processing(canvas, code.join("\n")); } catch(e) { console.log("Processing.js: Unable to execute pjs sketch."); throw e; } } else { throw "Processing.js: Unable to load pjs sketch files: " + errors.join("\n"); } } } if (filename.charAt(0) === '#') { // trying to get script from the element var scriptElement = document.getElementById(filename.substring(1)); if (scriptElement) { callback(scriptElement.text || scriptElement.textContent); } else { callback("", "Unable to load pjs sketch: element with id \'" + filename.substring(1) + "\' was not found"); } return; }
ajaxAsync(filename, callback); }
for (var i = 0; i < sourcesCount; ++i) { loadBlock(i, sources[i]); } };
/** * Automatic initialization function. */ var init = function() { document.removeEventListener('DOMContentLoaded', init, false);
// before running through init, clear the instances list, to prevent // sketch duplication when page content is dynamically swapped without // swapping out processing.js processingInstances = []; Processing.instances = processingInstances;
var canvas = document.getElementsByTagName('canvas'), filenames;
for (var i = 0, l = canvas.length; i < l; i++) { // datasrc and data-src are deprecated. var processingSources = canvas[i].getAttribute('data-processing-sources'); if (processingSources === null) { // Temporary fallback for datasrc and data-src processingSources = canvas[i].getAttribute('data-src'); if (processingSources === null) { processingSources = canvas[i].getAttribute('datasrc'); } } if (processingSources) { filenames = processingSources.split(/\s+/g); for (var j = 0; j < filenames.length;) { if (filenames[j]) { j++; } else { filenames.splice(j, 1); } } loadSketchFromSources(canvas[i], filenames); } }
// also process all <script>-indicated sketches, if there are any var s, last, source, instance, nodelist = document.getElementsByTagName('script'), scripts=[];
// snapshot the DOM, as the nodelist is only a DOM view, and is // updated instantly when a script element is added or removed. for (s = nodelist.length - 1; s >= 0; s--) { scripts.push(nodelist[s]); }
// iterate over all script elements to see if they contain Processing code for (s = 0, last = scripts.length; s < last; s++) { var script = scripts[s]; if (!script.getAttribute) { continue; }
var type = script.getAttribute("type"); if (type && (type.toLowerCase() === "text/processing" || type.toLowerCase() === "application/processing")) { var target = script.getAttribute("data-processing-target"); canvas = undef; if (target) { canvas = document.getElementById(target); } else { var nextSibling = script.nextSibling; while (nextSibling && nextSibling.nodeType !== 1) { nextSibling = nextSibling.nextSibling; } if (nextSibling && nextSibling.nodeName.toLowerCase() === "canvas") { canvas = nextSibling; } }
if (canvas) { if (script.getAttribute("src")) { filenames = script.getAttribute("src").split(/\s+/); loadSketchFromSources(canvas, filenames); continue; } source = script.textContent || script.text; instance = new Processing(canvas, source); } } } };
/** * automatic loading of all sketches on the page */ document.addEventListener('DOMContentLoaded', init, false);
/** * Make Processing run through init after already having * been set up for a page. This function exists mostly for pages * that swap content in/out without reloading a page. */ Processing.reload = function() { if (processingInstances.length > 0) { // unload sketches for (var i = processingInstances.length - 1; i >= 0; i--) { if (processingInstances[i]) { processingInstances[i].exit(); } } } // rerun init() to scan the DOM for sketches init(); };
/** * Disable the automatic loading of all sketches on the page. * This will work as long as it's issued before DOMContentLoaded. */ Processing.disableInit = function() { document.removeEventListener('DOMContentLoaded', init, false); };
// done. return Processing;
}; },{}],7:[function(require,module,exports){ /**
* 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. */
module.exports = 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;
};
},{}],8:[function(require,module,exports){ /**
* 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. */
module.exports = function virtHashCode(obj, undef) {
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;
};
},{}],9:[function(require,module,exports){ /**
* 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 */
module.exports = function(options) {
var virtHashCode = options.virtHashCode, virtEquals = options.virtEquals;
function Iterator(array) { var index = -1; this.hasNext = function() { return (index + 1) < array.length; };
this.next = function() { return array[++index]; };
this.remove = function() { array.splice(index--, 1); }; }
function ArrayList(a) { var array = [];
if (a && a.toArray) { array = a.toArray(); }
/** * @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.lastIndexOf() Returns the index of the last occurrence of the specified element in this list, * or -1 if this list does not contain the element. More formally, returns the highest index i such that * (o==null ? get(i)==null : o.equals(get(i))), or -1 if there is no such index. * * @param {Object} item element to search for. * * @returns {int} the index of the last occurrence of the specified element in this list, or -1 if this list does not contain the element. */ this.lastIndexOf = function(item) { for (var i = array.length-1; i >= 0; --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.removeAll Removes from this List all of the elements from * the current ArrayList which are present in the passed in paramater ArrayList 'c'. * Shifts any succeeding elements to the left (reduces their index). * * @param {ArrayList} the ArrayList to compare to the current ArrayList * * @returns {boolean} true if the ArrayList had an element removed; false otherwise */ this.removeAll = function(c) { var i, x, item, newList = new ArrayList(); newList.addAll(this); this.clear(); // For every item that exists in the original ArrayList and not in the c ArrayList // copy it into the empty 'this' ArrayList to create the new 'this' Array. for (i = 0, x = 0; i < newList.size(); i++) { item = newList.get(i); if (!c.contains(item)) { this.add(x++, item); } } if (this.size() < newList.size()) { 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;
};
},{}],10:[function(require,module,exports){ module.exports = (function(charMap, undef) {
var Char = function(chr) { if (typeof chr === 'string' && chr.length === 1) { this.code = chr.charCodeAt(0); } else if (typeof chr === 'number') { this.code = chr; } else if (chr instanceof Char) { this.code = chr; } else { this.code = NaN; } return (charMap[this.code] === undef) ? charMap[this.code] = this : charMap[this.code]; };
Char.prototype.toString = function() { return String.fromCharCode(this.code); };
Char.prototype.valueOf = function() { return this.code; };
return Char;
}({}));
},{}],11:[function(require,module,exports){ /**
- 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
- /
module.exports = function(options) {
var virtHashCode = options.virtHashCode, virtEquals = options.virtEquals;
/** * @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] !== undefined) { 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 === undefined) { buckets[index] = bucket = []; } bucket.push(allEntries[j]); } }
function Iterator(conversion, removeItem) { var bucketIndex = 0; var itemIndex = -1; var endOfBuckets = false; var currentItem;
function findNext() { while (!endOfBuckets) { ++itemIndex; if (bucketIndex >= buckets.length) { endOfBuckets = true; } else if (buckets[bucketIndex] === undefined || 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() { currentItem = conversion(buckets[bucketIndex][itemIndex]); findNext(); return currentItem; };
/* * @member Iterator * Remove the current item */ this.remove = function() { if (currentItem !== undefined) { removeItem(currentItem); --itemIndex; findNext(); } };
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 === undefined); };
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 === undefined) { 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 === undefined) { 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 === undefined) { 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 === undefined) { ++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 === undefined) { 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] = undefined; } 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;
};
},{}],12:[function(require,module,exports){ // module export module.exports = function(options,undef) {
var window = options.Browser.window, document = options.Browser.document, noop = options.noop;
/** * [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;
// 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 + "
" + 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) { noop(); } var ascent = Math.round(i / w4);
// Finding the descent uses a reverse scanline i = len - 1; while (--i > 0 && pixelData[i] === 255) { noop(); } 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);
// if we're caching, cache the context used for this pfont if (pfont.caching) { return ctx; } }
/** * Constructor for a system or from-file (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 = computeFontMetrics(this); this.css = this.getCSSDefinition(); if (this.context2d) { this.context2d.font = this.css; } }
/** * regulates whether or not we're caching the canvas * 2d context for quick text width computation. */ PFont.prototype.caching = true;
/** * 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(" "); };
/** * Rely on the cached context2d measureText function. */ PFont.prototype.measureTextWidth = function(string) { return this.context2d.measureText(string).width; };
/** * FALLBACK FUNCTION -- replaces Pfont.prototype.measureTextWidth * when the font cache becomes too large. This contructs a new * canvas 2d context object for calling measureText on. */ PFont.prototype.measureTextWidthFallback = function(string) { var canvas = document.createElement("canvas"), ctx = canvas.getContext("2d"); ctx.font = this.css; return ctx.measureText(string).width; };
/** * Global "loaded fonts" list, internal to PFont */ PFont.PFontCache = { length: 0 };
/** * 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) { // round fontSize to one decimal point fontSize = ((fontSize*10)+0.5|0)/10; var cache = PFont.PFontCache, idx = fontName+"/"+fontSize; if (!cache[idx]) { cache[idx] = new PFont(fontName, fontSize); cache.length++;
// FALLBACK FUNCTIONALITY 1: // If the cache has become large, switch over from full caching // to caching only the static metrics for each new font request. if (cache.length === 50) { PFont.prototype.measureTextWidth = PFont.prototype.measureTextWidthFallback; PFont.prototype.caching = false; // clear contexts stored for each cached font var entry; for (entry in cache) { if (entry !== "length") { cache[entry].context2d = null; } } return new PFont(fontName, fontSize); }
// FALLBACK FUNCTIONALITY 2: // If the cache has become too large, switch off font caching entirely. if (cache.length === 400) { PFont.PFontCache = {}; PFont.get = PFont.getFallback; return new PFont(fontName, fontSize); } } return cache[idx]; };
/** * FALLBACK FUNCTION -- replaces PFont.get when the font cache * becomes too large. This function bypasses font caching entirely. */ PFont.getFallback = function(fontName, fontSize) { return new PFont(fontName, fontSize); };
/** * 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;'; 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;"; element.style.fontFamily = '"' + fontName + '", "PjsEmptyFont", fantasy'; element.innerHTML = "AAAAAAAA"; document.body.appendChild(element); this.fontList.push(element); } };
return PFont;
}; },{}],13:[function(require,module,exports){ module.exports = function(options, undef) {
// FIXME: hack var p = options.p;
/** * PMatrix2D is a 3x2 affine matrix implementation. The constructor accepts another PMatrix2D or a list of six float elements. * If no parameters are provided the matrix is set to the identity matrix. * * @param {PMatrix2D} matrix the initial matrix to set to * @param {float} m00 the first element of the matrix * @param {float} m01 the second element of the matrix * @param {float} m02 the third element of the matrix * @param {float} m10 the fourth element of the matrix * @param {float} m11 the fifth element of the matrix * @param {float} m12 the sixth element of the matrix */ var PMatrix2D = function() { if (arguments.length === 0) { this.reset(); } else if (arguments.length === 1 && arguments[0] instanceof PMatrix2D) { this.set(arguments[0].array()); } else if (arguments.length === 6) { this.set(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]); } };
/** * PMatrix2D methods */ PMatrix2D.prototype = { /** * @member PMatrix2D * The set() function sets the matrix elements. The function accepts either another PMatrix2D, an array of elements, or a list of six floats. * * @param {PMatrix2D} matrix the matrix to set this matrix to * @param {float[]} elements an array of elements to set this matrix to * @param {float} m00 the first element of the matrix * @param {float} m01 the third element of the matrix * @param {float} m10 the fourth element of the matrix * @param {float} m11 the fith element of the matrix * @param {float} m12 the sixth element of the matrix */ set: function() { if (arguments.length === 6) { var a = arguments; this.set([a[0], a[1], a[2], a[3], a[4], a[5]]); } else if (arguments.length === 1 && arguments[0] instanceof PMatrix2D) { this.elements = arguments[0].array(); } else if (arguments.length === 1 && arguments[0] instanceof Array) { this.elements = arguments[0].slice(); } }, /** * @member PMatrix2D * The get() function returns a copy of this PMatrix2D. * * @return {PMatrix2D} a copy of this PMatrix2D */ get: function() { var outgoing = new PMatrix2D(); outgoing.set(this.elements); return outgoing; }, /** * @member PMatrix2D * The reset() function sets this PMatrix2D to the identity matrix. */ reset: function() { this.set([1, 0, 0, 0, 1, 0]); }, /** * @member PMatrix2D * The array() function returns a copy of the element values. * @addon * * @return {float[]} returns a copy of the element values */ array: function array() { return this.elements.slice(); }, /** * @member PMatrix2D * The translate() function translates this matrix by moving the current coordinates to the location specified by tx and ty. * * @param {float} tx the x-axis coordinate to move to * @param {float} ty the y-axis coordinate to move to */ translate: function(tx, ty) { this.elements[2] = tx * this.elements[0] + ty * this.elements[1] + this.elements[2]; this.elements[5] = tx * this.elements[3] + ty * this.elements[4] + this.elements[5]; }, /** * @member PMatrix2D * The invTranslate() function translates this matrix by moving the current coordinates to the negative location specified by tx and ty. * * @param {float} tx the x-axis coordinate to move to * @param {float} ty the y-axis coordinate to move to */ invTranslate: function(tx, ty) { this.translate(-tx, -ty); }, /** * @member PMatrix2D * The transpose() function is not used in processingjs. */ transpose: function() { // Does nothing in Processing. }, /** * @member PMatrix2D * The mult() function multiplied this matrix. * If two array elements are passed in the function will multiply a two element vector against this matrix. * If target is null or not length four, a new float array will be returned. * The values for vec and target can be the same (though that's less efficient). * If two PVectors are passed in the function multiply the x and y coordinates of a PVector against this matrix. * * @param {PVector} source, target the PVectors used to multiply this matrix * @param {float[]} source, target the arrays used to multiply this matrix * * @return {PVector|float[]} returns a PVector or an array representing the new matrix */ mult: function(source, target) { var x, y; if (source instanceof PVector) { x = source.x; y = source.y; if (!target) { target = new PVector(); } } else if (source instanceof Array) { x = source[0]; y = source[1]; if (!target) { target = []; } } if (target instanceof Array) { target[0] = this.elements[0] * x + this.elements[1] * y + this.elements[2]; target[1] = this.elements[3] * x + this.elements[4] * y + this.elements[5]; } else if (target instanceof PVector) { target.x = this.elements[0] * x + this.elements[1] * y + this.elements[2]; target.y = this.elements[3] * x + this.elements[4] * y + this.elements[5]; target.z = 0; } return target; }, /** * @member PMatrix2D * The multX() function calculates the x component of a vector from a transformation. * * @param {float} x the x component of the vector being transformed * @param {float} y the y component of the vector being transformed * * @return {float} returnes the result of the calculation */ multX: function(x, y) { return (x * this.elements[0] + y * this.elements[1] + this.elements[2]); }, /** * @member PMatrix2D * The multY() function calculates the y component of a vector from a transformation. * * @param {float} x the x component of the vector being transformed * @param {float} y the y component of the vector being transformed * * @return {float} returnes the result of the calculation */ multY: function(x, y) { return (x * this.elements[3] + y * this.elements[4] + this.elements[5]); }, /** * @member PMatrix2D * The skewX() function skews the matrix along the x-axis the amount specified by the angle parameter. * Angles should be specified in radians (values from 0 to PI*2) or converted to radians with the radians() function. * * @param {float} angle angle of skew specified in radians */ skewX: function(angle) { this.apply(1, 0, 1, angle, 0, 0); }, /** * @member PMatrix2D * The skewY() function skews the matrix along the y-axis the amount specified by the angle parameter. * Angles should be specified in radians (values from 0 to PI*2) or converted to radians with the radians() function. * * @param {float} angle angle of skew specified in radians */ skewY: function(angle) { this.apply(1, 0, 1, 0, angle, 0); }, /** * @member PMatrix2D * The shearX() function shears the matrix along the x-axis the amount specified by the angle parameter. * Angles should be specified in radians (values from 0 to PI*2) or converted to radians with the radians() function. * * @param {float} angle angle of skew specified in radians */ shearX: function(angle) { this.apply(1, 0, 1, Math.tan(angle) , 0, 0); }, /** * @member PMatrix2D * The shearY() function shears the matrix along the y-axis the amount specified by the angle parameter. * Angles should be specified in radians (values from 0 to PI*2) or converted to radians with the radians() function. * * @param {float} angle angle of skew specified in radians */ shearY: function(angle) { this.apply(1, 0, 1, 0, Math.tan(angle), 0); }, /** * @member PMatrix2D * The determinant() function calvculates the determinant of this matrix. * * @return {float} the determinant of the matrix */ determinant: function() { return (this.elements[0] * this.elements[4] - this.elements[1] * this.elements[3]); }, /** * @member PMatrix2D * The invert() function inverts this matrix * * @return {boolean} true if successful */ invert: function() { var d = this.determinant(); if (Math.abs( d ) > PConstants.MIN_INT) { var old00 = this.elements[0]; var old01 = this.elements[1]; var old02 = this.elements[2]; var old10 = this.elements[3]; var old11 = this.elements[4]; var old12 = this.elements[5]; this.elements[0] = old11 / d; this.elements[3] = -old10 / d; this.elements[1] = -old01 / d; this.elements[4] = old00 / d; this.elements[2] = (old01 * old12 - old11 * old02) / d; this.elements[5] = (old10 * old02 - old00 * old12) / d; return true; } return false; }, /** * @member PMatrix2D * The scale() function increases or decreases the size of a shape by expanding and contracting vertices. When only one parameter is specified scale will occur in all dimensions. * This is equivalent to a two parameter call. * * @param {float} sx the amount to scale on the x-axis * @param {float} sy the amount to scale on the y-axis */ scale: function(sx, sy) { if (sx && !sy) { sy = sx; } if (sx && sy) { this.elements[0] *= sx; this.elements[1] *= sy; this.elements[3] *= sx; this.elements[4] *= sy; } }, /** * @member PMatrix2D * The invScale() function decreases or increases the size of a shape by contracting and expanding vertices. When only one parameter is specified scale will occur in all dimensions. * This is equivalent to a two parameter call. * * @param {float} sx the amount to scale on the x-axis * @param {float} sy the amount to scale on the y-axis */ invScale: function(sx, sy) { if (sx && !sy) { sy = sx; } this.scale(1 / sx, 1 / sy); }, /** * @member PMatrix2D * The apply() function multiplies the current matrix by the one specified through the parameters. Note that either a PMatrix2D or a list of floats can be passed in. * * @param {PMatrix2D} matrix the matrix to apply this matrix to * @param {float} m00 the first element of the matrix * @param {float} m01 the third element of the matrix * @param {float} m10 the fourth element of the matrix * @param {float} m11 the fith element of the matrix * @param {float} m12 the sixth element of the matrix */ apply: function() { var source; if (arguments.length === 1 && arguments[0] instanceof PMatrix2D) { source = arguments[0].array(); } else if (arguments.length === 6) { source = Array.prototype.slice.call(arguments); } else if (arguments.length === 1 && arguments[0] instanceof Array) { source = arguments[0]; }
var result = [0, 0, this.elements[2], 0, 0, this.elements[5]]; var e = 0; for (var row = 0; row < 2; row++) { for (var col = 0; col < 3; col++, e++) { result[e] += this.elements[row * 3 + 0] * source[col + 0] + this.elements[row * 3 + 1] * source[col + 3]; } } this.elements = result.slice(); }, /** * @member PMatrix2D * The preApply() function applies another matrix to the left of this one. Note that either a PMatrix2D or elements of a matrix can be passed in. * * @param {PMatrix2D} matrix the matrix to apply this matrix to * @param {float} m00 the first element of the matrix * @param {float} m01 the third element of the matrix * @param {float} m10 the fourth element of the matrix * @param {float} m11 the fith element of the matrix * @param {float} m12 the sixth element of the matrix */ preApply: function() { var source; if (arguments.length === 1 && arguments[0] instanceof PMatrix2D) { source = arguments[0].array(); } else if (arguments.length === 6) { source = Array.prototype.slice.call(arguments); } else if (arguments.length === 1 && arguments[0] instanceof Array) { source = arguments[0]; } var result = [0, 0, source[2], 0, 0, source[5]]; result[2] = source[2] + this.elements[2] * source[0] + this.elements[5] * source[1]; result[5] = source[5] + this.elements[2] * source[3] + this.elements[5] * source[4]; result[0] = this.elements[0] * source[0] + this.elements[3] * source[1]; result[3] = this.elements[0] * source[3] + this.elements[3] * source[4]; result[1] = this.elements[1] * source[0] + this.elements[4] * source[1]; result[4] = this.elements[1] * source[3] + this.elements[4] * source[4]; this.elements = result.slice(); }, /** * @member PMatrix2D * The rotate() function rotates the matrix. * * @param {float} angle the angle of rotation in radiants */ rotate: function(angle) { var c = Math.cos(angle); var s = Math.sin(angle); var temp1 = this.elements[0]; var temp2 = this.elements[1]; this.elements[0] = c * temp1 + s * temp2; this.elements[1] = -s * temp1 + c * temp2; temp1 = this.elements[3]; temp2 = this.elements[4]; this.elements[3] = c * temp1 + s * temp2; this.elements[4] = -s * temp1 + c * temp2; }, /** * @member PMatrix2D * The rotateZ() function rotates the matrix. * * @param {float} angle the angle of rotation in radiants */ rotateZ: function(angle) { this.rotate(angle); }, /** * @member PMatrix2D * The invRotateZ() function rotates the matrix in opposite direction. * * @param {float} angle the angle of rotation in radiants */ invRotateZ: function(angle) { this.rotateZ(angle - Math.PI); }, /** * @member PMatrix2D * The print() function prints out the elements of this matrix */ print: function() { var digits = printMatrixHelper(this.elements); var output = "" + p.nfs(this.elements[0], digits, 4) + " " + p.nfs(this.elements[1], digits, 4) + " " + p.nfs(this.elements[2], digits, 4) + "\n" + p.nfs(this.elements[3], digits, 4) + " " + p.nfs(this.elements[4], digits, 4) + " " + p.nfs(this.elements[5], digits, 4) + "\n\n"; p.println(output); } };
return PMatrix2D;
};
},{}],14:[function(require,module,exports){ module.exports = function(options, undef) {
// FIXME: hack var p = options.p;
/** * PMatrix3D is a 4x4 matrix implementation. The constructor accepts another PMatrix3D or a list of six or sixteen float elements. * If no parameters are provided the matrix is set to the identity matrix. */ var PMatrix3D = function() { // When a matrix is created, it is set to an identity matrix this.reset(); };
/** * PMatrix3D methods */ PMatrix3D.prototype = { /** * @member PMatrix2D * The set() function sets the matrix elements. The function accepts either another PMatrix3D, an array of elements, or a list of six or sixteen floats. * * @param {PMatrix3D} matrix the initial matrix to set to * @param {float[]} elements an array of elements to set this matrix to * @param {float} m00 the first element of the matrix * @param {float} m01 the second element of the matrix * @param {float} m02 the third element of the matrix * @param {float} m03 the fourth element of the matrix * @param {float} m10 the fifth element of the matrix * @param {float} m11 the sixth element of the matrix * @param {float} m12 the seventh element of the matrix * @param {float} m13 the eight element of the matrix * @param {float} m20 the nineth element of the matrix * @param {float} m21 the tenth element of the matrix * @param {float} m22 the eleventh element of the matrix * @param {float} m23 the twelveth element of the matrix * @param {float} m30 the thirteenth element of the matrix * @param {float} m31 the fourtheenth element of the matrix * @param {float} m32 the fivetheenth element of the matrix * @param {float} m33 the sixteenth element of the matrix */ set: function() { if (arguments.length === 16) { this.elements = Array.prototype.slice.call(arguments); } else if (arguments.length === 1 && arguments[0] instanceof PMatrix3D) { this.elements = arguments[0].array(); } else if (arguments.length === 1 && arguments[0] instanceof Array) { this.elements = arguments[0].slice(); } }, /** * @member PMatrix3D * The get() function returns a copy of this PMatrix3D. * * @return {PMatrix3D} a copy of this PMatrix3D */ get: function() { var outgoing = new PMatrix3D(); outgoing.set(this.elements); return outgoing; }, /** * @member PMatrix3D * The reset() function sets this PMatrix3D to the identity matrix. */ reset: function() { this.elements = [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]; }, /** * @member PMatrix3D * The array() function returns a copy of the element values. * @addon * * @return {float[]} returns a copy of the element values */ array: function array() { return this.elements.slice(); }, /** * @member PMatrix3D * The translate() function translates this matrix by moving the current coordinates to the location specified by tx, ty, and tz. * * @param {float} tx the x-axis coordinate to move to * @param {float} ty the y-axis coordinate to move to * @param {float} tz the z-axis coordinate to move to */ translate: function(tx, ty, tz) { if (tz === undef) { tz = 0; }
this.elements[3] += tx * this.elements[0] + ty * this.elements[1] + tz * this.elements[2]; this.elements[7] += tx * this.elements[4] + ty * this.elements[5] + tz * this.elements[6]; this.elements[11] += tx * this.elements[8] + ty * this.elements[9] + tz * this.elements[10]; this.elements[15] += tx * this.elements[12] + ty * this.elements[13] + tz * this.elements[14]; }, /** * @member PMatrix3D * The transpose() function transpose this matrix. */ transpose: function() { var temp = this.elements[4]; this.elements[4] = this.elements[1]; this.elements[1] = temp;
temp = this.elements[8]; this.elements[8] = this.elements[2]; this.elements[2] = temp;
temp = this.elements[6]; this.elements[6] = this.elements[9]; this.elements[9] = temp;
temp = this.elements[3]; this.elements[3] = this.elements[12]; this.elements[12] = temp;
temp = this.elements[7]; this.elements[7] = this.elements[13]; this.elements[13] = temp;
temp = this.elements[11]; this.elements[11] = this.elements[14]; this.elements[14] = temp; }, /** * @member PMatrix3D * The mult() function multiplied this matrix. * If two array elements are passed in the function will multiply a two element vector against this matrix. * If target is null or not length four, a new float array will be returned. * The values for vec and target can be the same (though that's less efficient). * If two PVectors are passed in the function multiply the x and y coordinates of a PVector against this matrix. * * @param {PVector} source, target the PVectors used to multiply this matrix * @param {float[]} source, target the arrays used to multiply this matrix * * @return {PVector|float[]} returns a PVector or an array representing the new matrix */ mult: function(source, target) { var x, y, z, w; if (source instanceof PVector) { x = source.x; y = source.y; z = source.z; w = 1; if (!target) { target = new PVector(); } } else if (source instanceof Array) { x = source[0]; y = source[1]; z = source[2]; w = source[3] || 1;
if ( !target || (target.length !== 3 && target.length !== 4) ) { target = [0, 0, 0]; } }
if (target instanceof Array) { if (target.length === 3) { target[0] = this.elements[0] * x + this.elements[1] * y + this.elements[2] * z + this.elements[3]; target[1] = this.elements[4] * x + this.elements[5] * y + this.elements[6] * z + this.elements[7]; target[2] = this.elements[8] * x + this.elements[9] * y + this.elements[10] * z + this.elements[11]; } else if (target.length === 4) { target[0] = this.elements[0] * x + this.elements[1] * y + this.elements[2] * z + this.elements[3] * w; target[1] = this.elements[4] * x + this.elements[5] * y + this.elements[6] * z + this.elements[7] * w; target[2] = this.elements[8] * x + this.elements[9] * y + this.elements[10] * z + this.elements[11] * w; target[3] = this.elements[12] * x + this.elements[13] * y + this.elements[14] * z + this.elements[15] * w; } } if (target instanceof PVector) { target.x = this.elements[0] * x + this.elements[1] * y + this.elements[2] * z + this.elements[3]; target.y = this.elements[4] * x + this.elements[5] * y + this.elements[6] * z + this.elements[7]; target.z = this.elements[8] * x + this.elements[9] * y + this.elements[10] * z + this.elements[11]; } return target; }, /** * @member PMatrix3D * The preApply() function applies another matrix to the left of this one. Note that either a PMatrix3D or elements of a matrix can be passed in. * * @param {PMatrix3D} matrix the matrix to apply this matrix to * @param {float} m00 the first element of the matrix * @param {float} m01 the second element of the matrix * @param {float} m02 the third element of the matrix * @param {float} m03 the fourth element of the matrix * @param {float} m10 the fifth element of the matrix * @param {float} m11 the sixth element of the matrix * @param {float} m12 the seventh element of the matrix * @param {float} m13 the eight element of the matrix * @param {float} m20 the nineth element of the matrix * @param {float} m21 the tenth element of the matrix * @param {float} m22 the eleventh element of the matrix * @param {float} m23 the twelveth element of the matrix * @param {float} m30 the thirteenth element of the matrix * @param {float} m31 the fourtheenth element of the matrix * @param {float} m32 the fivetheenth element of the matrix * @param {float} m33 the sixteenth element of the matrix */ preApply: function() { var source; if (arguments.length === 1 && arguments[0] instanceof PMatrix3D) { source = arguments[0].array(); } else if (arguments.length === 16) { source = Array.prototype.slice.call(arguments); } else if (arguments.length === 1 && arguments[0] instanceof Array) { source = arguments[0]; }
var result = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var e = 0; for (var row = 0; row < 4; row++) { for (var col = 0; col < 4; col++, e++) { result[e] += this.elements[col + 0] * source[row * 4 + 0] + this.elements[col + 4] * source[row * 4 + 1] + this.elements[col + 8] * source[row * 4 + 2] + this.elements[col + 12] * source[row * 4 + 3]; } } this.elements = result.slice(); }, /** * @member PMatrix3D * The apply() function multiplies the current matrix by the one specified through the parameters. Note that either a PMatrix3D or a list of floats can be passed in. * * @param {PMatrix3D} matrix the matrix to apply this matrix to * @param {float} m00 the first element of the matrix * @param {float} m01 the second element of the matrix * @param {float} m02 the third element of the matrix * @param {float} m03 the fourth element of the matrix * @param {float} m10 the fifth element of the matrix * @param {float} m11 the sixth element of the matrix * @param {float} m12 the seventh element of the matrix * @param {float} m13 the eight element of the matrix * @param {float} m20 the nineth element of the matrix * @param {float} m21 the tenth element of the matrix * @param {float} m22 the eleventh element of the matrix * @param {float} m23 the twelveth element of the matrix * @param {float} m30 the thirteenth element of the matrix * @param {float} m31 the fourtheenth element of the matrix * @param {float} m32 the fivetheenth element of the matrix * @param {float} m33 the sixteenth element of the matrix */ apply: function() { var source; if (arguments.length === 1 && arguments[0] instanceof PMatrix3D) { source = arguments[0].array(); } else if (arguments.length === 16) { source = Array.prototype.slice.call(arguments); } else if (arguments.length === 1 && arguments[0] instanceof Array) { source = arguments[0]; }
var result = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var e = 0; for (var row = 0; row < 4; row++) { for (var col = 0; col < 4; col++, e++) { result[e] += this.elements[row * 4 + 0] * source[col + 0] + this.elements[row * 4 + 1] * source[col + 4] + this.elements[row * 4 + 2] * source[col + 8] + this.elements[row * 4 + 3] * source[col + 12]; } } this.elements = result.slice(); }, /** * @member PMatrix3D * The rotate() function rotates the matrix. * * @param {float} angle the angle of rotation in radiants */ rotate: function(angle, v0, v1, v2) { if (!v1) { this.rotateZ(angle); } else { // TODO should make sure this vector is normalized var c = Math.cos(angle); var s = Math.sin(angle); var t = 1.0 - c;
this.apply((t * v0 * v0) + c, (t * v0 * v1) - (s * v2), (t * v0 * v2) + (s * v1), 0, (t * v0 * v1) + (s * v2), (t * v1 * v1) + c, (t * v1 * v2) - (s * v0), 0, (t * v0 * v2) - (s * v1), (t * v1 * v2) + (s * v0), (t * v2 * v2) + c, 0, 0, 0, 0, 1); } }, /** * @member PMatrix3D * The invApply() function applies the inverted matrix to this matrix. * * @param {float} m00 the first element of the matrix * @param {float} m01 the second element of the matrix * @param {float} m02 the third element of the matrix * @param {float} m03 the fourth element of the matrix * @param {float} m10 the fifth element of the matrix * @param {float} m11 the sixth element of the matrix * @param {float} m12 the seventh element of the matrix * @param {float} m13 the eight element of the matrix * @param {float} m20 the nineth element of the matrix * @param {float} m21 the tenth element of the matrix * @param {float} m22 the eleventh element of the matrix * @param {float} m23 the twelveth element of the matrix * @param {float} m30 the thirteenth element of the matrix * @param {float} m31 the fourtheenth element of the matrix * @param {float} m32 the fivetheenth element of the matrix * @param {float} m33 the sixteenth element of the matrix * * @return {boolean} returns true if the operation was successful. */ invApply: function() { if (inverseCopy === undef) { inverseCopy = new PMatrix3D(); } var a = arguments; inverseCopy.set(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15]);
if (!inverseCopy.invert()) { return false; } this.preApply(inverseCopy); return true; }, /** * @member PMatrix3D * The rotateZ() function rotates the matrix. * * @param {float} angle the angle of rotation in radiants */ rotateX: function(angle) { var c = Math.cos(angle); var s = Math.sin(angle); this.apply([1, 0, 0, 0, 0, c, -s, 0, 0, s, c, 0, 0, 0, 0, 1]); }, /** * @member PMatrix3D * The rotateY() function rotates the matrix. * * @param {float} angle the angle of rotation in radiants */ rotateY: function(angle) { var c = Math.cos(angle); var s = Math.sin(angle); this.apply([c, 0, s, 0, 0, 1, 0, 0, -s, 0, c, 0, 0, 0, 0, 1]); }, /** * @member PMatrix3D * The rotateZ() function rotates the matrix. * * @param {float} angle the angle of rotation in radiants */ rotateZ: function(angle) { var c = Math.cos(angle); var s = Math.sin(angle); this.apply([c, -s, 0, 0, s, c, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]); }, /** * @member PMatrix3D * The scale() function increases or decreases the size of a matrix by expanding and contracting vertices. When only one parameter is specified scale will occur in all dimensions. * This is equivalent to a three parameter call. * * @param {float} sx the amount to scale on the x-axis * @param {float} sy the amount to scale on the y-axis * @param {float} sz the amount to scale on the z-axis */ scale: function(sx, sy, sz) { if (sx && !sy && !sz) { sy = sz = sx; } else if (sx && sy && !sz) { sz = 1; }
if (sx && sy && sz) { this.elements[0] *= sx; this.elements[1] *= sy; this.elements[2] *= sz; this.elements[4] *= sx; this.elements[5] *= sy; this.elements[6] *= sz; this.elements[8] *= sx; this.elements[9] *= sy; this.elements[10] *= sz; this.elements[12] *= sx; this.elements[13] *= sy; this.elements[14] *= sz; } }, /** * @member PMatrix3D * The skewX() function skews the matrix along the x-axis the amount specified by the angle parameter. * Angles should be specified in radians (values from 0 to PI*2) or converted to radians with the radians() function. * * @param {float} angle angle of skew specified in radians */ skewX: function(angle) { var t = Math.tan(angle); this.apply(1, t, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); }, /** * @member PMatrix3D * The skewY() function skews the matrix along the y-axis the amount specified by the angle parameter. * Angles should be specified in radians (values from 0 to PI*2) or converted to radians with the radians() function. * * @param {float} angle angle of skew specified in radians */ skewY: function(angle) { var t = Math.tan(angle); this.apply(1, 0, 0, 0, t, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); }, /** * @member PMatrix3D * The shearX() function shears the matrix along the x-axis the amount specified by the angle parameter. * Angles should be specified in radians (values from 0 to PI*2) or converted to radians with the radians() function. * * @param {float} angle angle of shear specified in radians */ shearX: function(angle) { var t = Math.tan(angle); this.apply(1, t, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); }, /** * @member PMatrix3D * The shearY() function shears the matrix along the y-axis the amount specified by the angle parameter. * Angles should be specified in radians (values from 0 to PI*2) or converted to radians with the radians() function. * * @param {float} angle angle of shear specified in radians */ shearY: function(angle) { var t = Math.tan(angle); this.apply(1, 0, 0, 0, t, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); }, multX: function(x, y, z, w) { if (!z) { return this.elements[0] * x + this.elements[1] * y + this.elements[3]; } if (!w) { return this.elements[0] * x + this.elements[1] * y + this.elements[2] * z + this.elements[3]; } return this.elements[0] * x + this.elements[1] * y + this.elements[2] * z + this.elements[3] * w; }, multY: function(x, y, z, w) { if (!z) { return this.elements[4] * x + this.elements[5] * y + this.elements[7]; } if (!w) { return this.elements[4] * x + this.elements[5] * y + this.elements[6] * z + this.elements[7]; } return this.elements[4] * x + this.elements[5] * y + this.elements[6] * z + this.elements[7] * w; }, multZ: function(x, y, z, w) { if (!w) { return this.elements[8] * x + this.elements[9] * y + this.elements[10] * z + this.elements[11]; } return this.elements[8] * x + this.elements[9] * y + this.elements[10] * z + this.elements[11] * w; }, multW: function(x, y, z, w) { if (!w) { return this.elements[12] * x + this.elements[13] * y + this.elements[14] * z + this.elements[15]; } return this.elements[12] * x + this.elements[13] * y + this.elements[14] * z + this.elements[15] * w; }, /** * @member PMatrix3D * The invert() function inverts this matrix * * @return {boolean} true if successful */ invert: function() { var fA0 = this.elements[0] * this.elements[5] - this.elements[1] * this.elements[4]; var fA1 = this.elements[0] * this.elements[6] - this.elements[2] * this.elements[4]; var fA2 = this.elements[0] * this.elements[7] - this.elements[3] * this.elements[4]; var fA3 = this.elements[1] * this.elements[6] - this.elements[2] * this.elements[5]; var fA4 = this.elements[1] * this.elements[7] - this.elements[3] * this.elements[5]; var fA5 = this.elements[2] * this.elements[7] - this.elements[3] * this.elements[6]; var fB0 = this.elements[8] * this.elements[13] - this.elements[9] * this.elements[12]; var fB1 = this.elements[8] * this.elements[14] - this.elements[10] * this.elements[12]; var fB2 = this.elements[8] * this.elements[15] - this.elements[11] * this.elements[12]; var fB3 = this.elements[9] * this.elements[14] - this.elements[10] * this.elements[13]; var fB4 = this.elements[9] * this.elements[15] - this.elements[11] * this.elements[13]; var fB5 = this.elements[10] * this.elements[15] - this.elements[11] * this.elements[14];
// Determinant var fDet = fA0 * fB5 - fA1 * fB4 + fA2 * fB3 + fA3 * fB2 - fA4 * fB1 + fA5 * fB0;
// Account for a very small value // return false if not successful. if (Math.abs(fDet) <= 1e-9) { return false; }
var kInv = []; kInv[0] = +this.elements[5] * fB5 - this.elements[6] * fB4 + this.elements[7] * fB3; kInv[4] = -this.elements[4] * fB5 + this.elements[6] * fB2 - this.elements[7] * fB1; kInv[8] = +this.elements[4] * fB4 - this.elements[5] * fB2 + this.elements[7] * fB0; kInv[12] = -this.elements[4] * fB3 + this.elements[5] * fB1 - this.elements[6] * fB0; kInv[1] = -this.elements[1] * fB5 + this.elements[2] * fB4 - this.elements[3] * fB3; kInv[5] = +this.elements[0] * fB5 - this.elements[2] * fB2 + this.elements[3] * fB1; kInv[9] = -this.elements[0] * fB4 + this.elements[1] * fB2 - this.elements[3] * fB0; kInv[13] = +this.elements[0] * fB3 - this.elements[1] * fB1 + this.elements[2] * fB0; kInv[2] = +this.elements[13] * fA5 - this.elements[14] * fA4 + this.elements[15] * fA3; kInv[6] = -this.elements[12] * fA5 + this.elements[14] * fA2 - this.elements[15] * fA1; kInv[10] = +this.elements[12] * fA4 - this.elements[13] * fA2 + this.elements[15] * fA0; kInv[14] = -this.elements[12] * fA3 + this.elements[13] * fA1 - this.elements[14] * fA0; kInv[3] = -this.elements[9] * fA5 + this.elements[10] * fA4 - this.elements[11] * fA3; kInv[7] = +this.elements[8] * fA5 - this.elements[10] * fA2 + this.elements[11] * fA1; kInv[11] = -this.elements[8] * fA4 + this.elements[9] * fA2 - this.elements[11] * fA0; kInv[15] = +this.elements[8] * fA3 - this.elements[9] * fA1 + this.elements[10] * fA0;
// Inverse using Determinant var fInvDet = 1.0 / fDet; kInv[0] *= fInvDet; kInv[1] *= fInvDet; kInv[2] *= fInvDet; kInv[3] *= fInvDet; kInv[4] *= fInvDet; kInv[5] *= fInvDet; kInv[6] *= fInvDet; kInv[7] *= fInvDet; kInv[8] *= fInvDet; kInv[9] *= fInvDet; kInv[10] *= fInvDet; kInv[11] *= fInvDet; kInv[12] *= fInvDet; kInv[13] *= fInvDet; kInv[14] *= fInvDet; kInv[15] *= fInvDet;
this.elements = kInv.slice(); return true; }, toString: function() { var str = ""; for (var i = 0; i < 15; i++) { str += this.elements[i] + ", "; } str += this.elements[15]; return str; }, /** * @member PMatrix3D * The print() function prints out the elements of this matrix */ print: function() { var digits = printMatrixHelper(this.elements);
var output = "" + p.nfs(this.elements[0], digits, 4) + " " + p.nfs(this.elements[1], digits, 4) + " " + p.nfs(this.elements[2], digits, 4) + " " + p.nfs(this.elements[3], digits, 4) + "\n" + p.nfs(this.elements[4], digits, 4) + " " + p.nfs(this.elements[5], digits, 4) + " " + p.nfs(this.elements[6], digits, 4) + " " + p.nfs(this.elements[7], digits, 4) + "\n" + p.nfs(this.elements[8], digits, 4) + " " + p.nfs(this.elements[9], digits, 4) + " " + p.nfs(this.elements[10], digits, 4) + " " + p.nfs(this.elements[11], digits, 4) + "\n" + p.nfs(this.elements[12], digits, 4) + " " + p.nfs(this.elements[13], digits, 4) + " " + p.nfs(this.elements[14], digits, 4) + " " + p.nfs(this.elements[15], digits, 4) + "\n\n"; p.println(output); }, invTranslate: function(tx, ty, tz) { this.preApply(1, 0, 0, -tx, 0, 1, 0, -ty, 0, 0, 1, -tz, 0, 0, 0, 1); }, invRotateX: function(angle) { var c = Math.cos(-angle); var s = Math.sin(-angle); this.preApply([1, 0, 0, 0, 0, c, -s, 0, 0, s, c, 0, 0, 0, 0, 1]); }, invRotateY: function(angle) { var c = Math.cos(-angle); var s = Math.sin(-angle); this.preApply([c, 0, s, 0, 0, 1, 0, 0, -s, 0, c, 0, 0, 0, 0, 1]); }, invRotateZ: function(angle) { var c = Math.cos(-angle); var s = Math.sin(-angle); this.preApply([c, -s, 0, 0, s, c, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]); }, invScale: function(x, y, z) { this.preApply([1 / x, 0, 0, 0, 0, 1 / y, 0, 0, 0, 0, 1 / z, 0, 0, 0, 0, 1]); } };
return PMatrix3D;
}; },{}],15:[function(require,module,exports){ module.exports = function(options) {
var PConstants = options.PConstants, PMatrix2D = options.PMatrix2D, PMatrix3D = options.PMatrix3D;
/** * Datatype for storing shapes. Processing can currently load and display SVG (Scalable Vector Graphics) shapes. * Before a shape is used, it must be loaded with the loadShape() function. The shape() function is used to draw the shape to the display window. * The PShape object contain a group of methods, linked below, that can operate on the shape data. *
The loadShape() method supports SVG files created with Inkscape and Adobe Illustrator. * It is not a full SVG implementation, but offers some straightforward support for handling vector data. * * @param {int} family the shape type, one of GROUP, PRIMITIVE, PATH, or GEOMETRY * * @see #shape() * @see #loadShape() * @see #shapeMode() */ var PShape = function(family) { this.family = family || PConstants.GROUP; this.visible = true; this.style = true; this.children = []; this.nameTable = []; this.params = []; this.name = ""; this.image = null; //type PImage this.matrix = null; this.kind = null; this.close = null; this.width = null; this.height = null; this.parent = null; }; /** * PShape methods * missing: findChild(), apply(), contains(), findChild(), getPrimitive(), getParams(), getVertex() , getVertexCount(), * getVertexCode() , getVertexCodes() , getVertexCodeCount(), getVertexX(), getVertexY(), getVertexZ() */ PShape.prototype = { /** * @member PShape * The isVisible() function returns a boolean value "true" if the image is set to be visible, "false" if not. This is modified with the setVisible() parameter. *
The visibility of a shape is usually controlled by whatever program created the SVG file. * For instance, this parameter is controlled by showing or hiding the shape in the layers palette in Adobe Illustrator. * * @return {boolean} returns "true" if the image is set to be visible, "false" if not */ isVisible: function(){ return this.visible; }, /** * @member PShape * The setVisible() function sets the shape to be visible or invisible. This is determined by the value of the visible parameter. *
The visibility of a shape is usually controlled by whatever program created the SVG file. * For instance, this parameter is controlled by showing or hiding the shape in the layers palette in Adobe Illustrator. * * @param {boolean} visible "false" makes the shape invisible and "true" makes it visible */ setVisible: function (visible){ this.visible = visible; }, /** * @member PShape * The disableStyle() function disables the shape's style data and uses Processing's current styles. Styles include attributes such as colors, stroke weight, and stroke joints. * Overrides this shape's style information and uses PGraphics styles and colors. Identical to ignoreStyles(true). Also disables styles for all child shapes. */ disableStyle: function(){ this.style = false; for(var i = 0, j=this.children.length; i<j; i++) { this.children[i].disableStyle(); } }, /** * @member PShape * The enableStyle() function enables the shape's style data and ignores Processing's current styles. Styles include attributes such as colors, stroke weight, and stroke joints. */ enableStyle: function(){ this.style = true; for(var i = 0, j=this.children.length; i<j; i++) { this.children[i].enableStyle(); } }, /** * @member PShape * The getFamily function returns the shape type * * @return {int} the shape type, one of GROUP, PRIMITIVE, PATH, or GEOMETRY */ getFamily: function(){ return this.family; }, /** * @member PShape * The getWidth() function gets the width of the drawing area (not necessarily the shape boundary). */ getWidth: function(){ return this.width; }, /** * @member PShape * The getHeight() function gets the height of the drawing area (not necessarily the shape boundary). */ getHeight: function(){ return this.height; }, /** * @member PShape * The setName() function sets the name of the shape * * @param {String} name the name of the shape */ setName: function(name){ this.name = name; }, /** * @member PShape * The getName() function returns the name of the shape * * @return {String} the name of the shape */ getName: function(){ return this.name; }, /** * @member PShape * Called by the following (the shape() command adds the g) * PShape s = loadShapes("blah.svg"); * shape(s); */ draw: function(renderContext) { if(!renderContext) { throw "render context missing for draw() in PShape"; } if (this.visible) { this.pre(renderContext); this.drawImpl(renderContext); this.post(renderContext); } }, /** * @member PShape * the drawImpl() function draws the SVG document. */ drawImpl: function(renderContext) { if (this.family === PConstants.GROUP) { this.drawGroup(renderContext); } else if (this.family === PConstants.PRIMITIVE) { this.drawPrimitive(renderContext); } else if (this.family === PConstants.GEOMETRY) { this.drawGeometry(renderContext); } else if (this.family === PConstants.PATH) { this.drawPath(renderContext); } }, /** * @member PShape * The drawPath() function draws the <path> part of the SVG document. */ drawPath: function(renderContext) { var i, j; if (this.vertices.length === 0) { return; } renderContext.beginShape(); if (this.vertexCodes.length === 0) { // each point is a simple vertex if (this.vertices[0].length === 2) { // drawing 2D vertices for (i = 0, j = this.vertices.length; i < j; i++) { renderContext.vertex(this.vertices[i][0], this.vertices[i][1]); } } else { // drawing 3D vertices for (i = 0, j = this.vertices.length; i < j; i++) { renderContext.vertex(this.vertices[i][0], this.vertices[i][1], this.vertices[i][2]); } } } else { // coded set of vertices var index = 0; if (this.vertices[0].length === 2) { // drawing a 2D path for (i = 0, j = this.vertexCodes.length; i < j; i++) { if (this.vertexCodes[i] === PConstants.VERTEX) { renderContext.vertex(this.vertices[index][0], this.vertices[index][1], this.vertices[index].moveTo); renderContext.breakShape = false; index++; } else if (this.vertexCodes[i] === PConstants.BEZIER_VERTEX) { renderContext.bezierVertex(this.vertices[index+0][0], this.vertices[index+0][1], this.vertices[index+1][0], this.vertices[index+1][1], this.vertices[index+2][0], this.vertices[index+2][1]); index += 3; } else if (this.vertexCodes[i] === PConstants.CURVE_VERTEX) { renderContext.curveVertex(this.vertices[index][0], this.vertices[index][1]); index++; } else if (this.vertexCodes[i] === PConstants.BREAK) { renderContext.breakShape = true; } } } else { // drawing a 3D path for (i = 0, j = this.vertexCodes.length; i < j; i++) { if (this.vertexCodes[i] === PConstants.VERTEX) { renderContext.vertex(this.vertices[index][0], this.vertices[index][1], this.vertices[index][2]); if (this.vertices[index].moveTo === true) { vertArray[vertArray.length-1].moveTo = true; } else if (this.vertices[index].moveTo === false) { vertArray[vertArray.length-1].moveTo = false; } renderContext.breakShape = false; } else if (this.vertexCodes[i] === PConstants.BEZIER_VERTEX) { renderContext.bezierVertex(this.vertices[index+0][0], this.vertices[index+0][1], this.vertices[index+0][2], this.vertices[index+1][0], this.vertices[index+1][1], this.vertices[index+1][2], this.vertices[index+2][0], this.vertices[index+2][1], this.vertices[index+2][2]); index += 3; } else if (this.vertexCodes[i] === PConstants.CURVE_VERTEX) { renderContext.curveVertex(this.vertices[index][0], this.vertices[index][1], this.vertices[index][2]); index++; } else if (this.vertexCodes[i] === PConstants.BREAK) { renderContext.breakShape = true; } } } } renderContext.endShape(this.close ? PConstants.CLOSE : PConstants.OPEN); }, /** * @member PShape * The drawGeometry() function draws the geometry part of the SVG document. */ drawGeometry: function(renderContext) { var i, j; renderContext.beginShape(this.kind); if (this.style) { for (i = 0, j = this.vertices.length; i < j; i++) { renderContext.vertex(this.vertices[i]); } } else { for (i = 0, j = this.vertices.length; i < j; i++) { var vert = this.vertices[i]; if (vert[2] === 0) { renderContext.vertex(vert[0], vert[1]); } else { renderContext.vertex(vert[0], vert[1], vert[2]); } } } renderContext.endShape(); }, /** * @member PShape * The drawGroup() function draws the <g> part of the SVG document. */ drawGroup: function(renderContext) { for (var i = 0, j = this.children.length; i < j; i++) { this.children[i].draw(renderContext); } }, /** * @member PShape * The drawPrimitive() function draws SVG document shape elements. These can be point, line, triangle, quad, rect, ellipse, arc, box, or sphere. */ drawPrimitive: function(renderContext) { if (this.kind === PConstants.POINT) { renderContext.point(this.params[0], this.params[1]); } else if (this.kind === PConstants.LINE) { if (this.params.length === 4) { // 2D renderContext.line(this.params[0], this.params[1], this.params[2], this.params[3]); } else { // 3D renderContext.line(this.params[0], this.params[1], this.params[2], this.params[3], this.params[4], this.params[5]); } } else if (this.kind === PConstants.TRIANGLE) { renderContext.triangle(this.params[0], this.params[1], this.params[2], this.params[3], this.params[4], this.params[5]); } else if (this.kind === PConstants.QUAD) { renderContext.quad(this.params[0], this.params[1], this.params[2], this.params[3], this.params[4], this.params[5], this.params[6], this.params[7]); } else if (this.kind === PConstants.RECT) { if (this.image !== null) { var imMode = imageModeConvert; renderContext.imageMode(PConstants.CORNER); renderContext.image(this.image, this.params[0], this.params[1], this.params[2], this.params[3]); imageModeConvert = imMode; } else { var rcMode = renderContext.curRectMode; renderContext.rectMode(PConstants.CORNER); renderContext.rect(this.params[0], this.params[1], this.params[2], this.params[3]); renderContext.curRectMode = rcMode; } } else if (this.kind === PConstants.ELLIPSE) { var elMode = renderContext.curEllipseMode; renderContext.ellipseMode(PConstants.CORNER); renderContext.ellipse(this.params[0], this.params[1], this.params[2], this.params[3]); renderContext.curEllipseMode = elMode; } else if (this.kind === PConstants.ARC) { var eMode = curEllipseMode; renderContext.ellipseMode(PConstants.CORNER); renderContext.arc(this.params[0], this.params[1], this.params[2], this.params[3], this.params[4], this.params[5]); curEllipseMode = eMode; } else if (this.kind === PConstants.BOX) { if (this.params.length === 1) { renderContext.box(this.params[0]); } else { renderContext.box(this.params[0], this.params[1], this.params[2]); } } else if (this.kind === PConstants.SPHERE) { renderContext.sphere(this.params[0]); } }, /** * @member PShape * The pre() function performs the preparations before the SVG is drawn. This includes doing transformations and storing previous styles. */ pre: function(renderContext) { if (this.matrix) { renderContext.pushMatrix(); renderContext.transform(this.matrix); } if (this.style) { renderContext.pushStyle(); this.styles(renderContext); } }, /** * @member PShape * The post() function performs the necessary actions after the SVG is drawn. This includes removing transformations and removing added styles. */ post: function(renderContext) { if (this.matrix) { renderContext.popMatrix(); } if (this.style) { renderContext.popStyle(); } }, /** * @member PShape * The styles() function changes the Processing's current styles */ styles: function(renderContext) { if (this.stroke) { renderContext.stroke(this.strokeColor); renderContext.strokeWeight(this.strokeWeight); renderContext.strokeCap(this.strokeCap); renderContext.strokeJoin(this.strokeJoin); } else { renderContext.noStroke(); }
if (this.fill) { renderContext.fill(this.fillColor);
} else { renderContext.noFill(); } }, /** * @member PShape * The getChild() function extracts a child shape from a parent shape. Specify the name of the shape with the target parameter or the * layer position of the shape to get with the index parameter. * The shape is returned as a PShape object, or null is returned if there is an error. * * @param {String} target the name of the shape to get * @param {int} index the layer position of the shape to get * * @return {PShape} returns a child element of a shape as a PShape object or null if there is an error */ getChild: function(child) { var i, j; if (typeof child === 'number') { return this.children[child]; } var found; if(child === "" || this.name === child){ return this; } if(this.nameTable.length > 0) { for(i = 0, j = this.nameTable.length; i < j || found; i++) { if(this.nameTable[i].getName === child) { found = this.nameTable[i]; break; } } if (found) { return found; } } for(i = 0, j = this.children.length; i < j; i++) { found = this.children[i].getChild(child); if(found) { return found; } } return null; }, /** * @member PShape * The getChildCount() returns the number of children * * @return {int} returns a count of children */ getChildCount: function () { return this.children.length; }, /** * @member PShape * The addChild() adds a child to the PShape. * * @param {PShape} child the child to add */ addChild: function( child ) { this.children.push(child); child.parent = this; if (child.getName() !== null) { this.addName(child.getName(), child); } }, /** * @member PShape * The addName() functions adds a shape to the name lookup table. * * @param {String} name the name to be added * @param {PShape} shape the shape */ addName: function(name, shape) { if (this.parent !== null) { this.parent.addName( name, shape ); } else { this.nameTable.push( [name, shape] ); } }, /** * @member PShape * The translate() function specifies an amount to displace the shape. The x parameter specifies left/right translation, the y parameter specifies up/down translation, and the z parameter specifies translations toward/away from the screen. * Subsequent calls to the method accumulates the effect. For example, calling translate(50, 0) and then translate(20, 0) is the same as translate(70, 0). * This transformation is applied directly to the shape, it's not refreshed each time draw() is run. *
Using this method with the z parameter requires using the P3D or OPENGL parameter in combination with size. * * @param {int|float} x left/right translation * @param {int|float} y up/down translation * @param {int|float} z forward/back translation * * @see PMatrix2D#translate * @see PMatrix3D#translate */ translate: function() { if(arguments.length === 2) { this.checkMatrix(2); this.matrix.translate(arguments[0], arguments[1]); } else { this.checkMatrix(3); this.matrix.translate(arguments[0], arguments[1], 0); } }, /** * @member PShape * The checkMatrix() function makes sure that the shape's matrix is 1) not null, and 2) has a matrix * that can handle at least the specified number of dimensions. * * @param {int} dimensions the specified number of dimensions */ checkMatrix: function(dimensions) { if(this.matrix === null) { if(dimensions === 2) { this.matrix = new PMatrix2D(); } else { this.matrix = new PMatrix3D(); } }else if(dimensions === 3 && this.matrix instanceof PMatrix2D) { this.matrix = new PMatrix3D(); } }, /** * @member PShape * The rotateX() function rotates a shape around the x-axis the amount specified by the angle parameter. Angles should be specified in radians (values from 0 to TWO_PI) or converted to radians with the radians() method. *
Shapes are always rotated around the upper-left corner of their bounding box. Positive numbers rotate objects in a clockwise direction. * Subsequent calls to the method accumulates the effect. For example, calling rotateX(HALF_PI) and then rotateX(HALF_PI) is the same as rotateX(PI). * This transformation is applied directly to the shape, it's not refreshed each time draw() is run. *
This method requires a 3D renderer. You need to pass P3D or OPENGL as a third parameter into the size() method as shown in the example above. * * @param {float}angle angle of rotation specified in radians * * @see PMatrix3D#rotateX */ rotateX: function(angle) { this.rotate(angle, 1, 0, 0); }, /** * @member PShape * The rotateY() function rotates a shape around the y-axis the amount specified by the angle parameter. Angles should be specified in radians (values from 0 to TWO_PI) or converted to radians with the radians() method. *
Shapes are always rotated around the upper-left corner of their bounding box. Positive numbers rotate objects in a clockwise direction. * Subsequent calls to the method accumulates the effect. For example, calling rotateY(HALF_PI) and then rotateY(HALF_PI) is the same as rotateY(PI). * This transformation is applied directly to the shape, it's not refreshed each time draw() is run. *
This method requires a 3D renderer. You need to pass P3D or OPENGL as a third parameter into the size() method as shown in the example above. * * @param {float}angle angle of rotation specified in radians * * @see PMatrix3D#rotateY */ rotateY: function(angle) { this.rotate(angle, 0, 1, 0); }, /** * @member PShape * The rotateZ() function rotates a shape around the z-axis the amount specified by the angle parameter. Angles should be specified in radians (values from 0 to TWO_PI) or converted to radians with the radians() method. *
Shapes are always rotated around the upper-left corner of their bounding box. Positive numbers rotate objects in a clockwise direction. * Subsequent calls to the method accumulates the effect. For example, calling rotateZ(HALF_PI) and then rotateZ(HALF_PI) is the same as rotateZ(PI). * This transformation is applied directly to the shape, it's not refreshed each time draw() is run. *
This method requires a 3D renderer. You need to pass P3D or OPENGL as a third parameter into the size() method as shown in the example above. * * @param {float}angle angle of rotation specified in radians * * @see PMatrix3D#rotateZ */ rotateZ: function(angle) { this.rotate(angle, 0, 0, 1); }, /** * @member PShape * The rotate() function rotates a shape the amount specified by the angle parameter. Angles should be specified in radians (values from 0 to TWO_PI) or converted to radians with the radians() method. *
Shapes are always rotated around the upper-left corner of their bounding box. Positive numbers rotate objects in a clockwise direction. * Transformations apply to everything that happens after and subsequent calls to the method accumulates the effect. * For example, calling rotate(HALF_PI) and then rotate(HALF_PI) is the same as rotate(PI). * This transformation is applied directly to the shape, it's not refreshed each time draw() is run. * If optional parameters x,y,z are supplied, the rotate is about the point (x, y, z). * * @param {float}angle angle of rotation specified in radians * @param {float}x x-coordinate of the point * @param {float}y y-coordinate of the point * @param {float}z z-coordinate of the point * @see PMatrix2D#rotate * @see PMatrix3D#rotate */ rotate: function() { if(arguments.length === 1){ this.checkMatrix(2); this.matrix.rotate(arguments[0]); } else { this.checkMatrix(3); this.matrix.rotate(arguments[0], arguments[1], arguments[2], arguments[3]); } }, /** * @member PShape * The scale() function increases or decreases the size of a shape by expanding and contracting vertices. Shapes always scale from the relative origin of their bounding box. * Scale values are specified as decimal percentages. For example, the method call scale(2.0) increases the dimension of a shape by 200%. * Subsequent calls to the method multiply the effect. For example, calling scale(2.0) and then scale(1.5) is the same as scale(3.0). * This transformation is applied directly to the shape, it's not refreshed each time draw() is run. *
Using this fuction with the z parameter requires passing P3D or OPENGL into the size() parameter. * * @param {float}s percentage to scale the object * @param {float}x percentage to scale the object in the x-axis * @param {float}y percentage to scale the object in the y-axis * @param {float}z percentage to scale the object in the z-axis * * @see PMatrix2D#scale * @see PMatrix3D#scale */ scale: function() { if(arguments.length === 2) { this.checkMatrix(2); this.matrix.scale(arguments[0], arguments[1]); } else if (arguments.length === 3) { this.checkMatrix(2); this.matrix.scale(arguments[0], arguments[1], arguments[2]); } else { this.checkMatrix(2); this.matrix.scale(arguments[0]); } }, /** * @member PShape * The resetMatrix() function resets the matrix * * @see PMatrix2D#reset * @see PMatrix3D#reset */ resetMatrix: function() { this.checkMatrix(2); this.matrix.reset(); }, /** * @member PShape * The applyMatrix() function multiplies this matrix by another matrix of type PMatrix3D or PMatrix2D. * Individual elements can also be provided * * @param {PMatrix3D|PMatrix2D} matrix the matrix to multiply by * * @see PMatrix2D#apply * @see PMatrix3D#apply */ applyMatrix: function(matrix) { if (arguments.length === 1) { this.applyMatrix(matrix.elements[0], matrix.elements[1], 0, matrix.elements[2], matrix.elements[3], matrix.elements[4], 0, matrix.elements[5], 0, 0, 1, 0, 0, 0, 0, 1); } else if (arguments.length === 6) { this.checkMatrix(2); this.matrix.apply(arguments[0], arguments[1], arguments[2], 0, arguments[3], arguments[4], arguments[5], 0, 0, 0, 1, 0, 0, 0, 0, 1);
} else if (arguments.length === 16) { this.checkMatrix(3); this.matrix.apply(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5], arguments[6], arguments[7], arguments[8], arguments[9], arguments[10], arguments[11], arguments[12], arguments[13], arguments[14], arguments[15]); } } };
return PShape;
}; },{}],16:[function(require,module,exports){ /**
* SVG stands for Scalable Vector Graphics, a portable graphics format. It is * a vector format so it allows for infinite resolution and relatively small * file sizes. Most modern media software can view SVG files, including Adobe * products, Firefox, etc. Illustrator and Inkscape can edit SVG files. * * @param {PApplet} parent typically use "this" * @param {String} filename name of the SVG file to load * @param {XMLElement} xml an XMLElement element * @param {PShapeSVG} parent the parent PShapeSVG * * @see PShape */
module.exports = function(options) {
var CommonFunctions = options.CommonFunctions, PConstants = options.PConstants, PShape = options.PShape, XMLElement = options.XMLElement, colors = options.colors;
var PShapeSVG = function() { PShape.call(this); // PShape is the base class. if (arguments.length === 1) { // xml element coming in this.element = arguments[0];
// set values to their defaults according to the SVG spec this.vertexCodes = []; this.vertices = []; this.opacity = 1;
this.stroke = false; this.strokeColor = PConstants.ALPHA_MASK; this.strokeWeight = 1; this.strokeCap = PConstants.SQUARE; // BUTT in svg spec this.strokeJoin = PConstants.MITER; this.strokeGradient = null; this.strokeGradientPaint = null; this.strokeName = null; this.strokeOpacity = 1;
this.fill = true; this.fillColor = PConstants.ALPHA_MASK; this.fillGradient = null; this.fillGradientPaint = null; this.fillName = null; this.fillOpacity = 1;
if (this.element.getName() !== "svg") { throw("root is not <svg>, it's <" + this.element.getName() + ">"); } } else if (arguments.length === 2) { if (typeof arguments[1] === 'string') { if (arguments[1].indexOf(".svg") > -1) { //its a filename this.element = new XMLElement(true, arguments[1]); // set values to their defaults according to the SVG spec this.vertexCodes = []; this.vertices = []; this.opacity = 1;
this.stroke = false; this.strokeColor = PConstants.ALPHA_MASK; this.strokeWeight = 1; this.strokeCap = PConstants.SQUARE; // BUTT in svg spec this.strokeJoin = PConstants.MITER; this.strokeGradient = ""; this.strokeGradientPaint = ""; this.strokeName = ""; this.strokeOpacity = 1;
this.fill = true; this.fillColor = PConstants.ALPHA_MASK; this.fillGradient = null; this.fillGradientPaint = null; this.fillOpacity = 1;
} } else { // XMLElement if (arguments[0]) { // PShapeSVG this.element = arguments[1]; this.vertexCodes = arguments[0].vertexCodes.slice(); this.vertices = arguments[0].vertices.slice();
this.stroke = arguments[0].stroke; this.strokeColor = arguments[0].strokeColor; this.strokeWeight = arguments[0].strokeWeight; this.strokeCap = arguments[0].strokeCap; this.strokeJoin = arguments[0].strokeJoin; this.strokeGradient = arguments[0].strokeGradient; this.strokeGradientPaint = arguments[0].strokeGradientPaint; this.strokeName = arguments[0].strokeName;
this.fill = arguments[0].fill; this.fillColor = arguments[0].fillColor; this.fillGradient = arguments[0].fillGradient; this.fillGradientPaint = arguments[0].fillGradientPaint; this.fillName = arguments[0].fillName; this.strokeOpacity = arguments[0].strokeOpacity; this.fillOpacity = arguments[0].fillOpacity; this.opacity = arguments[0].opacity; } } }
this.name = this.element.getStringAttribute("id"); var displayStr = this.element.getStringAttribute("display", "inline"); this.visible = displayStr !== "none"; var str = this.element.getAttribute("transform"); if (str) { this.matrix = this.parseMatrix(str); } // not proper parsing of the viewBox, but will cover us for cases where // the width and height of the object is not specified var viewBoxStr = this.element.getStringAttribute("viewBox"); if ( viewBoxStr !== null ) { var viewBox = viewBoxStr.split(" "); this.width = viewBox[2]; this.height = viewBox[3]; }
// TODO if viewbox is not same as width/height, then use it to scale // the original objects. for now, viewbox only used when width/height // are empty values (which by the spec means w/h of "100%" var unitWidth = this.element.getStringAttribute("width"); var unitHeight = this.element.getStringAttribute("height"); if (unitWidth !== null) { this.width = this.parseUnitSize(unitWidth); this.height = this.parseUnitSize(unitHeight); } else { if ((this.width === 0) || (this.height === 0)) { // For the spec, the default is 100% and 100%. For purposes // here, insert a dummy value because this is prolly just a // font or something for which the w/h doesn't matter. this.width = 1; this.height = 1;
//show warning throw("The width and/or height is not " + "readable in the <svg> tag of this file."); } } this.parseColors(this.element); this.parseChildren(this.element);
}; /** * PShapeSVG methods * missing: getChild(), print(), parseStyleAttributes(), styles() - deals with strokeGradient and fillGradient */ PShapeSVG.prototype = new PShape(); /** * @member PShapeSVG * The parseMatrix() function parses the specified SVG matrix into a PMatrix2D. Note that PMatrix2D * is rotated relative to the SVG definition, so parameters are rearranged * here. More about the transformation matrices in * <a href="http://www.w3.org/TR/SVG/coords.html#TransformAttribute">this section</a> * of the SVG documentation. * * @param {String} str text of the matrix param. * * @return {PMatrix2D} a PMatrix2D */ PShapeSVG.prototype.parseMatrix = (function() { function getCoords(s) { var m = []; s.replace(/\((.*?)\)/, (function() { return function(all, params) { // get the coordinates that can be separated by spaces or a comma m = params.replace(/,+/g, " ").split(/\s+/); }; }())); return m; }
return function(str) { this.checkMatrix(2); var pieces = []; str.replace(/\s*(\w+)\((.*?)\)/g, function(all) { // get a list of transform definitions pieces.push(CommonFunctions.trim(all)); }); if (pieces.length === 0) { return null; }
for (var i = 0, j = pieces.length; i < j; i++) { var m = getCoords(pieces[i]);
if (pieces[i].indexOf("matrix") !== -1) { this.matrix.set(m[0], m[2], m[4], m[1], m[3], m[5]); } else if (pieces[i].indexOf("translate") !== -1) { var tx = m[0]; var ty = (m.length === 2) ? m[1] : 0; this.matrix.translate(tx,ty); } else if (pieces[i].indexOf("scale") !== -1) { var sx = m[0]; var sy = (m.length === 2) ? m[1] : m[0]; this.matrix.scale(sx,sy); } else if (pieces[i].indexOf("rotate") !== -1) { var angle = m[0]; if (m.length === 1) { this.matrix.rotate(CommonFunctions.radians(angle)); } else if (m.length === 3) { this.matrix.translate(m[1], m[2]); this.matrix.rotate(CommonFunctions.radians(m[0])); this.matrix.translate(-m[1], -m[2]); } } else if (pieces[i].indexOf("skewX") !== -1) { this.matrix.skewX(parseFloat(m[0])); } else if (pieces[i].indexOf("skewY") !== -1) { this.matrix.skewY(m[0]); } else if (pieces[i].indexOf("shearX") !== -1) { this.matrix.shearX(m[0]); } else if (pieces[i].indexOf("shearY") !== -1) { this.matrix.shearY(m[0]); } } return this.matrix; }; }());
/** * @member PShapeSVG * The parseChildren() function parses the specified XMLElement * * @param {XMLElement}element the XMLElement to parse */ PShapeSVG.prototype.parseChildren = function(element) { var newelement = element.getChildren(); var base = new PShape(); var i, j; for (i = 0, j = newelement.length; i < j; i++) { var kid = this.parseChild(newelement[i]); if (kid) { base.addChild(kid); } } for (i = 0, j = base.children.length; i < j; i++) { this.children.push(base.children[i]); } }; /** * @member PShapeSVG * The getName() function returns the name * * @return {String} the name */ PShapeSVG.prototype.getName = function() { return this.name; }; /** * @member PShapeSVG * The parseChild() function parses a child XML element. * * @param {XMLElement} elem the element to parse * * @return {PShape} the newly created PShape */ PShapeSVG.prototype.parseChild = function( elem ) { var name = elem.getName(); var shape; if (name === "g") { shape = new PShapeSVG(this, elem); } else if (name === "defs") { // generally this will contain gradient info, so may // as well just throw it into a group element for parsing shape = new PShapeSVG(this, elem); } else if (name === "line") { shape = new PShapeSVG(this, elem); shape.parseLine(); } else if (name === "circle") { shape = new PShapeSVG(this, elem); shape.parseEllipse(true); } else if (name === "ellipse") { shape = new PShapeSVG(this, elem); shape.parseEllipse(false); } else if (name === "rect") { shape = new PShapeSVG(this, elem); shape.parseRect(); } else if (name === "polygon") { shape = new PShapeSVG(this, elem); shape.parsePoly(true); } else if (name === "polyline") { shape = new PShapeSVG(this, elem); shape.parsePoly(false); } else if (name === "path") { shape = new PShapeSVG(this, elem); shape.parsePath(); } else if (name === "radialGradient") { //return new RadialGradient(this, elem); unimplemented('PShapeSVG.prototype.parseChild, name = radialGradient'); } else if (name === "linearGradient") { //return new LinearGradient(this, elem); unimplemented('PShapeSVG.prototype.parseChild, name = linearGradient'); } else if (name === "text") { unimplemented('PShapeSVG.prototype.parseChild, name = text'); } else if (name === "filter") { unimplemented('PShapeSVG.prototype.parseChild, name = filter'); } else if (name === "mask") { unimplemented('PShapeSVG.prototype.parseChild, name = mask'); } else { // ignoring } return shape; }; /** * @member PShapeSVG * The parsePath() function parses the <path> element of the svg file * A path is defined by including a path element which contains a d="(path data)" attribute, where the d attribute contains * the moveto, line, curve (both cubic and quadratic Beziers), arc and closepath instructions. **/ PShapeSVG.prototype.parsePath = function() { this.family = PConstants.PATH; this.kind = 0; var pathDataChars = []; var c; //change multiple spaces and commas to single space var pathData = CommonFunctions.trim(this.element.getStringAttribute("d").replace(/[\s,]+/g,' ')); if (pathData === null) { return; } pathData = pathData.split(); var cx = 0, cy = 0, ctrlX = 0, ctrlY = 0, ctrlX1 = 0, ctrlX2 = 0, ctrlY1 = 0, ctrlY2 = 0, endX = 0, endY = 0, ppx = 0, ppy = 0, px = 0, py = 0, i = 0, valOf = 0; var str = ""; var tmpArray = []; var flag = false; var lastInstruction; var command; var j, k; while (i< pathData.length) { valOf = pathData[i].charCodeAt(0); if ((valOf >= 65 && valOf <= 90) || (valOf >= 97 && valOf <= 122)) { // if it's a letter // populate the tmpArray with coordinates j = i; i++; if (i < pathData.length) { // don't go over boundary of array tmpArray = []; valOf = pathData[i].charCodeAt(0); while (!((valOf >= 65 && valOf <= 90) || (valOf >= 97 && valOf <= 100) || (valOf >= 102 && valOf <= 122)) && flag === false) { // if its NOT a letter if (valOf === 32) { //if its a space and the str isn't empty // sometimes you get a space after the letter if (str !== "") { tmpArray.push(parseFloat(str)); str = ""; } i++; } else if (valOf === 45) { //if it's a - // allow for 'e' notation in numbers, e.g. 2.10e-9 if (pathData[i-1].charCodeAt(0) === 101) { str += pathData[i].toString(); i++; } else { // sometimes no space separator after (ex: 104.535-16.322) if (str !== "") { tmpArray.push(parseFloat(str)); } str = pathData[i].toString(); i++; } } else { str += pathData[i].toString(); i++; } if (i === pathData.length) { // don't go over boundary of array flag = true; } else { valOf = pathData[i].charCodeAt(0); } } } if (str !== "") { tmpArray.push(parseFloat(str)); str = ""; } command = pathData[j]; valOf = command.charCodeAt(0); if (valOf === 77) { // M - move to (absolute) if (tmpArray.length >= 2 && tmpArray.length % 2 === 0) { // need one+ pairs of co-ordinates cx = tmpArray[0]; cy = tmpArray[1]; this.parsePathMoveto(cx, cy); if (tmpArray.length > 2) { for (j = 2, k = tmpArray.length; j < k; j+=2) { // absolute line to cx = tmpArray[j]; cy = tmpArray[j+1]; this.parsePathLineto(cx,cy); } } } } else if (valOf === 109) { // m - move to (relative) if (tmpArray.length >= 2 && tmpArray.length % 2 === 0) { // need one+ pairs of co-ordinates cx += tmpArray[0]; cy += tmpArray[1]; this.parsePathMoveto(cx,cy); if (tmpArray.length > 2) { for (j = 2, k = tmpArray.length; j < k; j+=2) { // relative line to cx += tmpArray[j]; cy += tmpArray[j + 1]; this.parsePathLineto(cx,cy); } } } } else if (valOf === 76) { // L - lineto (absolute) if (tmpArray.length >= 2 && tmpArray.length % 2 === 0) { // need one+ pairs of co-ordinates for (j = 0, k = tmpArray.length; j < k; j+=2) { cx = tmpArray[j]; cy = tmpArray[j + 1]; this.parsePathLineto(cx,cy); } } } else if (valOf === 108) { // l - lineto (relative) if (tmpArray.length >= 2 && tmpArray.length % 2 === 0) { // need one+ pairs of co-ordinates for (j = 0, k = tmpArray.length; j < k; j+=2) { cx += tmpArray[j]; cy += tmpArray[j+1]; this.parsePathLineto(cx,cy); } } } else if (valOf === 72) { // H - horizontal lineto (absolute) for (j = 0, k = tmpArray.length; j < k; j++) { // multiple x co-ordinates can be provided cx = tmpArray[j]; this.parsePathLineto(cx, cy); } } else if (valOf === 104) { // h - horizontal lineto (relative) for (j = 0, k = tmpArray.length; j < k; j++) { // multiple x co-ordinates can be provided cx += tmpArray[j]; this.parsePathLineto(cx, cy); } } else if (valOf === 86) { // V - vertical lineto (absolute) for (j = 0, k = tmpArray.length; j < k; j++) { // multiple y co-ordinates can be provided cy = tmpArray[j]; this.parsePathLineto(cx, cy); } } else if (valOf === 118) { // v - vertical lineto (relative) for (j = 0, k = tmpArray.length; j < k; j++) { // multiple y co-ordinates can be provided cy += tmpArray[j]; this.parsePathLineto(cx, cy); } } else if (valOf === 67) { // C - curve to (absolute) if (tmpArray.length >= 6 && tmpArray.length % 6 === 0) { // need one+ multiples of 6 co-ordinates for (j = 0, k = tmpArray.length; j < k; j+=6) { ctrlX1 = tmpArray[j]; ctrlY1 = tmpArray[j + 1]; ctrlX2 = tmpArray[j + 2]; ctrlY2 = tmpArray[j + 3]; endX = tmpArray[j + 4]; endY = tmpArray[j + 5]; this.parsePathCurveto(ctrlX1, ctrlY1, ctrlX2, ctrlY2, endX, endY); cx = endX; cy = endY; } } } else if (valOf === 99) { // c - curve to (relative) if (tmpArray.length >= 6 && tmpArray.length % 6 === 0) { // need one+ multiples of 6 co-ordinates for (j = 0, k = tmpArray.length; j < k; j+=6) { ctrlX1 = cx + tmpArray[j]; ctrlY1 = cy + tmpArray[j + 1]; ctrlX2 = cx + tmpArray[j + 2]; ctrlY2 = cy + tmpArray[j + 3]; endX = cx + tmpArray[j + 4]; endY = cy + tmpArray[j + 5]; this.parsePathCurveto(ctrlX1, ctrlY1, ctrlX2, ctrlY2, endX, endY); cx = endX; cy = endY; } } } else if (valOf === 83) { // S - curve to shorthand (absolute) if (tmpArray.length >= 4 && tmpArray.length % 4 === 0) { // need one+ multiples of 4 co-ordinates for (j = 0, k = tmpArray.length; j < k; j+=4) { if (lastInstruction.toLowerCase() === "c" || lastInstruction.toLowerCase() === "s") { ppx = this.vertices[ this.vertices.length-2 ][0]; ppy = this.vertices[ this.vertices.length-2 ][1]; px = this.vertices[ this.vertices.length-1 ][0]; py = this.vertices[ this.vertices.length-1 ][1]; ctrlX1 = px + (px - ppx); ctrlY1 = py + (py - ppy); } else { //If there is no previous curve, //the current point will be used as the first control point. ctrlX1 = this.vertices[this.vertices.length-1][0]; ctrlY1 = this.vertices[this.vertices.length-1][1]; } ctrlX2 = tmpArray[j]; ctrlY2 = tmpArray[j + 1]; endX = tmpArray[j + 2]; endY = tmpArray[j + 3]; this.parsePathCurveto(ctrlX1, ctrlY1, ctrlX2, ctrlY2, endX, endY); cx = endX; cy = endY; } } } else if (valOf === 115) { // s - curve to shorthand (relative) if (tmpArray.length >= 4 && tmpArray.length % 4 === 0) { // need one+ multiples of 4 co-ordinates for (j = 0, k = tmpArray.length; j < k; j+=4) { if (lastInstruction.toLowerCase() === "c" || lastInstruction.toLowerCase() === "s") { ppx = this.vertices[this.vertices.length-2][0]; ppy = this.vertices[this.vertices.length-2][1]; px = this.vertices[this.vertices.length-1][0]; py = this.vertices[this.vertices.length-1][1]; ctrlX1 = px + (px - ppx); ctrlY1 = py + (py - ppy); } else { //If there is no previous curve, //the current point will be used as the first control point. ctrlX1 = this.vertices[this.vertices.length-1][0]; ctrlY1 = this.vertices[this.vertices.length-1][1]; } ctrlX2 = cx + tmpArray[j]; ctrlY2 = cy + tmpArray[j + 1]; endX = cx + tmpArray[j + 2]; endY = cy + tmpArray[j + 3]; this.parsePathCurveto(ctrlX1, ctrlY1, ctrlX2, ctrlY2, endX, endY); cx = endX; cy = endY; } } } else if (valOf === 81) { // Q - quadratic curve to (absolute) if (tmpArray.length >= 4 && tmpArray.length % 4 === 0) { // need one+ multiples of 4 co-ordinates for (j = 0, k = tmpArray.length; j < k; j+=4) { ctrlX = tmpArray[j]; ctrlY = tmpArray[j + 1]; endX = tmpArray[j + 2]; endY = tmpArray[j + 3]; this.parsePathQuadto(cx, cy, ctrlX, ctrlY, endX, endY); cx = endX; cy = endY; } } } else if (valOf === 113) { // q - quadratic curve to (relative) if (tmpArray.length >= 4 && tmpArray.length % 4 === 0) { // need one+ multiples of 4 co-ordinates for (j = 0, k = tmpArray.length; j < k; j+=4) { ctrlX = cx + tmpArray[j]; ctrlY = cy + tmpArray[j + 1]; endX = cx + tmpArray[j + 2]; endY = cy + tmpArray[j + 3]; this.parsePathQuadto(cx, cy, ctrlX, ctrlY, endX, endY); cx = endX; cy = endY; } } } else if (valOf === 84) { // T - quadratic curve to shorthand (absolute) if (tmpArray.length >= 2 && tmpArray.length % 2 === 0) { // need one+ pairs of co-ordinates for (j = 0, k = tmpArray.length; j < k; j+=2) { if (lastInstruction.toLowerCase() === "q" || lastInstruction.toLowerCase() === "t") { ppx = this.vertices[this.vertices.length-2][0]; ppy = this.vertices[this.vertices.length-2][1]; px = this.vertices[this.vertices.length-1][0]; py = this.vertices[this.vertices.length-1][1]; ctrlX = px + (px - ppx); ctrlY = py + (py - ppy); } else { // If there is no previous command or if the previous command // was not a Q, q, T or t, assume the control point is // coincident with the current point. ctrlX = cx; ctrlY = cy; } endX = tmpArray[j]; endY = tmpArray[j + 1]; this.parsePathQuadto(cx, cy, ctrlX, ctrlY, endX, endY); cx = endX; cy = endY; } } } else if (valOf === 116) { // t - quadratic curve to shorthand (relative) if (tmpArray.length >= 2 && tmpArray.length % 2 === 0) { // need one+ pairs of co-ordinates for (j = 0, k = tmpArray.length; j < k; j+=2) { if (lastInstruction.toLowerCase() === "q" || lastInstruction.toLowerCase() === "t") { ppx = this.vertices[this.vertices.length-2][0]; ppy = this.vertices[this.vertices.length-2][1]; px = this.vertices[this.vertices.length-1][0]; py = this.vertices[this.vertices.length-1][1]; ctrlX = px + (px - ppx); ctrlY = py + (py - ppy); } else { // If there is no previous command or if the previous command // was not a Q, q, T or t, assume the control point is // coincident with the current point. ctrlX = cx; ctrlY = cy; } endX = cx + tmpArray[j]; endY = cy + tmpArray[j + 1]; this.parsePathQuadto(cx, cy, ctrlX, ctrlY, endX, endY); cx = endX; cy = endY; } } } else if (valOf === 90 || valOf === 122) { // Z or z (these do the same thing) this.close = true; } lastInstruction = command.toString(); } else { i++;} } }; /** * @member PShapeSVG * PShapeSVG.parsePath() helper function * * @see PShapeSVG#parsePath */ PShapeSVG.prototype.parsePathQuadto = function(x1, y1, cx, cy, x2, y2) { if (this.vertices.length > 0) { this.parsePathCode(PConstants.BEZIER_VERTEX); // x1/y1 already covered by last moveto, lineto, or curveto this.parsePathVertex(x1 + ((cx-x1)*2/3), y1 + ((cy-y1)*2/3)); this.parsePathVertex(x2 + ((cx-x2)*2/3), y2 + ((cy-y2)*2/3)); this.parsePathVertex(x2, y2); } else { throw ("Path must start with M/m"); } }; /** * @member PShapeSVG * PShapeSVG.parsePath() helper function * * @see PShapeSVG#parsePath */ PShapeSVG.prototype.parsePathCurveto = function(x1, y1, x2, y2, x3, y3) { if (this.vertices.length > 0) { this.parsePathCode(PConstants.BEZIER_VERTEX ); this.parsePathVertex(x1, y1); this.parsePathVertex(x2, y2); this.parsePathVertex(x3, y3); } else { throw ("Path must start with M/m"); } }; /** * @member PShapeSVG * PShapeSVG.parsePath() helper function * * @see PShapeSVG#parsePath */ PShapeSVG.prototype.parsePathLineto = function(px, py) { if (this.vertices.length > 0) { this.parsePathCode(PConstants.VERTEX); this.parsePathVertex(px, py); // add property to distinguish between curContext.moveTo // or curContext.lineTo this.vertices[this.vertices.length-1].moveTo = false; } else { throw ("Path must start with M/m"); } };
PShapeSVG.prototype.parsePathMoveto = function(px, py) { if (this.vertices.length > 0) { this.parsePathCode(PConstants.BREAK); } this.parsePathCode(PConstants.VERTEX); this.parsePathVertex(px, py); // add property to distinguish between curContext.moveTo // or curContext.lineTo this.vertices[this.vertices.length-1].moveTo = true; }; /** * @member PShapeSVG * PShapeSVG.parsePath() helper function * * @see PShapeSVG#parsePath */ PShapeSVG.prototype.parsePathVertex = function(x, y) { var verts = []; verts[0] = x; verts[1] = y; this.vertices.push(verts); }; /** * @member PShapeSVG * PShapeSVG.parsePath() helper function * * @see PShapeSVG#parsePath */ PShapeSVG.prototype.parsePathCode = function(what) { this.vertexCodes.push(what); }; /** * @member PShapeSVG * The parsePoly() function parses a polyline or polygon from an SVG file. * * @param {boolean}val true if shape is closed (polygon), false if not (polyline) */ PShapeSVG.prototype.parsePoly = function(val) { this.family = PConstants.PATH; this.close = val; var pointsAttr = CommonFunctions.trim(this.element.getStringAttribute("points").replace(/[,\s]+/g,' ')); if (pointsAttr !== null) { //split into array var pointsBuffer = pointsAttr.split(" "); if (pointsBuffer.length % 2 === 0) { for (var i = 0, j = pointsBuffer.length; i < j; i++) { var verts = []; verts[0] = pointsBuffer[i]; verts[1] = pointsBuffer[++i]; this.vertices.push(verts); } } else { throw("Error parsing polygon points: odd number of coordinates provided"); } } }; /** * @member PShapeSVG * The parseRect() function parses a rect from an SVG file. */ PShapeSVG.prototype.parseRect = function() { this.kind = PConstants.RECT; this.family = PConstants.PRIMITIVE; this.params = []; this.params[0] = this.element.getFloatAttribute("x"); this.params[1] = this.element.getFloatAttribute("y"); this.params[2] = this.element.getFloatAttribute("width"); this.params[3] = this.element.getFloatAttribute("height"); if (this.params[2] < 0 || this.params[3] < 0) { throw("svg error: negative width or height found while parsing <rect>"); } }; /** * @member PShapeSVG * The parseEllipse() function handles parsing ellipse and circle tags. * * @param {boolean}val true if this is a circle and not an ellipse */ PShapeSVG.prototype.parseEllipse = function(val) { this.kind = PConstants.ELLIPSE; this.family = PConstants.PRIMITIVE; this.params = [];
this.params[0] = this.element.getFloatAttribute("cx") | 0 ; this.params[1] = this.element.getFloatAttribute("cy") | 0;
var rx, ry; if (val) { rx = ry = this.element.getFloatAttribute("r"); if (rx < 0) { throw("svg error: negative radius found while parsing <circle>"); } } else { rx = this.element.getFloatAttribute("rx"); ry = this.element.getFloatAttribute("ry"); if (rx < 0 || ry < 0) { throw("svg error: negative x-axis radius or y-axis radius found while parsing <ellipse>"); } } this.params[0] -= rx; this.params[1] -= ry;
this.params[2] = rx*2; this.params[3] = ry*2; }; /** * @member PShapeSVG * The parseLine() function handles parsing line tags. * * @param {boolean}val true if this is a circle and not an ellipse */ PShapeSVG.prototype.parseLine = function() { this.kind = PConstants.LINE; this.family = PConstants.PRIMITIVE; this.params = []; this.params[0] = this.element.getFloatAttribute("x1"); this.params[1] = this.element.getFloatAttribute("y1"); this.params[2] = this.element.getFloatAttribute("x2"); this.params[3] = this.element.getFloatAttribute("y2"); }; /** * @member PShapeSVG * The parseColors() function handles parsing the opacity, strijem stroke-width, stroke-linejoin,stroke-linecap, fill, and style attributes * * @param {XMLElement}element the element of which attributes to parse */ PShapeSVG.prototype.parseColors = function(element) { if (element.hasAttribute("opacity")) { this.setOpacity(element.getAttribute("opacity")); } if (element.hasAttribute("stroke")) { this.setStroke(element.getAttribute("stroke")); } if (element.hasAttribute("stroke-width")) { // if NaN (i.e. if it's 'inherit') then default // back to the inherit setting this.setStrokeWeight(element.getAttribute("stroke-width")); } if (element.hasAttribute("stroke-linejoin") ) { this.setStrokeJoin(element.getAttribute("stroke-linejoin")); } if (element.hasAttribute("stroke-linecap")) { this.setStrokeCap(element.getStringAttribute("stroke-linecap")); } // fill defaults to black (though stroke defaults to "none") // http://www.w3.org/TR/SVG/painting.html#FillProperties if (element.hasAttribute("fill")) { this.setFill(element.getStringAttribute("fill")); } if (element.hasAttribute("style")) { var styleText = element.getStringAttribute("style"); var styleTokens = styleText.toString().split( ";" );
for (var i = 0, j = styleTokens.length; i < j; i++) { var tokens = CommonFunctions.trim(styleTokens[i].split( ":" )); if (tokens[0] === "fill") { this.setFill(tokens[1]); } else if (tokens[0] === "fill-opacity") { this.setFillOpacity(tokens[1]); } else if (tokens[0] === "stroke") { this.setStroke(tokens[1]); } else if (tokens[0] === "stroke-width") { this.setStrokeWeight(tokens[1]); } else if (tokens[0] === "stroke-linecap") { this.setStrokeCap(tokens[1]); } else if (tokens[0] === "stroke-linejoin") { this.setStrokeJoin(tokens[1]); } else if (tokens[0] === "stroke-opacity") { this.setStrokeOpacity(tokens[1]); } else if (tokens[0] === "opacity") { this.setOpacity(tokens[1]); } // Other attributes are not yet implemented } } }; /** * @member PShapeSVG * PShapeSVG.parseColors() helper function * * @param {String} opacityText the value of fillOpacity * * @see PShapeSVG#parseColors */ PShapeSVG.prototype.setFillOpacity = function(opacityText) { this.fillOpacity = parseFloat(opacityText); this.fillColor = this.fillOpacity * 255 << 24 | this.fillColor & 0xFFFFFF; }; /** * @member PShapeSVG * PShapeSVG.parseColors() helper function * * @param {String} fillText the value of fill * * @see PShapeSVG#parseColors */ PShapeSVG.prototype.setFill = function (fillText) { var opacityMask = this.fillColor & 0xFF000000; if (fillText === "none") { this.fill = false; } else if (fillText.indexOf("#") === 0) { this.fill = true; if (fillText.length === 4) { // convert #00F to #0000FF fillText = fillText.replace(/#(.)(.)(.)/,"#$1$1$2$2$3$3"); } this.fillColor = opacityMask | (parseInt(fillText.substring(1), 16 )) & 0xFFFFFF; } else if (fillText.indexOf("rgb") === 0) { this.fill = true; this.fillColor = opacityMask | this.parseRGB(fillText); } else if (fillText.indexOf("url(#") === 0) { this.fillName = fillText.substring(5, fillText.length - 1 ); } else if (colors[fillText]) { this.fill = true; this.fillColor = opacityMask | (parseInt(colors[fillText].substring(1), 16)) & 0xFFFFFF; } }; /** * @member PShapeSVG * PShapeSVG.parseColors() helper function * * @param {String} opacity the value of opacity * * @see PShapeSVG#parseColors */ PShapeSVG.prototype.setOpacity = function(opacity) { this.strokeColor = parseFloat(opacity) * 255 << 24 | this.strokeColor & 0xFFFFFF; this.fillColor = parseFloat(opacity) * 255 << 24 | this.fillColor & 0xFFFFFF; }; /** * @member PShapeSVG * PShapeSVG.parseColors() helper function * * @param {String} strokeText the value to set stroke to * * @see PShapeSVG#parseColors */ PShapeSVG.prototype.setStroke = function(strokeText) { var opacityMask = this.strokeColor & 0xFF000000; if (strokeText === "none") { this.stroke = false; } else if (strokeText.charAt( 0 ) === "#") { this.stroke = true; if (strokeText.length === 4) { // convert #00F to #0000FF strokeText = strokeText.replace(/#(.)(.)(.)/,"#$1$1$2$2$3$3"); } this.strokeColor = opacityMask | (parseInt( strokeText.substring( 1 ), 16 )) & 0xFFFFFF; } else if (strokeText.indexOf( "rgb" ) === 0 ) { this.stroke = true; this.strokeColor = opacityMask | this.parseRGB(strokeText); } else if (strokeText.indexOf( "url(#" ) === 0) { this.strokeName = strokeText.substring(5, strokeText.length - 1); } else if (colors[strokeText]) { this.stroke = true; this.strokeColor = opacityMask | (parseInt(colors[strokeText].substring(1), 16)) & 0xFFFFFF; } }; /** * @member PShapeSVG * PShapeSVG.parseColors() helper function * * @param {String} weight the value to set strokeWeight to * * @see PShapeSVG#parseColors */ PShapeSVG.prototype.setStrokeWeight = function(weight) { this.strokeWeight = this.parseUnitSize(weight); }; /** * @member PShapeSVG * PShapeSVG.parseColors() helper function * * @param {String} linejoin the value to set strokeJoin to * * @see PShapeSVG#parseColors */ PShapeSVG.prototype.setStrokeJoin = function(linejoin) { if (linejoin === "miter") { this.strokeJoin = PConstants.MITER;
} else if (linejoin === "round") { this.strokeJoin = PConstants.ROUND;
} else if (linejoin === "bevel") { this.strokeJoin = PConstants.BEVEL; } }; /** * @member PShapeSVG * PShapeSVG.parseColors() helper function * * @param {String} linecap the value to set strokeCap to * * @see PShapeSVG#parseColors */ PShapeSVG.prototype.setStrokeCap = function (linecap) { if (linecap === "butt") { this.strokeCap = PConstants.SQUARE;
} else if (linecap === "round") { this.strokeCap = PConstants.ROUND;
} else if (linecap === "square") { this.strokeCap = PConstants.PROJECT; } }; /** * @member PShapeSVG * PShapeSVG.parseColors() helper function * * @param {String} opacityText the value to set stroke opacity to * * @see PShapeSVG#parseColors */ PShapeSVG.prototype.setStrokeOpacity = function (opacityText) { this.strokeOpacity = parseFloat(opacityText); this.strokeColor = this.strokeOpacity * 255 << 24 | this.strokeColor & 0xFFFFFF; }; /** * @member PShapeSVG * The parseRGB() function parses an rbg() color string and returns a color int * * @param {String} color the color to parse in rbg() format * * @return {int} the equivalent color int */ PShapeSVG.prototype.parseRGB = function(color) { var sub = color.substring(color.indexOf('(') + 1, color.indexOf(')')); var values = sub.split(", "); return (values[0] << 16) | (values[1] << 8) | (values[2]); }; /** * @member PShapeSVG * The parseUnitSize() function parse a size that may have a suffix for its units. * Ignoring cases where this could also be a percentage. * The <A HREF="http://www.w3.org/TR/SVG/coords.html#Units">units</A> spec:*
-
*
- "1pt" equals "1.25px" (and therefore 1.25 user units) *
- "1pc" equals "15px" (and therefore 15 user units) *
- "1mm" would be "3.543307px" (3.543307 user units) *
- "1cm" equals "35.43307px" (and therefore 35.43307 user units) *
- "1in" equals "90px" (and therefore 90 user units) *
*/ PShapeSVG.prototype.parseUnitSize = function (text) { var len = text.length - 2; if (len < 0) { return text; } if (text.indexOf("pt") === len) { return parseFloat(text.substring(0, len)) * 1.25; } if (text.indexOf("pc") === len) { return parseFloat( text.substring( 0, len)) * 15; } if (text.indexOf("mm") === len) { return parseFloat( text.substring(0, len)) * 3.543307; } if (text.indexOf("cm") === len) { return parseFloat(text.substring(0, len)) * 35.43307; } if (text.indexOf("in") === len) { return parseFloat(text.substring(0, len)) * 90; } if (text.indexOf("px") === len) { return parseFloat(text.substring(0, len)); } return parseFloat(text); };
return PShapeSVG;
};
},{}],17:[function(require,module,exports){ module.exports = function(options, undef) {
var PConstants = options.PConstants;
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(); } v.x = Math.cos(angle); v.y = Math.sin(angle); return v; };
PVector.random2D = function(v) { return PVector.fromAngle(Math.random() * PConstants.TWO_PI, v); };
PVector.random3D = function(v) { var angle = Math.random() * PConstants.TWO_PI; var vz = Math.random() * 2 - 1; var mult = Math.sqrt(1 - vz * vz); var vx = mult * Math.cos(angle); var vy = mult * Math.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) { return Math.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; this.y = y; this.z = z; } }, 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 = Math.cos(angle); var s = Math.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() { return (-Math.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; }; }
for (var method in PVector.prototype) { if (PVector.prototype.hasOwnProperty(method) && !PVector.hasOwnProperty(method)) { PVector[method] = createPVectorMethod(method); } }
return PVector;
};
},{}],18:[function(require,module,exports){
/**
* XMLAttribute is an attribute of a XML element. * * @param {String} fname the full name of the attribute * @param {String} n the short name of the attribute * @param {String} namespace the namespace URI of the attribute * @param {String} v the value of the attribute * @param {String }t the type of the attribute * * @see XMLElement */
module.exports = function() {
var XMLAttribute = function (fname, n, nameSpace, v, t){ this.fullName = fname || ""; this.name = n || ""; this.namespace = nameSpace || ""; this.value = v; this.type = t; };
XMLAttribute.prototype = { /** * @member XMLAttribute * The getName() function returns the short name of the attribute * * @return {String} the short name of the attribute */ getName: function() { return this.name; }, /** * @member XMLAttribute * The getFullName() function returns the full name of the attribute * * @return {String} the full name of the attribute */ getFullName: function() { return this.fullName; }, /** * @member XMLAttribute * The getNamespace() function returns the namespace of the attribute * * @return {String} the namespace of the attribute */ getNamespace: function() { return this.namespace; }, /** * @member XMLAttribute * The getValue() function returns the value of the attribute * * @return {String} the value of the attribute */ getValue: function() { return this.value; }, /** * @member XMLAttribute * The getValue() function returns the type of the attribute * * @return {String} the type of the attribute */ getType: function() { return this.type; }, /** * @member XMLAttribute * The setValue() function sets the value of the attribute * * @param {String} newval the new value */ setValue: function(newval) { this.value = newval; } };
return XMLAttribute;
};
},{}],19:[function(require,module,exports){ /**
* XMLElement is a representation of an XML object. The object is able to parse XML code * * @param {PApplet} parent typically use "this" * @param {String} filename name of the XML/SVG file to load * @param {String} xml the xml/svg string * @param {String} fullname the full name of the element * @param {String} namespace the namespace of the URI * @param {String} systemID the system ID of the XML data where the element starts * @param {Integer }lineNr the line in the XML data where the element starts */
module.exports = function(options, undef) {
var Browser = options.Browser, ajax = Browser.ajax, window = Browser.window, XMLHttpRequest = window.XMLHttpRequest, DOMParser = window.DOMParser, XMLAttribute = options. XMLAttribute;
var XMLElement = function(selector, uri, sysid, line) { this.attributes = []; this.children = []; this.fullName = null; this.name = null; this.namespace = ""; this.content = null; this.parent = null; this.lineNr = ""; this.systemID = ""; this.type = "ELEMENT";
if (selector) { if (typeof selector === "string") { if (uri === undef && selector.indexOf("<") > -1) { // load XML from text string this.parse(selector); } else { // XMLElement(fullname, namespace, sysid, line) format this.fullName = selector; this.namespace = uri; this.systemId = sysid; this.lineNr = line; } } else { // XMLElement(this,file) format this.parse(uri); } } }; /** * XMLElement methods * missing: enumerateAttributeNames(), enumerateChildren(), * NOTE: parse does not work when a url is passed in */ XMLElement.prototype = { /** * @member XMLElement * The parse() function retrieves the file via ajax() and uses DOMParser() * parseFromString method to make an XML document * @addon * * @param {String} filename name of the XML/SVG file to load * * @throws ExceptionType Error loading document * * @see XMLElement#parseChildrenRecursive */ parse: function(textstring) { var xmlDoc; try { var extension = textstring.substring(textstring.length-4); if (extension === ".xml" || extension === ".svg") { textstring = ajax(textstring); } xmlDoc = new DOMParser().parseFromString(textstring, "text/xml"); var elements = xmlDoc.documentElement; if (elements) { this.parseChildrenRecursive(null, elements); } else { throw ("Error loading document"); } return this; } catch(e) { throw(e); } }, /** * @member XMLElement * Internal helper function for parse(). * Loops through the * @addon * * @param {XMLElement} parent the parent node * @param {XML document childNodes} elementpath the remaining nodes that need parsing * * @return {XMLElement} the new element and its children elements */ parseChildrenRecursive: function (parent, elementpath){ var xmlelement, xmlattribute, tmpattrib, l, m, child; if (!parent) { // this element is the root element this.fullName = elementpath.localName; this.name = elementpath.nodeName; xmlelement = this; } else { // this element has a parent xmlelement = new XMLElement(elementpath.nodeName); xmlelement.parent = parent; }
// if this is a text node, return a PCData element (parsed character data) if (elementpath.nodeType === 3 && elementpath.textContent !== "") { return this.createPCDataElement(elementpath.textContent); }
// if this is a CDATA node, return a CData element (unparsed character data) if (elementpath.nodeType === 4) { return this.createCDataElement(elementpath.textContent); }
// bind all attributes, if there are any if (elementpath.attributes) { for (l = 0, m = elementpath.attributes.length; l < m; l++) { tmpattrib = elementpath.attributes[l]; xmlattribute = new XMLAttribute(tmpattrib.getname, tmpattrib.nodeName, tmpattrib.namespaceURI, tmpattrib.nodeValue, tmpattrib.nodeType); xmlelement.attributes.push(xmlattribute); } }
// bind all children, if there are any if (elementpath.childNodes) { for (l = 0, m = elementpath.childNodes.length; l < m; l++) { var node = elementpath.childNodes[l]; child = xmlelement.parseChildrenRecursive(xmlelement, node); if (child !== null) { xmlelement.children.push(child); } } }
return xmlelement; }, /** * @member XMLElement * The createElement() function Creates an empty element * * @param {String} fullName the full name of the element * @param {String} namespace the namespace URI * @param {String} systemID the system ID of the XML data where the element starts * @param {int} lineNr the line in the XML data where the element starts */ createElement: function (fullname, namespaceuri, sysid, line) { if (sysid === undef) { return new XMLElement(fullname, namespaceuri); } return new XMLElement(fullname, namespaceuri, sysid, line); }, /** * @member XMLElement * The createPCDataElement() function creates an element to be used for #PCDATA content. * Because Processing discards whitespace TEXT nodes, this method will not build an element * if the passed content is empty after trimming for whitespace. * * @return {XMLElement} new "pcdata" XMLElement, or null if content consists only of whitespace */ createPCDataElement: function (content, isCDATA) { if (content.replace(/^\s+$/g,"") === "") { return null; } var pcdata = new XMLElement(); pcdata.type = "TEXT"; pcdata.content = content; return pcdata; }, /** * @member XMLElement * The createCDataElement() function creates an element to be used for CDATA content. * * @return {XMLElement} new "cdata" XMLElement, or null if content consists only of whitespace */ createCDataElement: function (content) { var cdata = this.createPCDataElement(content); if (cdata === null) { return null; }
cdata.type = "CDATA"; var htmlentities = {"<": "<", ">": ">", "'": "'", '"': """}, entity; for (entity in htmlentities) { if (!Object.hasOwnProperty(htmlentities,entity)) { content = content.replace(new RegExp(entity, "g"), htmlentities[entity]); } } cdata.cdata = content; return cdata; }, /** * @member XMLElement * The hasAttribute() function returns whether an attribute exists * * @param {String} name name of the attribute * @param {String} namespace the namespace URI of the attribute * * @return {boolean} true if the attribute exists */ hasAttribute: function () { if (arguments.length === 1) { return this.getAttribute(arguments[0]) !== null; } if (arguments.length === 2) { return this.getAttribute(arguments[0],arguments[1]) !== null; } }, /** * @member XMLElement * The equals() function checks to see if the XMLElement being passed in equals another XMLElement * * @param {XMLElement} rawElement the element to compare to * * @return {boolean} true if the element equals another element */ equals: function(other) { if (!(other instanceof XMLElement)) { return false; } var i, j; if (this.fullName !== other.fullName) { return false; } if (this.attributes.length !== other.getAttributeCount()) { return false; } // attributes may be ordered differently if (this.attributes.length !== other.attributes.length) { return false; } var attr_name, attr_ns, attr_value, attr_type, attr_other; for (i = 0, j = this.attributes.length; i < j; i++) { attr_name = this.attributes[i].getName(); attr_ns = this.attributes[i].getNamespace(); attr_other = other.findAttribute(attr_name, attr_ns); if (attr_other === null) { return false; } if (this.attributes[i].getValue() !== attr_other.getValue()) { return false; } if (this.attributes[i].getType() !== attr_other.getType()) { return false; } } // children must be ordered identically if (this.children.length !== other.getChildCount()) { return false; } if (this.children.length>0) { var child1, child2; for (i = 0, j = this.children.length; i < j; i++) { child1 = this.getChild(i); child2 = other.getChild(i); if (!child1.equals(child2)) { return false; } } return true; } return (this.content === other.content); }, /** * @member XMLElement * The getContent() function returns the content of an element. If there is no such content, null is returned * * @return {String} the (possibly null) content */ getContent: function(){ if (this.type === "TEXT" || this.type === "CDATA") { return this.content; } var children = this.children; if (children.length === 1 && (children[0].type === "TEXT" || children[0].type === "CDATA")) { return children[0].content; } return null; }, /** * @member XMLElement * The getAttribute() function returns the value of an attribute * * @param {String} name the non-null full name of the attribute * @param {String} namespace the namespace URI, which may be null * @param {String} defaultValue the default value of the attribute * * @return {String} the value, or defaultValue if the attribute does not exist */ getAttribute: function (){ var attribute; if (arguments.length === 2) { attribute = this.findAttribute(arguments[0]); if (attribute) { return attribute.getValue(); } return arguments[1]; } else if (arguments.length === 1) { attribute = this.findAttribute(arguments[0]); if (attribute) { return attribute.getValue(); } return null; } else if (arguments.length === 3) { attribute = this.findAttribute(arguments[0],arguments[1]); if (attribute) { return attribute.getValue(); } return arguments[2]; } }, /** * @member XMLElement * The getStringAttribute() function returns the string attribute of the element * If the defaultValue parameter is used and the attribute doesn't exist, the defaultValue value is returned. * When calling the function without the defaultValue parameter, if the attribute doesn't exist, the value 0 is returned. * * @param name the name of the attribute * @param defaultValue value returned if the attribute is not found * * @return {String} the value, or defaultValue if the attribute does not exist */ getStringAttribute: function() { if (arguments.length === 1) { return this.getAttribute(arguments[0]); } if (arguments.length === 2) { return this.getAttribute(arguments[0], arguments[1]); } return this.getAttribute(arguments[0], arguments[1],arguments[2]); }, /** * Processing 1.5 XML API wrapper for the generic String * attribute getter. This may only take one argument. */ getString: function(attributeName) { return this.getStringAttribute(attributeName); }, /** * @member XMLElement * The getFloatAttribute() function returns the float attribute of the element. * If the defaultValue parameter is used and the attribute doesn't exist, the defaultValue value is returned. * When calling the function without the defaultValue parameter, if the attribute doesn't exist, the value 0 is returned. * * @param name the name of the attribute * @param defaultValue value returned if the attribute is not found * * @return {float} the value, or defaultValue if the attribute does not exist */ getFloatAttribute: function() { if (arguments.length === 1 ) { return parseFloat(this.getAttribute(arguments[0], 0)); } if (arguments.length === 2 ) { return this.getAttribute(arguments[0], arguments[1]); } return this.getAttribute(arguments[0], arguments[1],arguments[2]); }, /** * Processing 1.5 XML API wrapper for the generic float * attribute getter. This may only take one argument. */ getFloat: function(attributeName) { return this.getFloatAttribute(attributeName); }, /** * @member XMLElement * The getIntAttribute() function returns the integer attribute of the element. * If the defaultValue parameter is used and the attribute doesn't exist, the defaultValue value is returned. * When calling the function without the defaultValue parameter, if the attribute doesn't exist, the value 0 is returned. * * @param name the name of the attribute * @param defaultValue value returned if the attribute is not found * * @return {int} the value, or defaultValue if the attribute does not exist */ getIntAttribute: function () { if (arguments.length === 1) { return this.getAttribute( arguments[0], 0 ); } if (arguments.length === 2) { return this.getAttribute(arguments[0], arguments[1]); } return this.getAttribute(arguments[0], arguments[1],arguments[2]); }, /** * Processing 1.5 XML API wrapper for the generic int * attribute getter. This may only take one argument. */ getInt: function(attributeName) { return this.getIntAttribute(attributeName); }, /** * @member XMLElement * The hasChildren() function returns whether the element has children. * * @return {boolean} true if the element has children. */ hasChildren: function () { return this.children.length > 0 ; }, /** * @member XMLElement * The addChild() function adds a child element * * @param {XMLElement} child the non-null child to add. */ addChild: function (child) { if (child !== null) { child.parent = this; this.children.push(child); } }, /** * @member XMLElement * The insertChild() function inserts a child element at the index provided * * @param {XMLElement} child the non-null child to add. * @param {int} index where to put the child. */ insertChild: function (child, index) { if (child) { if ((child.getLocalName() === null) && (! this.hasChildren())) { var lastChild = this.children[this.children.length -1]; if (lastChild.getLocalName() === null) { lastChild.setContent(lastChild.getContent() + child.getContent()); return; } } child.parent = this; this.children.splice(index,0,child); } }, /** * @member XMLElement * The getChild() returns the child XMLElement as specified by the index parameter. * The value of the index parameter must be less than the total number of children to avoid going out of the array storing the child elements. * When the path parameter is specified, then it will return all children that match that path. The path is a series of elements and sub-elements, separated by slashes. * * @param {int} index where to put the child. * @param {String} path path to a particular element * * @return {XMLElement} the element */ getChild: function (selector) { if (typeof selector === "number") { return this.children[selector]; } if (selector.indexOf('/') !== -1) { // path traversal is required return this.getChildRecursive(selector.split("/"), 0); } var kid, kidName; for (var i = 0, j = this.getChildCount(); i < j; i++) { kid = this.getChild(i); kidName = kid.getName(); if (kidName !== null && kidName === selector) { return kid; } } return null; }, /** * @member XMLElement * The getChildren() returns all of the children as an XMLElement array. * When the path parameter is specified, then it will return all children that match that path. * The path is a series of elements and sub-elements, separated by slashes. * * @param {String} path element name or path/to/element * * @return {XMLElement} array of child elements that match * * @see XMLElement#getChildCount() * @see XMLElement#getChild() */ getChildren: function(){ if (arguments.length === 1) { if (typeof arguments[0] === "number") { return this.getChild( arguments[0]); } if (arguments[0].indexOf('/') !== -1) { // path was given return this.getChildrenRecursive( arguments[0].split("/"), 0); } var matches = []; var kid, kidName; for (var i = 0, j = this.getChildCount(); i < j; i++) { kid = this.getChild(i); kidName = kid.getName(); if (kidName !== null && kidName === arguments[0]) { matches.push(kid); } } return matches; } return this.children; }, /** * @member XMLElement * The getChildCount() returns the number of children for the element. * * @return {int} the count * * @see XMLElement#getChild() * @see XMLElement#getChildren() */ getChildCount: function() { return this.children.length; }, /** * @member XMLElement * Internal helper function for getChild(). * * @param {String[]} items result of splitting the query on slashes * @param {int} offset where in the items[] array we're currently looking * * @return {XMLElement} matching element or null if no match */ getChildRecursive: function (items, offset) { // terminating clause: we are the requested candidate if (offset === items.length) { return this; } // continuation clause var kid, kidName, matchName = items[offset]; for(var i = 0, j = this.getChildCount(); i < j; i++) { kid = this.getChild(i); kidName = kid.getName(); if (kidName !== null && kidName === matchName) { return kid.getChildRecursive(items, offset+1); } } return null; }, /** * @member XMLElement * Internal helper function for getChildren(). * * @param {String[]} items result of splitting the query on slashes * @param {int} offset where in the items[] array we're currently looking * * @return {XMLElement[]} matching elements or empty array if no match */ getChildrenRecursive: function (items, offset) { if (offset === items.length-1) { return this.getChildren(items[offset]); } var matches = this.getChildren(items[offset]); var kidMatches = []; for (var i = 0; i < matches.length; i++) { kidMatches = kidMatches.concat(matches[i].getChildrenRecursive(items, offset+1)); } return kidMatches; }, /** * @member XMLElement * The isLeaf() function returns whether the element is a leaf element. * * @return {boolean} true if the element has no children. */ isLeaf: function() { return !this.hasChildren(); }, /** * @member XMLElement * The listChildren() function put the names of all children into an array. Same as looping through * each child and calling getName() on each XMLElement. * * @return {String[]} a list of element names. */ listChildren: function() { var arr = []; for (var i = 0, j = this.children.length; i < j; i++) { arr.push( this.getChild(i).getName()); } return arr; }, /** * @member XMLElement * The removeAttribute() function removes an attribute * * @param {String} name the non-null name of the attribute. * @param {String} namespace the namespace URI of the attribute, which may be null. */ removeAttribute: function (name , namespace) { this.namespace = namespace || ""; for (var i = 0, j = this.attributes.length; i < j; i++) { if (this.attributes[i].getName() === name && this.attributes[i].getNamespace() === this.namespace) { this.attributes.splice(i, 1); break; } } }, /** * @member XMLElement * The removeChild() removes a child element. * * @param {XMLElement} child the the non-null child to be renoved */ removeChild: function(child) { if (child) { for (var i = 0, j = this.children.length; i < j; i++) { if (this.children[i].equals(child)) { this.children.splice(i, 1); break; } } } }, /** * @member XMLElement * The removeChildAtIndex() removes the child located at a certain index * * @param {int} index the index of the child, where the first child has index 0 */ removeChildAtIndex: function(index) { if (this.children.length > index) { //make sure its not outofbounds this.children.splice(index, 1); } }, /** * @member XMLElement * The findAttribute() function searches an attribute * * @param {String} name fullName the non-null full name of the attribute * @param {String} namespace the name space, which may be null * * @return {XMLAttribute} the attribute, or null if the attribute does not exist. */ findAttribute: function (name, namespace) { this.namespace = namespace || ""; for (var i = 0, j = this.attributes.length; i < j; i++) { if (this.attributes[i].getName() === name && this.attributes[i].getNamespace() === this.namespace) { return this.attributes[i]; } } return null; }, /** * @member XMLElement * The setAttribute() function sets an attribute. * * @param {String} name the non-null full name of the attribute * @param {String} namespace the non-null value of the attribute */ setAttribute: function() { var attr; if (arguments.length === 3) { var index = arguments[0].indexOf(':'); var name = arguments[0].substring(index + 1); attr = this.findAttribute(name, arguments[1]); if (attr) { attr.setValue(arguments[2]); } else { attr = new XMLAttribute(arguments[0], name, arguments[1], arguments[2], "CDATA"); this.attributes.push(attr); } } else { attr = this.findAttribute(arguments[0]); if (attr) { attr.setValue(arguments[1]); } else { attr = new XMLAttribute(arguments[0], arguments[0], null, arguments[1], "CDATA"); this.attributes.push(attr); } } }, /** * Processing 1.5 XML API wrapper for the generic String * attribute setter. This must take two arguments. */ setString: function(attribute, value) { this.setAttribute(attribute, value); }, /** * Processing 1.5 XML API wrapper for the generic int * attribute setter. This must take two arguments. */ setInt: function(attribute, value) { this.setAttribute(attribute, value); }, /** * Processing 1.5 XML API wrapper for the generic float * attribute setter. This must take two arguments. */ setFloat: function(attribute, value) { this.setAttribute(attribute, value); }, /** * @member XMLElement * The setContent() function sets the #PCDATA content. It is an error to call this method with a * non-null value if there are child objects. * * @param {String} content the (possibly null) content */ setContent: function(content) { if (this.children.length > 0) { Processing.debug("Tried to set content for XMLElement with children"); } this.content = content; }, /** * @member XMLElement * The setName() function sets the full name. This method also sets the short name and clears the * namespace URI. * * @param {String} name the non-null name * @param {String} namespace the namespace URI, which may be null. */ setName: function() { if (arguments.length === 1) { this.name = arguments[0]; this.fullName = arguments[0]; this.namespace = null; } else { var index = arguments[0].indexOf(':'); if ((arguments[1] === null) || (index < 0)) { this.name = arguments[0]; } else { this.name = arguments[0].substring(index + 1); } this.fullName = arguments[0]; this.namespace = arguments[1]; } }, /** * @member XMLElement * The getName() function returns the full name (i.e. the name including an eventual namespace * prefix) of the element. * * @return {String} the name, or null if the element only contains #PCDATA. */ getName: function() { return this.fullName; }, /** * @member XMLElement * The getLocalName() function returns the local name (i.e. the name excluding an eventual namespace * prefix) of the element. * * @return {String} the name, or null if the element only contains #PCDATA. */ getLocalName: function() { return this.name; }, /** * @member XMLElement * The getAttributeCount() function returns the number of attributes for the node * that this XMLElement represents. * * @return {int} the number of attributes in this XMLElement */ getAttributeCount: function() { return this.attributes.length; }, /** * @member XMLElement * The toString() function returns the XML definition of an XMLElement. * * @return {String} the XML definition of this XMLElement */ toString: function() { // shortcut for text and cdata nodes if (this.type === "TEXT") { return this.content; }
if (this.type === "CDATA") { return this.cdata; }
// real XMLElements var tagstring = this.fullName; var xmlstring = "<" + tagstring; var a,c;
// serialize the attributes to XML string for (a = 0; a<this.attributes.length; a++) { var attr = this.attributes[a]; xmlstring += " " + attr.getName() + "=" + '"' + attr.getValue() + '"'; }
// serialize all children to XML string if (this.children.length === 0) { if (this.content==="") { xmlstring += "/>"; } else { xmlstring += ">" + this.content + "</"+tagstring+">"; } } else { xmlstring += ">"; for (c = 0; c<this.children.length; c++) { xmlstring += this.children[c].toString(); } xmlstring += "</" + tagstring + ">"; } return xmlstring; } };
/** * static Processing 1.5 XML API wrapper for the * parse method. This may only take one argument. */ XMLElement.parse = function(xmlstring) { var element = new XMLElement(); element.parse(xmlstring); return element; };
return XMLElement;
};
},{}],20:[function(require,module,exports){ /**
* web colors, by name */
module.exports = {
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" };
},{}],21:[function(require,module,exports){ module.exports = function(virtHashCode, virtEquals, undef) {
return function withProxyFunctions(p, removeFirstArgument) { /** * The contains(string) function returns true if the string passed in the parameter * is a substring of this string. It returns false if the string passed * in the parameter is not a substring of this string. * * @param {String} The string to look for in the current string * * @return {boolean} returns true if this string contains * the string passed as parameter. returns false, otherwise. * */ p.__contains = function (subject, subStr) { if (typeof subject !== "string") { return subject.contains.apply(subject, removeFirstArgument(arguments)); } //Parameter is not null AND //The type of the parameter is the same as this object (string) //The javascript function that finds a substring returns 0 or higher return ( (subject !== null) && (subStr !== null) && (typeof subStr === "string") && (subject.indexOf(subStr) > -1) ); };
/** * The __replaceAll() function searches all matches between a substring (or regular expression) and a string, * and replaces the matched substring with a new substring * * @param {String} subject a substring * @param {String} regex a substring or a regular expression * @param {String} replace the string to replace the found value * * @return {String} returns result * * @see #match */ p.__replaceAll = function(subject, regex, replacement) { if (typeof subject !== "string") { return subject.replaceAll.apply(subject, removeFirstArgument(arguments)); }
return subject.replace(new RegExp(regex, "g"), replacement); };
/** * The __replaceFirst() function searches first matche between a substring (or regular expression) and a string, * and replaces the matched substring with a new substring * * @param {String} subject a substring * @param {String} regex a substring or a regular expression * @param {String} replace the string to replace the found value * * @return {String} returns result * * @see #match */ p.__replaceFirst = function(subject, regex, replacement) { if (typeof subject !== "string") { return subject.replaceFirst.apply(subject, removeFirstArgument(arguments)); }
return subject.replace(new RegExp(regex, ""), replacement); };
/** * The __replace() function searches all matches between a substring and a string, * and replaces the matched substring with a new substring * * @param {String} subject a substring * @param {String} what a substring to find * @param {String} replacement the string to replace the found value * * @return {String} returns result */ p.__replace = function(subject, what, replacement) { if (typeof subject !== "string") { return subject.replace.apply(subject, removeFirstArgument(arguments)); } if (what instanceof RegExp) { return subject.replace(what, replacement); }
if (typeof what !== "string") { what = what.toString(); } if (what === "") { return subject; }
var i = subject.indexOf(what); if (i < 0) { return subject; }
var j = 0, result = ""; do { result += subject.substring(j, i) + replacement; j = i + what.length; } while ( (i = subject.indexOf(what, j)) >= 0); return result + subject.substring(j); };
/** * The __equals() function compares two strings (or objects) to see if they are the same. * This method is necessary because it's not possible to compare strings using the equality operator (==). * Returns true if the strings are the same and false if they are not. * * @param {String} subject a string used for comparison * @param {String} other a string used for comparison with * * @return {boolean} true is the strings are the same false otherwise */ p.__equals = function(subject, other) { if (subject.equals instanceof Function) { return subject.equals.apply(subject, removeFirstArgument(arguments)); }
return virtEquals(subject, other); };
/** * The __equalsIgnoreCase() function compares two strings to see if they are the same. * Returns true if the strings are the same, either when forced to all lower case or * all upper case. * * @param {String} subject a string used for comparison * @param {String} other a string used for comparison with * * @return {boolean} true is the strings are the same, ignoring case. false otherwise */ p.__equalsIgnoreCase = function(subject, other) { if (typeof subject !== "string") { return subject.equalsIgnoreCase.apply(subject, removeFirstArgument(arguments)); }
return subject.toLowerCase() === other.toLowerCase(); };
/** * The __toCharArray() function splits the string into a char array. * * @param {String} subject The string * * @return {Char[]} a char array */ p.__toCharArray = function(subject) { if (typeof subject !== "string") { return subject.toCharArray.apply(subject, removeFirstArgument(arguments)); }
var chars = []; for (var i = 0, len = subject.length; i < len; ++i) { chars[i] = new Char(subject.charAt(i)); } return chars; };
/** * The __split() function splits a string using the regex delimiter * specified. If limit is specified, the resultant array will have number * of elements equal to or less than the limit. * * @param {String} subject string to be split * @param {String} regexp regex string used to split the subject * @param {int} limit max number of tokens to be returned * * @return {String[]} an array of tokens from the split string */ p.__split = function(subject, regex, limit) { if (typeof subject !== "string") { return subject.split.apply(subject, removeFirstArgument(arguments)); }
var pattern = new RegExp(regex);
// If limit is not specified, use JavaScript's built-in String.split. if ((limit === undef) || (limit < 1)) { return subject.split(pattern); }
// If limit is specified, JavaScript's built-in String.split has a // different behaviour than Java's. A Java-compatible implementation is // provided here. var result = [], currSubject = subject, pos; while (((pos = currSubject.search(pattern)) !== -1) && (result.length < (limit - 1))) { var match = pattern.exec(currSubject).toString(); result.push(currSubject.substring(0, pos)); currSubject = currSubject.substring(pos + match.length); } if ((pos !== -1) || (currSubject !== "")) { result.push(currSubject); } return result; };
/** * The codePointAt() function returns the unicode value of the character at a given index of a string. * * @param {int} idx the index of the character * * @return {String} code the String containing the unicode value of the character */ p.__codePointAt = function(subject, idx) { var code = subject.charCodeAt(idx), hi, low; if (0xD800 <= code && code <= 0xDBFF) { hi = code; low = subject.charCodeAt(idx + 1); return ((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000; } return code; };
/** * The matches() function checks whether or not a string matches a given regular expression. * * @param {String} str the String on which the match is tested * @param {String} regexp the regexp for which a match is tested * * @return {boolean} true if the string fits the regexp, false otherwise */ p.__matches = function(str, regexp) { return (new RegExp(regexp)).test(str); };
/** * The startsWith() function tests if a string starts with the specified prefix. If the prefix * is the empty String or equal to the subject String, startsWith() will also return true. * * @param {String} prefix the String used to compare against the start of the subject String. * @param {int} toffset (optional) an offset into the subject String where searching should begin. * * @return {boolean} true if the subject String starts with the prefix. */ p.__startsWith = function(subject, prefix, toffset) { if (typeof subject !== "string") { return subject.startsWith.apply(subject, removeFirstArgument(arguments)); }
toffset = toffset || 0; if (toffset < 0 || toffset > subject.length) { return false; } return (prefix === || prefix === subject) ? true : (subject.indexOf(prefix) === toffset); };
/** * The endsWith() function tests if a string ends with the specified suffix. If the suffix * is the empty String, endsWith() will also return true. * * @param {String} suffix the String used to compare against the end of the subject String. * * @return {boolean} true if the subject String starts with the prefix. */ p.__endsWith = function(subject, suffix) { if (typeof subject !== "string") { return subject.endsWith.apply(subject, removeFirstArgument(arguments)); }
var suffixLen = suffix ? suffix.length : 0; return (suffix === || suffix === subject) ? true : (subject.indexOf(suffix) === subject.length - suffixLen); };
/** * The returns hash code of the. * * @param {Object} subject The string * * @return {int} a hash code */ p.__hashCode = function(subject) { if (subject.hashCode instanceof Function) { return subject.hashCode.apply(subject, removeFirstArgument(arguments)); } return virtHashCode(subject); };
/** * The __printStackTrace() prints stack trace to the console. * * @param {Exception} subject The error */ p.__printStackTrace = function(subject) { p.println("Exception: " + subject.toString() ); }; };
};
},{}],22:[function(require,module,exports){ /**
* For many "math" functions, we can delegate * to the Math object. For others, we can't. */
module.exports = function withMath(p, undef) {
var internalRandomGenerator = function() { return Math.random(); };
/** * Calculates the absolute value (magnitude) of a number. The absolute value of a number is always positive. * * @param {int|float} value int or float * * @returns {int|float} */ p.abs = Math.abs;
/** * Calculates the closest int value that is greater than or equal to the value of the parameter. * For example, ceil(9.03) returns the value 10. * * @param {float} value float * * @returns {int} * * @see floor * @see round */ p.ceil = Math.ceil;
/** * Returns Euler's number e (2.71828...) raised to the power of the value parameter. * * @param {int|float} value int or float: the exponent to raise e to * * @returns {float} */ p.exp = Math.exp;
/** * Calculates the closest int value that is less than or equal to the value of the parameter. * * @param {int|float} value the value to floor * * @returns {int|float} * * @see ceil * @see round */ p.floor = Math.floor;
/** * Calculates the natural logarithm (the base-e logarithm) of a number. This function * expects the values greater than 0.0. * * @param {int|float} value int or float: number must be greater then 0.0 * * @returns {float} */ p.log = Math.log;
/** * Facilitates exponential expressions. The pow() function is an efficient way of * multiplying numbers by themselves (or their reciprocal) in large quantities. * For example, pow(3, 5) is equivalent to the expression 3*3*3*3*3 and pow(3, -5) * is equivalent to 1 / 3*3*3*3*3. * * @param {int|float} num base of the exponential expression * @param {int|float} exponent power of which to raise the base * * @returns {float} * * @see sqrt */ p.pow = Math.pow;
/** * Calculates the integer closest to the value parameter. For example, round(9.2) returns the value 9. * * @param {float} value number to round * * @returns {int} * * @see floor * @see ceil */ p.round = Math.round; /** * Calculates the square root of a number. The square root of a number is always positive, * even though there may be a valid negative root. The square root s of number a is such * that s*s = a. It is the opposite of squaring. * * @param {float} value int or float, non negative * * @returns {float} * * @see pow * @see sq */
p.sqrt = Math.sqrt;
// Trigonometry /** * The inverse of cos(), returns the arc cosine of a value. This function expects the * values in the range of -1 to 1 and values are returned in the range 0 to PI (3.1415927). * * @param {float} value the value whose arc cosine is to be returned * * @returns {float} * * @see cos * @see asin * @see atan */ p.acos = Math.acos;
/** * The inverse of sin(), returns the arc sine of a value. This function expects the values * in the range of -1 to 1 and values are returned in the range -PI/2 to PI/2. * * @param {float} value the value whose arc sine is to be returned * * @returns {float} * * @see sin * @see acos * @see atan */ p.asin = Math.asin;
/** * The inverse of tan(), returns the arc tangent of a value. This function expects the values * in the range of -Infinity to Infinity (exclusive) and values are returned in the range -PI/2 to PI/2 . * * @param {float} value -Infinity to Infinity (exclusive) * * @returns {float} * * @see tan * @see asin * @see acos */ p.atan = Math.atan;
/** * Calculates the angle (in radians) from a specified point to the coordinate origin as measured from * the positive x-axis. Values are returned as a float in the range from PI to -PI. The atan2() function * is most often used for orienting geometry to the position of the cursor. Note: The y-coordinate of the * point is the first parameter and the x-coordinate is the second due the the structure of calculating the tangent. * * @param {float} y y-coordinate of the point * @param {float} x x-coordinate of the point * * @returns {float} * * @see tan */ p.atan2 = Math.atan2;
/** * Calculates the cosine of an angle. This function expects the values of the angle parameter to be provided * in radians (values from 0 to PI*2). Values are returned in the range -1 to 1. * * @param {float} value an angle in radians * * @returns {float} * * @see tan * @see sin */ p.cos = Math.cos;
/** * Calculates the sine of an angle. This function expects the values of the angle parameter to be provided in * radians (values from 0 to 6.28). Values are returned in the range -1 to 1. * * @param {float} value an angle in radians * * @returns {float} * * @see cos * @see radians */ p.sin = Math.sin;
/** * Calculates the ratio of the sine and cosine of an angle. This function expects the values of the angle * parameter to be provided in radians (values from 0 to PI*2). Values are returned in the range infinity to -infinity. * * @param {float} value an angle in radians * * @returns {float} * * @see cos * @see sin * @see radians */ p.tan = Math.tan;
/** * Constrains a value to not exceed a maximum and minimum value. * * @param {int|float} value the value to constrain * @param {int|float} value minimum limit * @param {int|float} value maximum limit * * @returns {int|float} * * @see max * @see min */ p.constrain = function(aNumber, aMin, aMax) { return aNumber > aMax ? aMax : aNumber < aMin ? aMin : aNumber; };
/** * Calculates the distance between two points. * * @param {int|float} x1 int or float: x-coordinate of the first point * @param {int|float} y1 int or float: y-coordinate of the first point * @param {int|float} z1 int or float: z-coordinate of the first point * @param {int|float} x2 int or float: x-coordinate of the second point * @param {int|float} y2 int or float: y-coordinate of the second point * @param {int|float} z2 int or float: z-coordinate of the second point * * @returns {float} */ p.dist = function() { var dx, dy, dz; if (arguments.length === 4) { dx = arguments[0] - arguments[2]; dy = arguments[1] - arguments[3]; return Math.sqrt(dx * dx + dy * dy); } if (arguments.length === 6) { dx = arguments[0] - arguments[3]; dy = arguments[1] - arguments[4]; dz = arguments[2] - arguments[5]; return Math.sqrt(dx * dx + dy * dy + dz * dz); } };
/** * Calculates a number between two numbers at a specific increment. The amt parameter is the * amount to interpolate between the two values where 0.0 equal to the first point, 0.1 is very * near the first point, 0.5 is half-way in between, etc. The lerp function is convenient for * creating motion along a straight path and for drawing dotted lines. * * @param {int|float} value1 float or int: first value * @param {int|float} value2 float or int: second value * @param {int|float} amt float: between 0.0 and 1.0 * * @returns {float} * * @see curvePoint * @see bezierPoint */ p.lerp = function(value1, value2, amt) { return ((value2 - value1) * amt) + value1; };
/** * Calculates the magnitude (or length) of a vector. A vector is a direction in space commonly * used in computer graphics and linear algebra. Because it has no "start" position, the magnitude * of a vector can be thought of as the distance from coordinate (0,0) to its (x,y) value. * Therefore, mag() is a shortcut for writing "dist(0, 0, x, y)". * * @param {int|float} a float or int: first value * @param {int|float} b float or int: second value * @param {int|float} c float or int: third value * * @returns {float} * * @see dist */ p.mag = function(a, b, c) { if (c) { return Math.sqrt(a * a + b * b + c * c); }
return Math.sqrt(a * a + b * b); };
/** * Re-maps a number from one range to another. In the example above, the number '25' is converted from * a value in the range 0..100 into a value that ranges from the left edge (0) to the right edge (width) of the screen. * Numbers outside the range are not clamped to 0 and 1, because out-of-range values are often intentional and useful. * * @param {float} value The incoming value to be converted * @param {float} istart Lower bound of the value's current range * @param {float} istop Upper bound of the value's current range * @param {float} ostart Lower bound of the value's target range * @param {float} ostop Upper bound of the value's target range * * @returns {float} * * @see norm * @see lerp */ p.map = function(value, istart, istop, ostart, ostop) { return ostart + (ostop - ostart) * ((value - istart) / (istop - istart)); };
/** * Determines the largest value in a sequence of numbers. * * @param {int|float} value1 int or float * @param {int|float} value2 int or float * @param {int|float} value3 int or float * @param {int|float} array int or float array * * @returns {int|float} * * @see min */ p.max = function() { if (arguments.length === 2) { return arguments[0] < arguments[1] ? arguments[1] : arguments[0]; } var numbers = arguments.length === 1 ? arguments[0] : arguments; // if single argument, array is used if (! ("length" in numbers && numbers.length > 0)) { throw "Non-empty array is expected"; } var max = numbers[0], count = numbers.length; for (var i = 1; i < count; ++i) { if (max < numbers[i]) { max = numbers[i]; } } return max; };
/** * Determines the smallest value in a sequence of numbers. * * @param {int|float} value1 int or float * @param {int|float} value2 int or float * @param {int|float} value3 int or float * @param {int|float} array int or float array * * @returns {int|float} * * @see max */ p.min = function() { if (arguments.length === 2) { return arguments[0] < arguments[1] ? arguments[0] : arguments[1]; } var numbers = arguments.length === 1 ? arguments[0] : arguments; // if single argument, array is used if (! ("length" in numbers && numbers.length > 0)) { throw "Non-empty array is expected"; } var min = numbers[0], count = numbers.length; for (var i = 1; i < count; ++i) { if (min > numbers[i]) { min = numbers[i]; } } return min; };
/** * Normalizes a number from another range into a value between 0 and 1. * Identical to map(value, low, high, 0, 1); * Numbers outside the range are not clamped to 0 and 1, because out-of-range * values are often intentional and useful. * * @param {float} aNumber The incoming value to be converted * @param {float} low Lower bound of the value's current range * @param {float} high Upper bound of the value's current range * * @returns {float} * * @see map * @see lerp */ p.norm = function(aNumber, low, high) { return (aNumber - low) / (high - low); };
/** * Squares a number (multiplies a number by itself). The result is always a positive number, * as multiplying two negative numbers always yields a positive result. For example, -1 * -1 = 1. * * @param {float} value int or float * * @returns {float} * * @see sqrt */ p.sq = function(aNumber) { return aNumber * aNumber; };
/** * Converts a radian measurement to its corresponding value in degrees. Radians and degrees are two ways of * measuring the same thing. There are 360 degrees in a circle and 2*PI radians in a circle. For example, * 90 degrees = PI/2 = 1.5707964. All trigonometric methods in Processing require their parameters to be specified in radians. * * @param {int|float} value an angle in radians * * @returns {float} * * @see radians */ p.degrees = function(aAngle) { return (aAngle * 180) / Math.PI; };
/** * Generates random numbers. Each time the random() function is called, it returns an unexpected value within * the specified range. If one parameter is passed to the function it will return a float between zero and the * value of the high parameter. The function call random(5) returns values between 0 and 5 (starting at zero, * up to but not including 5). If two parameters are passed, it will return a float with a value between the * parameters. The function call random(-5, 10.2) returns values starting at -5 up to (but not including) 10.2. * To convert a floating-point random number to an integer, use the int() function. * * @param {int|float} value1 if one parameter is used, the top end to random from, if two params the low end * @param {int|float} value2 the top end of the random range * * @returns {float} * * @see randomSeed * @see noise */ p.random = function() { if(arguments.length === 0) { return internalRandomGenerator(); } if(arguments.length === 1) { return internalRandomGenerator() * arguments[0]; } var aMin = arguments[0], aMax = arguments[1]; return internalRandomGenerator() * (aMax - aMin) + aMin; };
// Pseudo-random generator function Marsaglia(i1, i2) { // from http://www.math.uni-bielefeld.de/~sillke/ALGORITHMS/random/marsaglia-c var z=i1 || 362436069, w= i2 || 521288629; var intGenerator = function() { z=(36969*(z&65535)+(z>>>16)) & 0xFFFFFFFF; w=(18000*(w&65535)+(w>>>16)) & 0xFFFFFFFF; return (((z&0xFFFF)<<16) | (w&0xFFFF)) & 0xFFFFFFFF; };
this.doubleGenerator = function() { var i = intGenerator() / 4294967296; return i < 0 ? 1 + i : i; }; this.intGenerator = intGenerator; }
Marsaglia.createRandomized = function() { var now = new Date(); return new Marsaglia((now / 60000) & 0xFFFFFFFF, now & 0xFFFFFFFF); };
/** * Sets the seed value for random(). By default, random() produces different results each time the * program is run. Set the value parameter to a constant to return the same pseudo-random numbers * each time the software is run. * * @param {int|float} seed int * * @see random * @see noise * @see noiseSeed */ p.randomSeed = function(seed) { internalRandomGenerator = (new Marsaglia(seed)).doubleGenerator; this.haveNextNextGaussian = false; };
/** * Returns a float from a random series of numbers having a mean of 0 and standard deviation of 1. Each time * the randomGaussian() function is called, it returns a number fitting a Gaussian, or normal, distribution. * There is theoretically no minimum or maximum value that randomGaussian() might return. Rather, there is just a * very low probability that values far from the mean will be returned; and a higher probability that numbers * near the mean will be returned. * * @returns {float} * * @see random * @see noise */ p.randomGaussian = function() { if (this.haveNextNextGaussian) { this.haveNextNextGaussian = false; return this.nextNextGaussian; } var v1, v2, s; do { v1 = 2 * internalRandomGenerator() - 1; // between -1.0 and 1.0 v2 = 2 * internalRandomGenerator() - 1; // between -1.0 and 1.0 s = v1 * v1 + v2 * v2; } while (s >= 1 || s === 0);
var multiplier = Math.sqrt(-2 * Math.log(s) / s); this.nextNextGaussian = v2 * multiplier; this.haveNextNextGaussian = true;
return v1 * multiplier; };
// Noise functions and helpers function PerlinNoise(seed) { var rnd = seed !== undef ? new Marsaglia(seed) : Marsaglia.createRandomized(); var i, j; // http://www.noisemachine.com/talk1/17b.html // http://mrl.nyu.edu/~perlin/noise/ // generate permutation var perm = new Uint8Array(512); for(i=0;i<256;++i) { perm[i] = i; } for(i=0;i<256;++i) { var t = perm[j = rnd.intGenerator() & 0xFF]; perm[j] = perm[i]; perm[i] = t; } // copy to avoid taking mod in perm[0]; for(i=0;i<256;++i) { perm[i + 256] = perm[i]; }
function grad3d(i,x,y,z) { var h = i & 15; // convert into 12 gradient directions var u = h<8 ? x : y, v = h<4 ? y : h===12||h===14 ? x : z; return ((h&1) === 0 ? u : -u) + ((h&2) === 0 ? v : -v); }
function grad2d(i,x,y) { var v = (i & 1) === 0 ? x : y; return (i&2) === 0 ? -v : v; }
function grad1d(i,x) { return (i&1) === 0 ? -x : x; }
function lerp(t,a,b) { return a + t * (b - a); }
this.noise3d = function(x, y, z) { var X = Math.floor(x)&255, Y = Math.floor(y)&255, Z = Math.floor(z)&255; x -= Math.floor(x); y -= Math.floor(y); z -= Math.floor(z); var fx = (3-2*x)*x*x, fy = (3-2*y)*y*y, fz = (3-2*z)*z*z; var p0 = perm[X]+Y, p00 = perm[p0] + Z, p01 = perm[p0 + 1] + Z, p1 = perm[X + 1] + Y, p10 = perm[p1] + Z, p11 = perm[p1 + 1] + Z; return lerp(fz, lerp(fy, lerp(fx, grad3d(perm[p00], x, y, z), grad3d(perm[p10], x-1, y, z)), lerp(fx, grad3d(perm[p01], x, y-1, z), grad3d(perm[p11], x-1, y-1,z))), lerp(fy, lerp(fx, grad3d(perm[p00 + 1], x, y, z-1), grad3d(perm[p10 + 1], x-1, y, z-1)), lerp(fx, grad3d(perm[p01 + 1], x, y-1, z-1), grad3d(perm[p11 + 1], x-1, y-1,z-1)))); };
this.noise2d = function(x, y) { var X = Math.floor(x)&255, Y = Math.floor(y)&255; x -= Math.floor(x); y -= Math.floor(y); var fx = (3-2*x)*x*x, fy = (3-2*y)*y*y; var p0 = perm[X]+Y, p1 = perm[X + 1] + Y; return lerp(fy, lerp(fx, grad2d(perm[p0], x, y), grad2d(perm[p1], x-1, y)), lerp(fx, grad2d(perm[p0 + 1], x, y-1), grad2d(perm[p1 + 1], x-1, y-1))); };
this.noise1d = function(x) { var X = Math.floor(x)&255; x -= Math.floor(x); var fx = (3-2*x)*x*x; return lerp(fx, grad1d(perm[X], x), grad1d(perm[X+1], x-1)); }; }
// processing defaults var noiseProfile = { generator: undef, octaves: 4, fallout: 0.5, seed: undef};
/** * Returns the Perlin noise value at specified coordinates. Perlin noise is a random sequence * generator producing a more natural ordered, harmonic succession of numbers compared to the * standard random() function. It was invented by Ken Perlin in the 1980s and been used since * in graphical applications to produce procedural textures, natural motion, shapes, terrains etc. * The main difference to the random() function is that Perlin noise is defined in an infinite * n-dimensional space where each pair of coordinates corresponds to a fixed semi-random value * (fixed only for the lifespan of the program). The resulting value will always be between 0.0 * and 1.0. Processing can compute 1D, 2D and 3D noise, depending on the number of coordinates * given. The noise value can be animated by moving through the noise space as demonstrated in * the example above. The 2nd and 3rd dimension can also be interpreted as time. * The actual noise is structured similar to an audio signal, in respect to the function's use * of frequencies. Similar to the concept of harmonics in physics, perlin noise is computed over * several octaves which are added together for the final result. * Another way to adjust the character of the resulting sequence is the scale of the input * coordinates. As the function works within an infinite space the value of the coordinates * doesn't matter as such, only the distance between successive coordinates does (eg. when using * noise() within a loop). As a general rule the smaller the difference between coordinates, the * smoother the resulting noise sequence will be. Steps of 0.005-0.03 work best for most applications, * but this will differ depending on use. * * @param {float} x x coordinate in noise space * @param {float} y y coordinate in noise space * @param {float} z z coordinate in noise space * * @returns {float} * * @see random * @see noiseDetail */ p.noise = function(x, y, z) { if(noiseProfile.generator === undef) { // caching noiseProfile.generator = new PerlinNoise(noiseProfile.seed); } var generator = noiseProfile.generator; var effect = 1, k = 1, sum = 0; for(var i=0; i<noiseProfile.octaves; ++i) { effect *= noiseProfile.fallout; switch (arguments.length) { case 1: sum += effect * (1 + generator.noise1d(k*x))/2; break; case 2: sum += effect * (1 + generator.noise2d(k*x, k*y))/2; break; case 3: sum += effect * (1 + generator.noise3d(k*x, k*y, k*z))/2; break; } k *= 2; } return sum; };
/** * Adjusts the character and level of detail produced by the Perlin noise function. * Similar to harmonics in physics, noise is computed over several octaves. Lower octaves * contribute more to the output signal and as such define the overal intensity of the noise, * whereas higher octaves create finer grained details in the noise sequence. By default, * noise is computed over 4 octaves with each octave contributing exactly half than its * predecessor, starting at 50% strength for the 1st octave. This falloff amount can be * changed by adding an additional function parameter. Eg. a falloff factor of 0.75 means * each octave will now have 75% impact (25% less) of the previous lower octave. Any value * between 0.0 and 1.0 is valid, however note that values greater than 0.5 might result in * greater than 1.0 values returned by noise(). By changing these parameters, the signal * created by the noise() function can be adapted to fit very specific needs and characteristics. * * @param {int} octaves number of octaves to be used by the noise() function * @param {float} falloff falloff factor for each octave * * @see noise */ p.noiseDetail = function(octaves, fallout) { noiseProfile.octaves = octaves; if(fallout !== undef) { noiseProfile.fallout = fallout; } };
/** * Sets the seed value for noise(). By default, noise() produces different results each * time the program is run. Set the value parameter to a constant to return the same * pseudo-random numbers each time the software is run. * * @param {int} seed int * * @returns {float} * * @see random * @see radomSeed * @see noise * @see noiseDetail */ p.noiseSeed = function(seed) { noiseProfile.seed = seed; noiseProfile.generator = undef; };
};
},{}],23:[function(require,module,exports){ /**
* Common functions traditionally on "p" that should be class functions * that get bound to "p" when an instance is actually built, instead. */
module.exports = (function commonFunctions(undef) {
var CommonFunctions = { /** * Remove whitespace characters from the beginning and ending * of a String or a String array. Works like String.trim() but includes the * unicode nbsp character as well. If an array is passed in the function will return a new array not effecting the array passed in. * * @param {String} str the string to trim * @param {String[]} str the string array to trim * * @return {String|String[]} retrurns a string or an array will removed whitespaces */ trim: function(str) { if (str instanceof Array) { var arr = []; for (var i = 0; i < str.length; i++) { arr.push(str[i].replace(/^\s*/, ).replace(/\s*$/, ).replace(/\r*$/, )); } return arr; } return str.replace(/^\s*/, ).replace(/\s*$/, ).replace(/\r*$/, ); },
/** * Converts a degree measurement to its corresponding value in radians. Radians and degrees are two ways of * measuring the same thing. There are 360 degrees in a circle and 2*PI radians in a circle. For example, * 90 degrees = PI/2 = 1.5707964. All trigonometric methods in Processing require their parameters to be specified in radians. * * @param {int|float} value an angle in radians * * @returns {float} * * @see degrees */ radians: function(aAngle) { return (aAngle / 180) * Math.PI; },
/** * Number-to-String formatting function. Prepends "plus" or "minus" depending * on whether the value is positive or negative, respectively, after padding * the value with zeroes on the left and right, the number of zeroes used dictated * by the values 'leftDigits' and 'rightDigits'. 'value' cannot be an array. * * @param {int|float} value the number to format * @param {String} plus the prefix for positive numbers * @param {String} minus the prefix for negative numbers * @param {int} left number of digits to the left of the decimal point * @param {int} right number of digits to the right of the decimal point * @param {String} group string delimited for groups, such as the comma in "1,000" * * @returns {String or String[]} * * @see nfCore */ nfCoreScalar: function (value, plus, minus, leftDigits, rightDigits, group) { var sign = (value < 0) ? minus : plus; var autoDetectDecimals = rightDigits === 0; var rightDigitsOfDefault = (rightDigits === undef || rightDigits < 0) ? 0 : rightDigits;
var absValue = Math.abs(value); if (autoDetectDecimals) { rightDigitsOfDefault = 1; absValue *= 10; while (Math.abs(Math.round(absValue) - absValue) > 1e-6 && rightDigitsOfDefault < 7) { ++rightDigitsOfDefault; absValue *= 10; } } else if (rightDigitsOfDefault !== 0) { absValue *= Math.pow(10, rightDigitsOfDefault); }
// Using Java's default rounding policy HALF_EVEN. This policy is based // on the idea that 0.5 values round to the nearest even number, and // everything else is rounded normally. var number, doubled = absValue * 2; if (Math.floor(absValue) === absValue) { number = absValue; } else if (Math.floor(doubled) === doubled) { var floored = Math.floor(absValue); number = floored + (floored % 2); } else { number = Math.round(absValue); }
var buffer = ""; var totalDigits = leftDigits + rightDigitsOfDefault; while (totalDigits > 0 || number > 0) { totalDigits--; buffer = "" + (number % 10) + buffer; number = Math.floor(number / 10); } if (group !== undef) { var i = buffer.length - 3 - rightDigitsOfDefault; while(i > 0) { buffer = buffer.substring(0,i) + group + buffer.substring(i); i-=3; } } if (rightDigitsOfDefault > 0) { return sign + buffer.substring(0, buffer.length - rightDigitsOfDefault) + "." + buffer.substring(buffer.length - rightDigitsOfDefault, buffer.length); } return sign + buffer; },
/** * Number-to-String formatting function. Prepends "plus" or "minus" depending * on whether the value is positive or negative, respectively, after padding * the value with zeroes on the left and right, the number of zeroes used dictated * by the values 'leftDigits' and 'rightDigits'. 'value' can be an array; * if the input is an array, each value in it is formatted separately, and * an array with formatted values is returned. * * @param {int|int[]|float|float[]} value the number(s) to format * @param {String} plus the prefix for positive numbers * @param {String} minus the prefix for negative numbers * @param {int} left number of digits to the left of the decimal point * @param {int} right number of digits to the right of the decimal point * @param {String} group string delimited for groups, such as the comma in "1,000" * * @returns {String or String[]} * * @see nfCoreScalar */ nfCore: function(value, plus, minus, leftDigits, rightDigits, group) { if (value instanceof Array) { var arr = []; for (var i = 0, len = value.length; i < len; i++) { arr.push(CommonFunctions.nfCoreScalar(value[i], plus, minus, leftDigits, rightDigits, group)); } return arr; } return CommonFunctions.nfCoreScalar(value, plus, minus, leftDigits, rightDigits, group); },
/** * Utility function for formatting numbers into strings. There are two versions, one for * formatting floats and one for formatting ints. The values for the digits, left, and * right parameters should always be positive integers. * As shown in the above example, nf() is used to add zeros to the left and/or right * of a number. This is typically for aligning a list of numbers. To remove digits from * a floating-point number, use the int(), ceil(), floor(), or round() functions. * * @param {int|int[]|float|float[]} value the number(s) to format * @param {int} left number of digits to the left of the decimal point * @param {int} right number of digits to the right of the decimal point * * @returns {String or String[]} * * @see nfs * @see nfp * @see nfc */ nf: function(value, leftDigits, rightDigits) { return CommonFunctions.nfCore(value, "", "-", leftDigits, rightDigits); },
/** * Utility function for formatting numbers into strings. Similar to nf() but leaves a blank space in front * of positive numbers so they align with negative numbers in spite of the minus symbol. There are two * versions, one for formatting floats and one for formatting ints. The values for the digits, left, * and right parameters should always be positive integers. * * @param {int|int[]|float|float[]} value the number(s) to format * @param {int} left number of digits to the left of the decimal point * @param {int} right number of digits to the right of the decimal point * * @returns {String or String[]} * * @see nf * @see nfp * @see nfc */ nfs: function(value, leftDigits, rightDigits) { return CommonFunctions.nfCore(value, " ", "-", leftDigits, rightDigits); },
/** * Utility function for formatting numbers into strings. Similar to nf() but puts a "+" in front of * positive numbers and a "-" in front of negative numbers. There are two versions, one for formatting * floats and one for formatting ints. The values for the digits, left, and right parameters should * always be positive integers. * * @param {int|int[]|float|float[]} value the number(s) to format * @param {int} left number of digits to the left of the decimal point * @param {int} right number of digits to the right of the decimal point * * @returns {String or String[]} * * @see nfs * @see nf * @see nfc */ nfp: function(value, leftDigits, rightDigits) { return CommonFunctions.nfCore(value, "+", "-", leftDigits, rightDigits); },
/** * Utility function for formatting numbers into strings and placing appropriate commas to mark * units of 1000. There are two versions, one for formatting ints and one for formatting an array * of ints. The value for the digits parameter should always be a positive integer. * * @param {int|int[]|float|float[]} value the number(s) to format * @param {int} left number of digits to the left of the decimal point * @param {int} right number of digits to the right of the decimal point * * @returns {String or String[]} * * @see nf * @see nfs * @see nfp */ nfc: function(value, rightDigits) { return CommonFunctions.nfCore(value, "", "-", 0, rightDigits, ","); },
// used to bind all common functions to "p" withCommonFunctions: function withCommonFunctions(p) { ["trim", "radians", "nf", "nfs", "nfp", "nfc"].forEach(function(f){ p[f] = CommonFunctions[f]; }); } };
return CommonFunctions;
}());
},{}],24:[function(require,module,exports){ /**
* Touch and Mouse event handling */
module.exports = function withTouch(p, curElement, attachEventHandler, document, PConstants, undef) {
/** * Determine the location of the (mouse) pointer. */ function calculateOffset(curElement, event) { var element = curElement, offsetX = 0, offsetY = 0;
p.pmouseX = p.mouseX; p.pmouseY = p.mouseY;
// Find element offset if (element.offsetParent) { do { offsetX += element.offsetLeft; offsetY += element.offsetTop; } while (!!(element = element.offsetParent)); }
// Find Scroll offset element = curElement; do { offsetX -= element.scrollLeft || 0; offsetY -= element.scrollTop || 0; } while (!!(element = element.parentNode));
// 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; }
// Add padding and border style widths to offset offsetX += stylePaddingLeft; offsetY += stylePaddingTop;
offsetX += styleBorderLeft; offsetY += styleBorderTop;
// Take into account any scrolling done offsetX += window.pageXOffset; offsetY += window.pageYOffset;
return {'X':offsetX,'Y':offsetY}; }
// simple relative position function updateMousePosition(curElement, event) { var offset = calculateOffset(curElement, event); // Dropping support for IE clientX and clientY, switching to pageX and pageY // so we don't have to calculate scroll offset. // Removed in ticket #184. See rev: 2f106d1c7017fed92d045ba918db47d28e5c16f4 p.mouseX = event.pageX - offset.X; p.mouseY = event.pageY - offset.Y; }
/** * Return a TouchEvent with canvas-specific x/y co-ordinates */ function addTouchEventOffset(t) { var offset = calculateOffset(t.changedTouches[0].target, t.changedTouches[0]), i;
for (i = 0; i < t.touches.length; i++) { var touch = t.touches[i]; touch.offsetX = touch.pageX - offset.X; touch.offsetY = touch.pageY - offset.Y; } for (i = 0; i < t.targetTouches.length; i++) { var targetTouch = t.targetTouches[i]; targetTouch.offsetX = targetTouch.pageX - offset.X; targetTouch.offsetY = targetTouch.pageY - offset.Y; } for (i = 0; i < t.changedTouches.length; i++) { var changedTouch = t.changedTouches[i]; changedTouch.offsetX = changedTouch.pageX - offset.X; changedTouch.offsetY = changedTouch.pageY - offset.Y; }
return t; }
/** * Touch event support. */ attachEventHandler(curElement, "touchstart", function (t) { // Removes unwanted behaviour of the canvas when touching canvas curElement.setAttribute("style","-webkit-user-select: none"); curElement.setAttribute("onclick","void(0)"); curElement.setAttribute("style","-webkit-tap-highlight-color:rgba(0,0,0,0)"); // Loop though eventHandlers and remove mouse listeners for (var i=0, ehl=eventHandlers.length; i<ehl; i++) { var type = eventHandlers[i].type; // Have this function remove itself from the eventHandlers list too if (type === "mouseout" || type === "mousemove" || type === "mousedown" || type === "mouseup" || type === "DOMMouseScroll" || type === "mousewheel" || type === "touchstart") { detachEventHandler(eventHandlers[i]); } }
// If there are any native touch events defined in the sketch, connect all of them // Otherwise, connect all of the emulated mouse events if (p.touchStart !== undef || p.touchMove !== undef || p.touchEnd !== undef || p.touchCancel !== undef) { attachEventHandler(curElement, "touchstart", function(t) { if (p.touchStart !== undef) { t = addTouchEventOffset(t); p.touchStart(t); } });
attachEventHandler(curElement, "touchmove", function(t) { if (p.touchMove !== undef) { t.preventDefault(); // Stop the viewport from scrolling t = addTouchEventOffset(t); p.touchMove(t); } });
attachEventHandler(curElement, "touchend", function(t) { if (p.touchEnd !== undef) { t = addTouchEventOffset(t); p.touchEnd(t); } });
attachEventHandler(curElement, "touchcancel", function(t) { if (p.touchCancel !== undef) { t = addTouchEventOffset(t); p.touchCancel(t); } });
} else { // Emulated touch start/mouse down event attachEventHandler(curElement, "touchstart", function(e) { updateMousePosition(curElement, e.touches[0]);
p.__mousePressed = true; p.mouseDragging = false; p.mouseButton = PConstants.LEFT;
if (typeof p.mousePressed === "function") { p.mousePressed(); } });
// Emulated touch move/mouse move event attachEventHandler(curElement, "touchmove", function(e) { e.preventDefault(); updateMousePosition(curElement, e.touches[0]);
if (typeof p.mouseMoved === "function" && !p.__mousePressed) { p.mouseMoved(); } if (typeof p.mouseDragged === "function" && p.__mousePressed) { p.mouseDragged(); p.mouseDragging = true; } });
// Emulated touch up/mouse up event attachEventHandler(curElement, "touchend", function(e) { p.__mousePressed = false;
if (typeof p.mouseClicked === "function" && !p.mouseDragging) { p.mouseClicked(); }
if (typeof p.mouseReleased === "function") { p.mouseReleased(); } }); }
// Refire the touch start event we consumed in this function curElement.dispatchEvent(t); });
/** * Context menu toggles. Most often you will not want the * browser's context menu to show on a right click, but * sometimes, you do, so we add two unofficial functions * that can be used to trigger context menu behaviour. */ (function() { var enabled = true, contextMenu = function(e) { e.preventDefault(); e.stopPropagation(); };
p.disableContextMenu = function() { if (!enabled) { return; } attachEventHandler(curElement, 'contextmenu', contextMenu); enabled = false; };
p.enableContextMenu = function() { if (enabled) { return; } detachEventHandler({elem: curElement, type: 'contextmenu', fn: contextMenu}); enabled = true; }; }());
/** * Mouse moved or dragged */ attachEventHandler(curElement, "mousemove", function(e) { updateMousePosition(curElement, e); if (typeof p.mouseMoved === "function" && !p.__mousePressed) { p.mouseMoved(); } if (typeof p.mouseDragged === "function" && p.__mousePressed) { p.mouseDragged(); p.mouseDragging = true; } });
/** * Unofficial mouse-out handling */ attachEventHandler(curElement, "mouseout", function(e) { if (typeof p.mouseOut === "function") { p.mouseOut(); } });
/** * Mouse over */ attachEventHandler(curElement, "mouseover", function(e) { updateMousePosition(curElement, e); if (typeof p.mouseOver === "function") { p.mouseOver(); } });
/** * Disable browser's default handling for click-drag of a canvas. */ curElement.onmousedown = function () { // make sure focus happens, but nothing else curElement.focus(); return false; };
/** * Mouse pressed or drag */ attachEventHandler(curElement, "mousedown", function(e) { p.__mousePressed = true; p.mouseDragging = false; switch (e.which) { case 1: p.mouseButton = PConstants.LEFT; break; case 2: p.mouseButton = PConstants.CENTER; break; case 3: p.mouseButton = PConstants.RIGHT; break; }
if (typeof p.mousePressed === "function") { p.mousePressed(); } });
/** * Mouse clicked or released */ attachEventHandler(curElement, "mouseup", function(e) { p.__mousePressed = false;
if (typeof p.mouseClicked === "function" && !p.mouseDragging) { p.mouseClicked(); }
if (typeof p.mouseReleased === "function") { p.mouseReleased(); } });
/** * Unofficial scroll wheel handling. */ var mouseWheelHandler = function(e) { var delta = 0;
if (e.wheelDelta) { delta = e.wheelDelta / 120; if (window.opera) { delta = -delta; } } else if (e.detail) { delta = -e.detail / 3; }
p.mouseScroll = delta;
if (delta && typeof p.mouseScrolled === 'function') { p.mouseScrolled(); } };
// Support Gecko and non-Gecko scroll events attachEventHandler(document, 'DOMMouseScroll', mouseWheelHandler); attachEventHandler(document, 'mousewheel', mouseWheelHandler);
};
},{}],25:[function(require,module,exports){ /**
* The parser for turning Processing syntax into Pjs JavaScript. * This code is not trivial; unless you know what you're doing, * you shouldn't be changing things in here =) */
module.exports = function setupParser(Processing, options) {
var defaultScope = options.defaultScope, PConstants = defaultScope.PConstants, aFunctions = options.aFunctions, Browser = options.Browser, document = Browser.document, undef;
// Processing global methods and constants for the parser function getGlobalMembers() { // The names array contains the names of everything that is inside "p." // When something new is added to "p." it must also be added to this list. var names = [ /* this code is generated by jsglobals.js */ "abs", "acos", "alpha", "ambient", "ambientLight", "append", "applyMatrix", "arc", "arrayCopy", "asin", "atan", "atan2", "background", "beginCamera", "beginDraw", "beginShape", "bezier", "bezierDetail", "bezierPoint", "bezierTangent", "bezierVertex", "binary", "blend", "blendColor", "blit_resize", "blue", "box", "breakShape", "brightness", "camera", "ceil", "Character", "color", "colorMode", "concat", "constrain", "copy", "cos", "createFont", "createGraphics", "createImage", "cursor", "curve", "curveDetail", "curvePoint", "curveTangent", "curveTightness", "curveVertex", "day", "degrees", "directionalLight", "disableContextMenu", "dist", "draw", "ellipse", "ellipseMode", "emissive", "enableContextMenu", "endCamera", "endDraw", "endShape", "exit", "exp", "expand", "externals", "fill", "filter", "floor", "focused", "frameCount", "frameRate", "frustum", "get", "glyphLook", "glyphTable", "green", "height", "hex", "hint", "hour", "hue", "image", "imageMode", "intersect", "join", "key", "keyCode", "keyPressed", "keyReleased", "keyTyped", "lerp", "lerpColor", "lightFalloff", "lights", "lightSpecular", "line", "link", "loadBytes", "loadFont", "loadGlyphs", "loadImage", "loadPixels", "loadShape", "loadXML", "loadStrings", "log", "loop", "mag", "map", "match", "matchAll", "max", "millis", "min", "minute", "mix", "modelX", "modelY", "modelZ", "modes", "month", "mouseButton", "mouseClicked", "mouseDragged", "mouseMoved", "mouseOut", "mouseOver", "mousePressed", "mouseReleased", "mouseScroll", "mouseScrolled", "mouseX", "mouseY", "name", "nf", "nfc", "nfp", "nfs", "noCursor", "noFill", "noise", "noiseDetail", "noiseSeed", "noLights", "noLoop", "norm", "normal", "noSmooth", "noStroke", "noTint", "ortho", "param", "parseBoolean", "parseByte", "parseChar", "parseFloat", "parseInt", "peg", "perspective", "PImage", "pixels", "PMatrix2D", "PMatrix3D", "PMatrixStack", "pmouseX", "pmouseY", "point", "pointLight", "popMatrix", "popStyle", "pow", "print", "printCamera", "println", "printMatrix", "printProjection", "PShape", "PShapeSVG", "pushMatrix", "pushStyle", "quad", "radians", "random", "randomGaussian", "randomSeed", "rect", "rectMode", "red", "redraw", "requestImage", "resetMatrix", "reverse", "rotate", "rotateX", "rotateY", "rotateZ", "round", "saturation", "save", "saveFrame", "saveStrings", "scale", "screenX", "screenY", "screenZ", "second", "set", "setup", "shape", "shapeMode", "shared", "shearX", "shearY", "shininess", "shorten", "sin", "size", "smooth", "sort", "specular", "sphere", "sphereDetail", "splice", "split", "splitTokens", "spotLight", "sq", "sqrt", "status", "str", "stroke", "strokeCap", "strokeJoin", "strokeWeight", "subset", "tan", "text", "textAlign", "textAscent", "textDescent", "textFont", "textLeading", "textMode", "textSize", "texture", "textureMode", "textWidth", "tint", "toImageData", "touchCancel", "touchEnd", "touchMove", "touchStart", "translate", "transform", "triangle", "trim", "unbinary", "unhex", "updatePixels", "use3DContext", "vertex", "width", "XMLElement", "XML", "year", "__contains", "__equals", "__equalsIgnoreCase", "__frameRate", "__hashCode", "__int_cast", "__instanceof", "__keyPressed", "__mousePressed", "__printStackTrace", "__replace", "__replaceAll", "__replaceFirst", "__toCharArray", "__split", "__codePointAt", "__startsWith", "__endsWith", "__matches"];
// custom functions and properties are added here if(aFunctions) { Object.keys(aFunctions).forEach(function(name) { names.push(name); }); }
// custom libraries that were attached to Processing var members = {}; var i, l; for (i = 0, l = names.length; i < l ; ++i) { members[names[i]] = null; } for (var lib in Processing.lib) { if (Processing.lib.hasOwnProperty(lib)) { if (Processing.lib[lib].exports) { var exportedNames = Processing.lib[lib].exports; for (i = 0, l = exportedNames.length; i < l; ++i) { members[exportedNames[i]] = null; } } } } return members; }
/*
Parser converts Java-like syntax into JavaScript. Creates an Abstract Syntax Tree -- "Light AST" from the Java-like code.
It is an object tree. The root object is created from the AstRoot class, which contains statements.
A statement object can be of type: AstForStatement, AstCatchStatement, AstPrefixStatement, AstMethod, AstClass, AstInterface, AstFunction, AstStatementBlock and AstLabel.
AstPrefixStatement can be a statement of type: if, switch, while, with, do, else, finally, return, throw, try, break, and continue.
These object's toString function returns the JavaScript code for the statement.
Any processing calls need "processing." prepended to them.
Similarly, calls from inside classes need "$this_1.", prepended to them, with 1 being the depth level for inner classes. This includes members passed down from inheritance.
The resulting code is then eval'd and run.
*/
function parseProcessing(code) { var globalMembers = getGlobalMembers();
// masks parentheses, brackets and braces with '"A5"' // where A is the bracket type, and 5 is the index in an array containing all brackets split into atoms // 'while(true){}' -> 'while"B1""A2"' // parentheses() = B, brackets[] = C and braces{} = A function splitToAtoms(code) { var atoms = []; var items = code.split(/([\{\[\(\)\]\}])/); var result = items[0];
var stack = []; for(var i=1; i < items.length; i += 2) { var item = items[i]; if(item === '[' || item === '{' || item === '(') { stack.push(result); result = item; } else if(item === ']' || item === '}' || item === ')') { var kind = item === '}' ? 'A' : item === ')' ? 'B' : 'C'; var index = atoms.length; atoms.push(result + item); result = stack.pop() + '"' + kind + (index + 1) + '"'; } result += items[i + 1]; } atoms.unshift(result); return atoms; }
// replaces strings and regexs keyed by index with an array of strings function injectStrings(code, strings) { return code.replace(/'(\d+)'/g, function(all, index) { var val = strings[index]; if(val.charAt(0) === "/") { return val; } return (/^'((?:[^'\\\n])|(?:\\.[0-9A-Fa-f]*))'$/).test(val) ? "(new $p.Character(" + val + "))" : val; }); }
// trims off leading and trailing spaces // returns an object. object.left, object.middle, object.right, object.untrim function trimSpaces(string) { var m1 = /^\s*/.exec(string), result; if(m1[0].length === string.length) { result = {left: m1[0], middle: "", right: ""}; } else { var m2 = /\s*$/.exec(string); result = {left: m1[0], middle: string.substring(m1[0].length, m2.index), right: m2[0]}; } result.untrim = function(t) { return this.left + t + this.right; }; return result; }
// simple trim of leading and trailing spaces function trim(string) { return string.replace(/^\s+/,).replace(/\s+$/,); }
function appendToLookupTable(table, array) { for(var i=0,l=array.length;i<l;++i) { table[array[i]] = null; } return table; }
function isLookupTableEmpty(table) { for(var i in table) { if(table.hasOwnProperty(i)) { return false; } } return true; }
function getAtomIndex(templ) { return templ.substring(2, templ.length - 1); }
// remove carriage returns "\r" var codeWoExtraCr = code.replace(/\r\n?|\n\r/g, "\n");
// masks strings and regexs with "'5'", where 5 is the index in an array containing all strings and regexs // also removes all comments var strings = []; var codeWoStrings = codeWoExtraCr.replace(/("(?:[^"\\\n]|\\.)*")|('(?:[^'\\\n]|\\.)*')|(([\[\(=|&!\^:?]\s*)(\/(?![*\/])(?:[^\/\\\n]|\\.)*\/[gim]*)\b)|(\/\/[^\n]*\n)|(\/\*(?:(?!\*\/)(?:.|\n))*\*\/)/g, function(all, quoted, aposed, regexCtx, prefix, regex, singleComment, comment) { var index; if(quoted || aposed) { // replace strings index = strings.length; strings.push(all); return "'" + index + "'"; } if(regexCtx) { // replace RegExps index = strings.length; strings.push(regex); return prefix + "'" + index + "'"; } // kill comments return comment !== "" ? " " : "\n"; });
// protect character codes from namespace collision codeWoStrings = codeWoStrings.replace(/__x([0-9A-F]{4})/g, function(all, hexCode) { // $ = __x0024 // _ = __x005F // this protects existing character codes from conversion // __x0024 = __x005F_x0024 return "__x005F_x" + hexCode; });
// convert dollar sign to character code codeWoStrings = codeWoStrings.replace(/\$/g, "__x0024");
// Remove newlines after return statements codeWoStrings = codeWoStrings.replace(/return\s*[\n\r]+/g, "return ");
// removes generics var genericsWereRemoved; var codeWoGenerics = codeWoStrings; var replaceFunc = function(all, before, types, after) { if(!!before || !!after) { return all; } genericsWereRemoved = true; return ""; };
do { genericsWereRemoved = false; codeWoGenerics = codeWoGenerics.replace(/([<]?)<\s*((?:\?|[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)(?:\[\])*(?:\s+(?:extends|super)\s+[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)?(?:\s*,\s*(?:\?|[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)(?:\[\])*(?:\s+(?:extends|super)\s+[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)?)*)\s*>([=]?)/g, replaceFunc); } while (genericsWereRemoved);
var atoms = splitToAtoms(codeWoGenerics); var replaceContext; var declaredClasses = {}, currentClassId, classIdSeed = 0;
function addAtom(text, type) { var lastIndex = atoms.length; atoms.push(text); return '"' + type + lastIndex + '"'; }
function generateClassId() { return "class" + (++classIdSeed); }
function appendClass(class_, classId, scopeId) { class_.classId = classId; class_.scopeId = scopeId; declaredClasses[classId] = class_; }
// functions defined below var transformClassBody, transformInterfaceBody, transformStatementsBlock, transformStatements, transformMain, transformExpression;
var classesRegex = /\b((?:(?:public|private|final|protected|static|abstract)\s+)*)(class|interface)\s+([A-Za-z_$][\w$]*\b)(\s+extends\s+[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*(?:\s*,\s*[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*\b)*)?(\s+implements\s+[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*(?:\s*,\s*[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*\b)*)?\s*("A\d+")/g; var methodsRegex = /\b((?:(?:public|private|final|protected|static|abstract|synchronized)\s+)*)((?!(?:else|new|return|throw|function|public|private|protected)\b)[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*(?:\s*"C\d+")*)\s*([A-Za-z_$][\w$]*\b)\s*("B\d+")(\s*throws\s+[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*(?:\s*,\s*[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)*)?\s*("A\d+"|;)/g; var fieldTest = /^((?:(?:public|private|final|protected|static)\s+)*)((?!(?:else|new|return|throw)\b)[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*(?:\s*"C\d+")*)\s*([A-Za-z_$][\w$]*\b)\s*(?:"C\d+"\s*)*([=,]|$)/; var cstrsRegex = /\b((?:(?:public|private|final|protected|static|abstract)\s+)*)((?!(?:new|return|throw)\b)[A-Za-z_$][\w$]*\b)\s*("B\d+")(\s*throws\s+[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*(?:\s*,\s*[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)*)?\s*("A\d+")/g; var attrAndTypeRegex = /^((?:(?:public|private|final|protected|static)\s+)*)((?!(?:new|return|throw)\b)[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*(?:\s*"C\d+")*)\s*/; var functionsRegex = /\bfunction(?:\s+([A-Za-z_$][\w$]*))?\s*("B\d+")\s*("A\d+")/g;
// This converts classes, methods and functions into atoms, and adds them to the atoms array. // classes = E, methods = D and functions = H function extractClassesAndMethods(code) { var s = code; s = s.replace(classesRegex, function(all) { return addAtom(all, 'E'); }); s = s.replace(methodsRegex, function(all) { return addAtom(all, 'D'); }); s = s.replace(functionsRegex, function(all) { return addAtom(all, 'H'); }); return s; }
// This converts constructors into atoms, and adds them to the atoms array. // constructors = G function extractConstructors(code, className) { var result = code.replace(cstrsRegex, function(all, attr, name, params, throws_, body) { if(name !== className) { return all; } return addAtom(all, 'G'); }); return result; }
// AstParam contains the name of a parameter inside a function declaration function AstParam(name) { this.name = name; } AstParam.prototype.toString = function() { return this.name; }; // AstParams contains an array of AstParam objects function AstParams(params, methodArgsParam) { this.params = params; this.methodArgsParam = methodArgsParam; } AstParams.prototype.getNames = function() { var names = []; for(var i=0,l=this.params.length;i<l;++i) { names.push(this.params[i].name); } return names; }; AstParams.prototype.prependMethodArgs = function(body) { if (!this.methodArgsParam) { return body; } return "{\nvar " + this.methodArgsParam.name + " = Array.prototype.slice.call(arguments, " + this.params.length + ");\n" + body.substring(1); }; AstParams.prototype.toString = function() { if(this.params.length === 0) { return "()"; } var result = "("; for(var i=0,l=this.params.length;i<l;++i) { result += this.params[i] + ", "; } return result.substring(0, result.length - 2) + ")"; };
function transformParams(params) { var paramsWoPars = trim(params.substring(1, params.length - 1)); var result = [], methodArgsParam = null; if(paramsWoPars !== "") { var paramList = paramsWoPars.split(","); for(var i=0; i < paramList.length; ++i) { var param = /\b([A-Za-z_$][\w$]*\b)(\s*"[ABC][\d]*")*\s*$/.exec(paramList[i]); if (i === paramList.length - 1 && paramList[i].indexOf('...') >= 0) { methodArgsParam = new AstParam(param[1]); break; } result.push(new AstParam(param[1])); } } return new AstParams(result, methodArgsParam); }
function preExpressionTransform(expr) { var s = expr; // new type[] {...} --> {...} s = s.replace(/\bnew\s+([A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)(?:\s*"C\d+")+\s*("A\d+")/g, function(all, type, init) { return init; }); // new Runnable() {...} --> "F???" s = s.replace(/\bnew\s+([A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)(?:\s*"B\d+")\s*("A\d+")/g, function(all, type, init) { return addAtom(all, 'F'); }); // function(...) { } --> "H???" s = s.replace(functionsRegex, function(all) { return addAtom(all, 'H'); }); // new type[?] --> createJavaArray('type', [?]) s = s.replace(/\bnew\s+([A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)\s*("C\d+"(?:\s*"C\d+")*)/g, function(all, type, index) { var args = index.replace(/"C(\d+)"/g, function(all, j) { return atoms[j]; }) .replace(/\[\s*\]/g, "[null]").replace(/\s*\]\s*\[\s*/g, ", "); var arrayInitializer = "{" + args.substring(1, args.length - 1) + "}"; var createArrayArgs = "('" + type + "', " + addAtom(arrayInitializer, 'A') + ")"; return '$p.createJavaArray' + addAtom(createArrayArgs, 'B'); }); // .length() --> .length s = s.replace(/(\.\s*length)\s*"B\d+"/g, "$1"); // #000000 --> 0x000000 s = s.replace(/#([0-9A-Fa-f]{6})\b/g, function(all, digits) { return "0xFF" + digits; }); // delete (type)???, except (int)??? s = s.replace(/"B(\d+)"(\s*(?:[\w$']|"B))/g, function(all, index, next) { var atom = atoms[index]; if(!/^\(\s*[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*\s*(?:"C\d+"\s*)*\)$/.test(atom)) { return all; } if(/^\(\s*int\s*\)$/.test(atom)) { return "(int)" + next; } var indexParts = atom.split(/"C(\d+)"/g); if(indexParts.length > 1) { // even items contains atom numbers, can check only first if(! /^\[\s*\]$/.test(atoms[indexParts[1]])) { return all; // fallback - not a cast } } return "" + next; }); // (int)??? -> __int_cast(???) s = s.replace(/\(int\)([^,\]\)\}\?\:\*\+\-\/\^\|\%\&\~<\>\=]+)/g, function(all, arg) { var trimmed = trimSpaces(arg); return trimmed.untrim("__int_cast(" + trimmed.middle + ")"); }); // super() -> $superCstr(), super. -> $super.; s = s.replace(/\bsuper(\s*"B\d+")/g, "$$superCstr$1").replace(/\bsuper(\s*\.)/g, "$$super$1"); // 000.43->0.43 and 0010f->10, but not 0010 s = s.replace(/\b0+((\d*)(?:\.[\d*])?(?:[eE][\-\+]?\d+)?[fF]?)\b/, function(all, numberWo0, intPart) { if( numberWo0 === intPart) { return all; } return intPart === "" ? "0" + numberWo0 : numberWo0; }); // 3.0f -> 3.0 s = s.replace(/\b(\.?\d+\.?)[fF]\b/g, "$1"); // Weird (?) parsing errors with % s = s.replace(/([^\s])%([^=\s])/g, "$1 % $2"); // Since frameRate() and frameRate are different things, // we need to differentiate them somehow. So when we parse // the Processing.js source, replace frameRate so it isn't // confused with frameRate(), as well as keyPressed and mousePressed s = s.replace(/\b(frameRate|keyPressed|mousePressed)\b(?!\s*"B)/g, "__$1"); // "boolean", "byte", "int", etc. => "parseBoolean", "parseByte", "parseInt", etc. s = s.replace(/\b(boolean|byte|char|float|int)\s*"B/g, function(all, name) { return "parse" + name.substring(0, 1).toUpperCase() + name.substring(1) + "\"B"; }); // "pixels" replacements: // pixels[i] = c => pixels.setPixel(i,c) | pixels[i] => pixels.getPixel(i) // pixels.length => pixels.getLength() // pixels = ar => pixels.set(ar) | pixels => pixels.toArray() s = s.replace(/\bpixels\b\s*(("C(\d+)")|\.length)?(\s*=(?!=)([^,\]\)\}]+))?/g, function(all, indexOrLength, index, atomIndex, equalsPart, rightSide) { if(index) { var atom = atoms[atomIndex]; if(equalsPart) { return "pixels.setPixel" + addAtom("(" +atom.substring(1, atom.length - 1) + "," + rightSide + ")", 'B'); } return "pixels.getPixel" + addAtom("(" + atom.substring(1, atom.length - 1) + ")", 'B'); } if(indexOrLength) { // length return "pixels.getLength" + addAtom("()", 'B'); } if(equalsPart) { return "pixels.set" + addAtom("(" + rightSide + ")", 'B'); } return "pixels.toArray" + addAtom("()", 'B'); }); // Java method replacements for: replace, replaceAll, replaceFirst, equals, hashCode, etc. // xxx.replace(yyy) -> __replace(xxx, yyy) // "xx".replace(yyy) -> __replace("xx", yyy) var repeatJavaReplacement; function replacePrototypeMethods(all, subject, method, atomIndex) { var atom = atoms[atomIndex]; repeatJavaReplacement = true; var trimmed = trimSpaces(atom.substring(1, atom.length - 1)); return "__" + method + ( trimmed.middle === "" ? addAtom("(" + subject.replace(/\.\s*$/, "") + ")", 'B') : addAtom("(" + subject.replace(/\.\s*$/, "") + "," + trimmed.middle + ")", 'B') ); } do { repeatJavaReplacement = false; s = s.replace(/((?:'\d+'|\b[A-Za-z_$][\w$]*\s*(?:"[BC]\d+")*)\s*\.\s*(?:[A-Za-z_$][\w$]*\s*(?:"[BC]\d+"\s*)*\.\s*)*)(replace|replaceAll|replaceFirst|contains|equals|equalsIgnoreCase|hashCode|toCharArray|printStackTrace|split|startsWith|endsWith|codePointAt|matches)\s*"B(\d+)"/g, replacePrototypeMethods); } while (repeatJavaReplacement); // xxx instanceof yyy -> __instanceof(xxx, yyy) function replaceInstanceof(all, subject, type) { repeatJavaReplacement = true; return "__instanceof" + addAtom("(" + subject + ", " + type + ")", 'B'); } do { repeatJavaReplacement = false; s = s.replace(/((?:'\d+'|\b[A-Za-z_$][\w$]*\s*(?:"[BC]\d+")*)\s*(?:\.\s*[A-Za-z_$][\w$]*\s*(?:"[BC]\d+"\s*)*)*)instanceof\s+([A-Za-z_$][\w$]*\s*(?:\.\s*[A-Za-z_$][\w$]*)*)/g, replaceInstanceof); } while (repeatJavaReplacement); // this() -> $constr() s = s.replace(/\bthis(\s*"B\d+")/g, "$$constr$1");
return s; }
function AstInlineClass(baseInterfaceName, body) { this.baseInterfaceName = baseInterfaceName; this.body = body; body.owner = this; } AstInlineClass.prototype.toString = function() { return "new (" + this.body + ")"; };
function transformInlineClass(class_) { var m = new RegExp(/\bnew\s*([A-Za-z_$][\w$]*\s*(?:\.\s*[A-Za-z_$][\w$]*)*)\s*"B\d+"\s*"A(\d+)"/).exec(class_); var oldClassId = currentClassId, newClassId = generateClassId(); currentClassId = newClassId; var uniqueClassName = m[1] + "$" + newClassId; var inlineClass = new AstInlineClass(uniqueClassName, transformClassBody(atoms[m[2]], uniqueClassName, "", "implements " + m[1])); appendClass(inlineClass, newClassId, oldClassId); currentClassId = oldClassId; return inlineClass; }
function AstFunction(name, params, body) { this.name = name; this.params = params; this.body = body; } AstFunction.prototype.toString = function() { var oldContext = replaceContext; // saving "this." and parameters var names = appendToLookupTable({"this":null}, this.params.getNames()); replaceContext = function (subject) { return names.hasOwnProperty(subject.name) ? subject.name : oldContext(subject); }; var result = "function"; if(this.name) { result += " " + this.name; } var body = this.params.prependMethodArgs(this.body.toString()); result += this.params + " " + body; replaceContext = oldContext; return result; };
function transformFunction(class_) { var m = new RegExp(/\b([A-Za-z_$][\w$]*)\s*"B(\d+)"\s*"A(\d+)"/).exec(class_); return new AstFunction( m[1] !== "function" ? m[1] : null, transformParams(atoms[m[2]]), transformStatementsBlock(atoms[m[3]])); }
function AstInlineObject(members) { this.members = members; } AstInlineObject.prototype.toString = function() { var oldContext = replaceContext; replaceContext = function (subject) { return subject.name === "this" ? "this" : oldContext(subject); // saving "this." }; var result = ""; for(var i=0,l=this.members.length;i<l;++i) { if(this.members[i].label) { result += this.members[i].label + ": "; } result += this.members[i].value.toString() + ", "; } replaceContext = oldContext; return result.substring(0, result.length - 2); };
function transformInlineObject(obj) { var members = obj.split(','); for(var i=0; i < members.length; ++i) { var label = members[i].indexOf(':'); if(label < 0) { members[i] = { value: transformExpression(members[i]) }; } else { members[i] = { label: trim(members[i].substring(0, label)), value: transformExpression( trim(members[i].substring(label + 1)) ) }; } } return new AstInlineObject(members); }
function expandExpression(expr) { if(expr.charAt(0) === '(' || expr.charAt(0) === '[') { return expr.charAt(0) + expandExpression(expr.substring(1, expr.length - 1)) + expr.charAt(expr.length - 1); } if(expr.charAt(0) === '{') { if(/^\{\s*(?:[A-Za-z_$][\w$]*|'\d+')\s*:/.test(expr)) { return "{" + addAtom(expr.substring(1, expr.length - 1), 'I') + "}"; } return "[" + expandExpression(expr.substring(1, expr.length - 1)) + "]"; } var trimmed = trimSpaces(expr); var result = preExpressionTransform(trimmed.middle); result = result.replace(/"[ABC](\d+)"/g, function(all, index) { return expandExpression(atoms[index]); }); return trimmed.untrim(result); }
function replaceContextInVars(expr) { return expr.replace(/(\.\s*)?((?:\b[A-Za-z_]|\$)[\w$]*)(\s*\.\s*([A-Za-z_$][\w$]*)(\s*\()?)?/g, function(all, memberAccessSign, identifier, suffix, subMember, callSign) { if(memberAccessSign) { return all; } var subject = { name: identifier, member: subMember, callSign: !!callSign }; return replaceContext(subject) + (suffix === undef ? "" : suffix); }); }
function AstExpression(expr, transforms) { this.expr = expr; this.transforms = transforms; } AstExpression.prototype.toString = function() { var transforms = this.transforms; var expr = replaceContextInVars(this.expr); return expr.replace(/"!(\d+)"/g, function(all, index) { return transforms[index].toString(); }); };
transformExpression = function(expr) { var transforms = []; var s = expandExpression(expr); s = s.replace(/"H(\d+)"/g, function(all, index) { transforms.push(transformFunction(atoms[index])); return '"!' + (transforms.length - 1) + '"'; }); s = s.replace(/"F(\d+)"/g, function(all, index) { transforms.push(transformInlineClass(atoms[index])); return '"!' + (transforms.length - 1) + '"'; }); s = s.replace(/"I(\d+)"/g, function(all, index) { transforms.push(transformInlineObject(atoms[index])); return '"!' + (transforms.length - 1) + '"'; });
return new AstExpression(s, transforms); };
function AstVarDefinition(name, value, isDefault) { this.name = name; this.value = value; this.isDefault = isDefault; } AstVarDefinition.prototype.toString = function() { return this.name + ' = ' + this.value; };
function transformVarDefinition(def, defaultTypeValue) { var eqIndex = def.indexOf("="); var name, value, isDefault; if(eqIndex < 0) { name = def; value = defaultTypeValue; isDefault = true; } else { name = def.substring(0, eqIndex); value = transformExpression(def.substring(eqIndex + 1)); isDefault = false; } return new AstVarDefinition( trim(name.replace(/(\s*"C\d+")+/g, "")), value, isDefault); }
function getDefaultValueForType(type) { if(type === "int" || type === "float") { return "0"; } if(type === "boolean") { return "false"; } if(type === "color") { return "0x00000000"; } return "null"; }
function AstVar(definitions, varType) { this.definitions = definitions; this.varType = varType; } AstVar.prototype.getNames = function() { var names = []; for(var i=0,l=this.definitions.length;i<l;++i) { names.push(this.definitions[i].name); } return names; }; AstVar.prototype.toString = function() { return "var " + this.definitions.join(","); }; function AstStatement(expression) { this.expression = expression; } AstStatement.prototype.toString = function() { return this.expression.toString(); };
function transformStatement(statement) { if(fieldTest.test(statement)) { var attrAndType = attrAndTypeRegex.exec(statement); var definitions = statement.substring(attrAndType[0].length).split(","); var defaultTypeValue = getDefaultValueForType(attrAndType[2]); for(var i=0; i < definitions.length; ++i) { definitions[i] = transformVarDefinition(definitions[i], defaultTypeValue); } return new AstVar(definitions, attrAndType[2]); } return new AstStatement(transformExpression(statement)); }
function AstForExpression(initStatement, condition, step) { this.initStatement = initStatement; this.condition = condition; this.step = step; } AstForExpression.prototype.toString = function() { return "(" + this.initStatement + "; " + this.condition + "; " + this.step + ")"; };
function AstForInExpression(initStatement, container) { this.initStatement = initStatement; this.container = container; } AstForInExpression.prototype.toString = function() { var init = this.initStatement.toString(); if(init.indexOf("=") >= 0) { // can be without var declaration init = init.substring(0, init.indexOf("=")); } return "(" + init + " in " + this.container + ")"; };
function AstForEachExpression(initStatement, container) { this.initStatement = initStatement; this.container = container; } AstForEachExpression.iteratorId = 0; AstForEachExpression.prototype.toString = function() { var init = this.initStatement.toString(); var iterator = "$it" + (AstForEachExpression.iteratorId++); var variableName = init.replace(/^\s*var\s*/, "").split("=")[0]; var initIteratorAndVariable = "var " + iterator + " = new $p.ObjectIterator(" + this.container + "), " + variableName + " = void(0)"; var nextIterationCondition = iterator + ".hasNext() && ((" + variableName + " = " + iterator + ".next()) || true)"; return "(" + initIteratorAndVariable + "; " + nextIterationCondition + ";)"; };
function transformForExpression(expr) { var content; if (/\bin\b/.test(expr)) { content = expr.substring(1, expr.length - 1).split(/\bin\b/g); return new AstForInExpression( transformStatement(trim(content[0])), transformExpression(content[1])); } if (expr.indexOf(":") >= 0 && expr.indexOf(";") < 0) { content = expr.substring(1, expr.length - 1).split(":"); return new AstForEachExpression( transformStatement(trim(content[0])), transformExpression(content[1])); } content = expr.substring(1, expr.length - 1).split(";"); return new AstForExpression( transformStatement(trim(content[0])), transformExpression(content[1]), transformExpression(content[2])); }
function sortByWeight(array) { array.sort(function (a,b) { return b.weight - a.weight; }); }
function AstInnerInterface(name, body, isStatic) { this.name = name; this.body = body; this.isStatic = isStatic; body.owner = this; } AstInnerInterface.prototype.toString = function() { return "" + this.body; }; function AstInnerClass(name, body, isStatic) { this.name = name; this.body = body; this.isStatic = isStatic; body.owner = this; } AstInnerClass.prototype.toString = function() { return "" + this.body; };
function transformInnerClass(class_) { var m = classesRegex.exec(class_); // 1 - attr, 2 - class|int, 3 - name, 4 - extends, 5 - implements, 6 - body classesRegex.lastIndex = 0; var isStatic = m[1].indexOf("static") >= 0; var body = atoms[getAtomIndex(m[6])], innerClass; var oldClassId = currentClassId, newClassId = generateClassId(); currentClassId = newClassId; if(m[2] === "interface") { innerClass = new AstInnerInterface(m[3], transformInterfaceBody(body, m[3], m[4]), isStatic); } else { innerClass = new AstInnerClass(m[3], transformClassBody(body, m[3], m[4], m[5]), isStatic); } appendClass(innerClass, newClassId, oldClassId); currentClassId = oldClassId; return innerClass; }
function AstClassMethod(name, params, body, isStatic) { this.name = name; this.params = params; this.body = body; this.isStatic = isStatic; } AstClassMethod.prototype.toString = function(){ var paramNames = appendToLookupTable({}, this.params.getNames()); var oldContext = replaceContext; replaceContext = function (subject) { return paramNames.hasOwnProperty(subject.name) ? subject.name : oldContext(subject); }; var body = this.params.prependMethodArgs(this.body.toString()); var result = "function " + this.methodId + this.params + " " + body +"\n"; replaceContext = oldContext; return result; };
function transformClassMethod(method) { var m = methodsRegex.exec(method); methodsRegex.lastIndex = 0; var isStatic = m[1].indexOf("static") >= 0; var body = m[6] !== ';' ? atoms[getAtomIndex(m[6])] : "{}"; return new AstClassMethod(m[3], transformParams(atoms[getAtomIndex(m[4])]), transformStatementsBlock(body), isStatic ); }
function AstClassField(definitions, fieldType, isStatic) { this.definitions = definitions; this.fieldType = fieldType; this.isStatic = isStatic; } AstClassField.prototype.getNames = function() { var names = []; for(var i=0,l=this.definitions.length;i<l;++i) { names.push(this.definitions[i].name); } return names; }; AstClassField.prototype.toString = function() { var thisPrefix = replaceContext({ name: "[this]" }); if(this.isStatic) { var className = this.owner.name; var staticDeclarations = []; for(var i=0,l=this.definitions.length;i<l;++i) { var definition = this.definitions[i]; var name = definition.name, staticName = className + "." + name; var declaration = "if(" + staticName + " === void(0)) {\n" + " " + staticName + " = " + definition.value + "; }\n" + "$p.defineProperty(" + thisPrefix + ", " + "'" + name + "', { get: function(){return " + staticName + ";}, " + "set: function(val){" + staticName + " = val;} });\n"; staticDeclarations.push(declaration); } return staticDeclarations.join(""); } return thisPrefix + "." + this.definitions.join("; " + thisPrefix + "."); };
function transformClassField(statement) { var attrAndType = attrAndTypeRegex.exec(statement); var isStatic = attrAndType[1].indexOf("static") >= 0; var definitions = statement.substring(attrAndType[0].length).split(/,\s*/g); var defaultTypeValue = getDefaultValueForType(attrAndType[2]); for(var i=0; i < definitions.length; ++i) { definitions[i] = transformVarDefinition(definitions[i], defaultTypeValue); } return new AstClassField(definitions, attrAndType[2], isStatic); }
function AstConstructor(params, body) { this.params = params; this.body = body; } AstConstructor.prototype.toString = function() { var paramNames = appendToLookupTable({}, this.params.getNames()); var oldContext = replaceContext; replaceContext = function (subject) { return paramNames.hasOwnProperty(subject.name) ? subject.name : oldContext(subject); }; var prefix = "function $constr_" + this.params.params.length + this.params.toString(); var body = this.params.prependMethodArgs(this.body.toString()); if(!/\$(superCstr|constr)\b/.test(body)) { body = "{\n$superCstr();\n" + body.substring(1); } replaceContext = oldContext; return prefix + body + "\n"; };
function transformConstructor(cstr) { var m = new RegExp(/"B(\d+)"\s*"A(\d+)"/).exec(cstr); var params = transformParams(atoms[m[1]]);
return new AstConstructor(params, transformStatementsBlock(atoms[m[2]])); }
function AstInterfaceBody(name, interfacesNames, methodsNames, fields, innerClasses, misc) { var i,l; this.name = name; this.interfacesNames = interfacesNames; this.methodsNames = methodsNames; this.fields = fields; this.innerClasses = innerClasses; this.misc = misc; for(i=0,l=fields.length; i<l; ++i) { fields[i].owner = this; } } AstInterfaceBody.prototype.getMembers = function(classFields, classMethods, classInners) { if(this.owner.base) { this.owner.base.body.getMembers(classFields, classMethods, classInners); } var i, j, l, m; for(i=0,l=this.fields.length;i<l;++i) { var fieldNames = this.fields[i].getNames(); for(j=0,m=fieldNames.length;j<m;++j) { classFields[fieldNames[j]] = this.fields[i]; } } for(i=0,l=this.methodsNames.length;i<l;++i) { var methodName = this.methodsNames[i]; classMethods[methodName] = true; } for(i=0,l=this.innerClasses.length;i<l;++i) { var innerClass = this.innerClasses[i]; classInners[innerClass.name] = innerClass; } }; AstInterfaceBody.prototype.toString = function() { function getScopeLevel(p) { var i = 0; while(p) { ++i; p=p.scope; } return i; }
var scopeLevel = getScopeLevel(this.owner);
var className = this.name; var staticDefinitions = ""; var metadata = "";
var thisClassFields = {}, thisClassMethods = {}, thisClassInners = {}; this.getMembers(thisClassFields, thisClassMethods, thisClassInners);
var i, l, j, m;
if (this.owner.interfaces) { // interface name can be present, but interface is not var resolvedInterfaces = [], resolvedInterface; for (i = 0, l = this.interfacesNames.length; i < l; ++i) { if (!this.owner.interfaces[i]) { continue; } resolvedInterface = replaceContext({name: this.interfacesNames[i]}); resolvedInterfaces.push(resolvedInterface); staticDefinitions += "$p.extendInterfaceMembers(" + className + ", " + resolvedInterface + ");\n"; } metadata += className + ".$interfaces = [" + resolvedInterfaces.join(", ") + "];\n"; } metadata += className + ".$isInterface = true;\n"; metadata += className + ".$methods = [\'" + this.methodsNames.join("\', \'") + "\'];\n";
sortByWeight(this.innerClasses); for (i = 0, l = this.innerClasses.length; i < l; ++i) { var innerClass = this.innerClasses[i]; if (innerClass.isStatic) { staticDefinitions += className + "." + innerClass.name + " = " + innerClass + ";\n"; } }
for (i = 0, l = this.fields.length; i < l; ++i) { var field = this.fields[i]; if (field.isStatic) { staticDefinitions += className + "." + field.definitions.join(";\n" + className + ".") + ";\n"; } }
return "(function() {\n" + "function " + className + "() { throw \'Unable to create the interface\'; }\n" + staticDefinitions + metadata + "return " + className + ";\n" + "})()"; };
transformInterfaceBody = function(body, name, baseInterfaces) { var declarations = body.substring(1, body.length - 1); declarations = extractClassesAndMethods(declarations); declarations = extractConstructors(declarations, name); var methodsNames = [], classes = []; declarations = declarations.replace(/"([DE])(\d+)"/g, function(all, type, index) { if(type === 'D') { methodsNames.push(index); } else if(type === 'E') { classes.push(index); } return ""; }); var fields = declarations.split(/;(?:\s*;)*/g); var baseInterfaceNames; var i, l;
if(baseInterfaces !== undef) { baseInterfaceNames = baseInterfaces.replace(/^\s*extends\s+(.+?)\s*$/g, "$1").split(/\s*,\s*/g); }
for(i = 0, l = methodsNames.length; i < l; ++i) { var method = transformClassMethod(atoms[methodsNames[i]]); methodsNames[i] = method.name; } for(i = 0, l = fields.length - 1; i < l; ++i) { var field = trimSpaces(fields[i]); fields[i] = transformClassField(field.middle); } var tail = fields.pop(); for(i = 0, l = classes.length; i < l; ++i) { classes[i] = transformInnerClass(atoms[classes[i]]); }
return new AstInterfaceBody(name, baseInterfaceNames, methodsNames, fields, classes, { tail: tail }); };
function AstClassBody(name, baseClassName, interfacesNames, functions, methods, fields, cstrs, innerClasses, misc) { var i,l; this.name = name; this.baseClassName = baseClassName; this.interfacesNames = interfacesNames; this.functions = functions; this.methods = methods; this.fields = fields; this.cstrs = cstrs; this.innerClasses = innerClasses; this.misc = misc; for(i=0,l=fields.length; i<l; ++i) { fields[i].owner = this; } } AstClassBody.prototype.getMembers = function(classFields, classMethods, classInners) { if(this.owner.base) { this.owner.base.body.getMembers(classFields, classMethods, classInners); } var i, j, l, m; for(i=0,l=this.fields.length;i<l;++i) { var fieldNames = this.fields[i].getNames(); for(j=0,m=fieldNames.length;j<m;++j) { classFields[fieldNames[j]] = this.fields[i]; } } for(i=0,l=this.methods.length;i<l;++i) { var method = this.methods[i]; classMethods[method.name] = method; } for(i=0,l=this.innerClasses.length;i<l;++i) { var innerClass = this.innerClasses[i]; classInners[innerClass.name] = innerClass; } }; AstClassBody.prototype.toString = function() { function getScopeLevel(p) { var i = 0; while(p) { ++i; p=p.scope; } return i; }
var scopeLevel = getScopeLevel(this.owner);
var selfId = "$this_" + scopeLevel; var className = this.name; var result = "var " + selfId + " = this;\n"; var staticDefinitions = ""; var metadata = "";
var thisClassFields = {}, thisClassMethods = {}, thisClassInners = {}; this.getMembers(thisClassFields, thisClassMethods, thisClassInners);
var oldContext = replaceContext; replaceContext = function (subject) { var name = subject.name; if(name === "this") { // returns "$this_N.$self" pointer instead of "this" in cases: // "this()", "this.XXX()", "this", but not for "this.XXX" return subject.callSign || !subject.member ? selfId + ".$self" : selfId; } if(thisClassFields.hasOwnProperty(name)) { return thisClassFields[name].isStatic ? className + "." + name : selfId + "." + name; } if(thisClassInners.hasOwnProperty(name)) { return selfId + "." + name; } if(thisClassMethods.hasOwnProperty(name)) { return thisClassMethods[name].isStatic ? className + "." + name : selfId + ".$self." + name; } return oldContext(subject); };
var resolvedBaseClassName; if (this.baseClassName) { resolvedBaseClassName = oldContext({name: this.baseClassName}); result += "var $super = { $upcast: " + selfId + " };\n"; result += "function $superCstr(){" + resolvedBaseClassName + ".apply($super,arguments);if(!('$self' in $super)) $p.extendClassChain($super)}\n"; metadata += className + ".$base = " + resolvedBaseClassName + ";\n"; } else { result += "function $superCstr(){$p.extendClassChain("+ selfId +")}\n"; }
if (this.owner.base) { // base class name can be present, but class is not staticDefinitions += "$p.extendStaticMembers(" + className + ", " + resolvedBaseClassName + ");\n"; }
var i, l, j, m;
if (this.owner.interfaces) { // interface name can be present, but interface is not var resolvedInterfaces = [], resolvedInterface; for (i = 0, l = this.interfacesNames.length; i < l; ++i) { if (!this.owner.interfaces[i]) { continue; } resolvedInterface = oldContext({name: this.interfacesNames[i]}); resolvedInterfaces.push(resolvedInterface); staticDefinitions += "$p.extendInterfaceMembers(" + className + ", " + resolvedInterface + ");\n"; } metadata += className + ".$interfaces = [" + resolvedInterfaces.join(", ") + "];\n"; }
if (this.functions.length > 0) { result += this.functions.join('\n') + '\n'; }
sortByWeight(this.innerClasses); for (i = 0, l = this.innerClasses.length; i < l; ++i) { var innerClass = this.innerClasses[i]; if (innerClass.isStatic) { staticDefinitions += className + "." + innerClass.name + " = " + innerClass + ";\n"; result += selfId + "." + innerClass.name + " = " + className + "." + innerClass.name + ";\n"; } else { result += selfId + "." + innerClass.name + " = " + innerClass + ";\n"; } }
for (i = 0, l = this.fields.length; i < l; ++i) { var field = this.fields[i]; if (field.isStatic) { staticDefinitions += className + "." + field.definitions.join(";\n" + className + ".") + ";\n"; for (j = 0, m = field.definitions.length; j < m; ++j) { var fieldName = field.definitions[j].name, staticName = className + "." + fieldName; result += "$p.defineProperty(" + selfId + ", '" + fieldName + "', {" + "get: function(){return " + staticName + "}, " + "set: function(val){" + staticName + " = val}});\n"; } } else { result += selfId + "." + field.definitions.join(";\n" + selfId + ".") + ";\n"; } } var methodOverloads = {}; for (i = 0, l = this.methods.length; i < l; ++i) { var method = this.methods[i]; var overload = methodOverloads[method.name]; var methodId = method.name + "$" + method.params.params.length; var hasMethodArgs = !!method.params.methodArgsParam; if (overload) { ++overload; methodId += "_" + overload; } else { overload = 1; } method.methodId = methodId; methodOverloads[method.name] = overload; if (method.isStatic) { staticDefinitions += method; staticDefinitions += "$p.addMethod(" + className + ", '" + method.name + "', " + methodId + ", " + hasMethodArgs + ");\n"; result += "$p.addMethod(" + selfId + ", '" + method.name + "', " + methodId + ", " + hasMethodArgs + ");\n"; } else { result += method; result += "$p.addMethod(" + selfId + ", '" + method.name + "', " + methodId + ", " + hasMethodArgs + ");\n"; } } result += trim(this.misc.tail);
if (this.cstrs.length > 0) { result += this.cstrs.join('\n') + '\n'; }
result += "function $constr() {\n"; var cstrsIfs = []; for (i = 0, l = this.cstrs.length; i < l; ++i) { var paramsLength = this.cstrs[i].params.params.length; var methodArgsPresent = !!this.cstrs[i].params.methodArgsParam; cstrsIfs.push("if(arguments.length " + (methodArgsPresent ? ">=" : "===") + " " + paramsLength + ") { " + "$constr_" + paramsLength + ".apply(" + selfId + ", arguments); }"); } if(cstrsIfs.length > 0) { result += cstrsIfs.join(" else ") + " else "; } // ??? add check if length is 0, otherwise fail result += "$superCstr();\n}\n"; result += "$constr.apply(null, arguments);\n";
replaceContext = oldContext; return "(function() {\n" + "function " + className + "() {\n" + result + "}\n" + staticDefinitions + metadata + "return " + className + ";\n" + "})()"; };
transformClassBody = function(body, name, baseName, interfaces) { var declarations = body.substring(1, body.length - 1); declarations = extractClassesAndMethods(declarations); declarations = extractConstructors(declarations, name); var methods = [], classes = [], cstrs = [], functions = []; declarations = declarations.replace(/"([DEGH])(\d+)"/g, function(all, type, index) { if(type === 'D') { methods.push(index); } else if(type === 'E') { classes.push(index); } else if(type === 'H') { functions.push(index); } else { cstrs.push(index); } return ""; }); var fields = declarations.replace(/^(?:\s*;)+/, "").split(/;(?:\s*;)*/g); var baseClassName, interfacesNames; var i;
if(baseName !== undef) { baseClassName = baseName.replace(/^\s*extends\s+([A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)\s*$/g, "$1"); }
if(interfaces !== undef) { interfacesNames = interfaces.replace(/^\s*implements\s+(.+?)\s*$/g, "$1").split(/\s*,\s*/g); }
for(i = 0; i < functions.length; ++i) { functions[i] = transformFunction(atoms[functions[i]]); } for(i = 0; i < methods.length; ++i) { methods[i] = transformClassMethod(atoms[methods[i]]); } for(i = 0; i < fields.length - 1; ++i) { var field = trimSpaces(fields[i]); fields[i] = transformClassField(field.middle); } var tail = fields.pop(); for(i = 0; i < cstrs.length; ++i) { cstrs[i] = transformConstructor(atoms[cstrs[i]]); } for(i = 0; i < classes.length; ++i) { classes[i] = transformInnerClass(atoms[classes[i]]); }
return new AstClassBody(name, baseClassName, interfacesNames, functions, methods, fields, cstrs, classes, { tail: tail }); };
function AstInterface(name, body) { this.name = name; this.body = body; body.owner = this; } AstInterface.prototype.toString = function() { return "var " + this.name + " = " + this.body + ";\n" + "$p." + this.name + " = " + this.name + ";\n"; }; function AstClass(name, body) { this.name = name; this.body = body; body.owner = this; } AstClass.prototype.toString = function() { return "var " + this.name + " = " + this.body + ";\n" + "$p." + this.name + " = " + this.name + ";\n"; };
function transformGlobalClass(class_) { var m = classesRegex.exec(class_); // 1 - attr, 2 - class|int, 3 - name, 4 - extends, 5 - implements, 6 - body classesRegex.lastIndex = 0; var body = atoms[getAtomIndex(m[6])]; var oldClassId = currentClassId, newClassId = generateClassId(); currentClassId = newClassId; var globalClass; if(m[2] === "interface") { globalClass = new AstInterface(m[3], transformInterfaceBody(body, m[3], m[4]) ); } else { globalClass = new AstClass(m[3], transformClassBody(body, m[3], m[4], m[5]) ); } appendClass(globalClass, newClassId, oldClassId); currentClassId = oldClassId; return globalClass; }
function AstMethod(name, params, body) { this.name = name; this.params = params; this.body = body; } AstMethod.prototype.toString = function(){ var paramNames = appendToLookupTable({}, this.params.getNames()); var oldContext = replaceContext; replaceContext = function (subject) { return paramNames.hasOwnProperty(subject.name) ? subject.name : oldContext(subject); }; var body = this.params.prependMethodArgs(this.body.toString()); var result = "function " + this.name + this.params + " " + body + "\n" + "$p." + this.name + " = " + this.name + ";\n" + this.name + " = " + this.name + ".bind($p);";
// "$p." + this.name + " = " + this.name + ";";
replaceContext = oldContext; return result; };
function transformGlobalMethod(method) { var m = methodsRegex.exec(method); var result = methodsRegex.lastIndex = 0; return new AstMethod(m[3], transformParams(atoms[getAtomIndex(m[4])]), transformStatementsBlock(atoms[getAtomIndex(m[6])])); }
function preStatementsTransform(statements) { var s = statements; // turns multiple catch blocks into one, because we have no way to properly get into them anyway. s = s.replace(/\b(catch\s*"B\d+"\s*"A\d+")(\s*catch\s*"B\d+"\s*"A\d+")+/g, "$1"); return s; }
function AstForStatement(argument, misc) { this.argument = argument; this.misc = misc; } AstForStatement.prototype.toString = function() { return this.misc.prefix + this.argument.toString(); }; function AstCatchStatement(argument, misc) { this.argument = argument; this.misc = misc; } AstCatchStatement.prototype.toString = function() { return this.misc.prefix + this.argument.toString(); }; function AstPrefixStatement(name, argument, misc) { this.name = name; this.argument = argument; this.misc = misc; } AstPrefixStatement.prototype.toString = function() { var result = this.misc.prefix; if(this.argument !== undef) { result += this.argument.toString(); } return result; }; function AstSwitchCase(expr) { this.expr = expr; } AstSwitchCase.prototype.toString = function() { return "case " + this.expr + ":"; }; function AstLabel(label) { this.label = label; } AstLabel.prototype.toString = function() { return this.label; };
transformStatements = function(statements, transformMethod, transformClass) { var nextStatement = new RegExp(/\b(catch|for|if|switch|while|with)\s*"B(\d+)"|\b(do|else|finally|return|throw|try|break|continue)\b|("[ADEH](\d+)")|\b(case)\s+([^:]+):|\b([A-Za-z_$][\w$]*\s*:)|(;)/g); var res = []; statements = preStatementsTransform(statements); var lastIndex = 0, m, space; // m contains the matches from the nextStatement regexp, null if there are no matches. // nextStatement.exec starts searching at nextStatement.lastIndex. while((m = nextStatement.exec(statements)) !== null) { if(m[1] !== undef) { // catch, for ... var i = statements.lastIndexOf('"B', nextStatement.lastIndex); var statementsPrefix = statements.substring(lastIndex, i); if(m[1] === "for") { res.push(new AstForStatement(transformForExpression(atoms[m[2]]), { prefix: statementsPrefix }) ); } else if(m[1] === "catch") { res.push(new AstCatchStatement(transformParams(atoms[m[2]]), { prefix: statementsPrefix }) ); } else { res.push(new AstPrefixStatement(m[1], transformExpression(atoms[m[2]]), { prefix: statementsPrefix }) ); } } else if(m[3] !== undef) { // do, else, ... res.push(new AstPrefixStatement(m[3], undef, { prefix: statements.substring(lastIndex, nextStatement.lastIndex) }) ); } else if(m[4] !== undef) { // block, class and methods space = statements.substring(lastIndex, nextStatement.lastIndex - m[4].length); if(trim(space).length !== 0) { continue; } // avoiding new type[] {} construct res.push(space); var kind = m[4].charAt(1), atomIndex = m[5]; if(kind === 'D') { res.push(transformMethod(atoms[atomIndex])); } else if(kind === 'E') { res.push(transformClass(atoms[atomIndex])); } else if(kind === 'H') { res.push(transformFunction(atoms[atomIndex])); } else { res.push(transformStatementsBlock(atoms[atomIndex])); } } else if(m[6] !== undef) { // switch case res.push(new AstSwitchCase(transformExpression(trim(m[7])))); } else if(m[8] !== undef) { // label space = statements.substring(lastIndex, nextStatement.lastIndex - m[8].length); if(trim(space).length !== 0) { continue; } // avoiding ?: construct res.push(new AstLabel(statements.substring(lastIndex, nextStatement.lastIndex)) ); } else { // semicolon var statement = trimSpaces(statements.substring(lastIndex, nextStatement.lastIndex - 1)); res.push(statement.left); res.push(transformStatement(statement.middle)); res.push(statement.right + ";"); } lastIndex = nextStatement.lastIndex; } var statementsTail = trimSpaces(statements.substring(lastIndex)); res.push(statementsTail.left); if(statementsTail.middle !== "") { res.push(transformStatement(statementsTail.middle)); res.push(";" + statementsTail.right); } return res; };
function getLocalNames(statements) { var localNames = []; for(var i=0,l=statements.length;i<l;++i) { var statement = statements[i]; if(statement instanceof AstVar) { localNames = localNames.concat(statement.getNames()); } else if(statement instanceof AstForStatement && statement.argument.initStatement instanceof AstVar) { localNames = localNames.concat(statement.argument.initStatement.getNames()); } else if(statement instanceof AstInnerInterface || statement instanceof AstInnerClass || statement instanceof AstInterface || statement instanceof AstClass || statement instanceof AstMethod || statement instanceof AstFunction) { localNames.push(statement.name); } } return appendToLookupTable({}, localNames); }
function AstStatementsBlock(statements) { this.statements = statements; } AstStatementsBlock.prototype.toString = function() { var localNames = getLocalNames(this.statements); var oldContext = replaceContext;
// replacing context only when necessary if(!isLookupTableEmpty(localNames)) { replaceContext = function (subject) { return localNames.hasOwnProperty(subject.name) ? subject.name : oldContext(subject); }; }
var result = "{\n" + this.statements.join() + "\n}"; replaceContext = oldContext; return result; };
transformStatementsBlock = function(block) { var content = trimSpaces(block.substring(1, block.length - 1)); return new AstStatementsBlock(transformStatements(content.middle)); };
function AstRoot(statements) { this.statements = statements; } AstRoot.prototype.toString = function() { var classes = [], otherStatements = [], statement; for (var i = 0, len = this.statements.length; i < len; ++i) { statement = this.statements[i]; if (statement instanceof AstClass || statement instanceof AstInterface) { classes.push(statement); } else { otherStatements.push(statement); } } sortByWeight(classes);
var localNames = getLocalNames(this.statements); replaceContext = function (subject) { var name = subject.name; if(localNames.hasOwnProperty(name)) { return name; } if(globalMembers.hasOwnProperty(name) || PConstants.hasOwnProperty(name) || defaultScope.hasOwnProperty(name)) { return "$p." + name; } return name; }; var result = "// this code was autogenerated from PJS\n" + "(function($p) {\n" + classes.join() + "\n" + otherStatements.join() + "\n})"; replaceContext = null; return result; };
transformMain = function() { var statements = extractClassesAndMethods(atoms[0]); statements = statements.replace(/\bimport\s+[^;]+;/g, ""); return new AstRoot( transformStatements(statements, transformGlobalMethod, transformGlobalClass) ); };
function generateMetadata(ast) { var globalScope = {}; var id, class_; for(id in declaredClasses) { if(declaredClasses.hasOwnProperty(id)) { class_ = declaredClasses[id]; var scopeId = class_.scopeId, name = class_.name; if(scopeId) { var scope = declaredClasses[scopeId]; class_.scope = scope; if(scope.inScope === undef) { scope.inScope = {}; } scope.inScope[name] = class_; } else { globalScope[name] = class_; } } }
function findInScopes(class_, name) { var parts = name.split('.'); var currentScope = class_.scope, found; while(currentScope) { if(currentScope.hasOwnProperty(parts[0])) { found = currentScope[parts[0]]; break; } currentScope = currentScope.scope; } if(found === undef) { found = globalScope[parts[0]]; } for(var i=1,l=parts.length;i<l && found;++i) { found = found.inScope[parts[i]]; } return found; }
for(id in declaredClasses) { if(declaredClasses.hasOwnProperty(id)) { class_ = declaredClasses[id]; var baseClassName = class_.body.baseClassName; if(baseClassName) { var parent = findInScopes(class_, baseClassName); if (parent) { class_.base = parent; if (!parent.derived) { parent.derived = []; } parent.derived.push(class_); } } var interfacesNames = class_.body.interfacesNames, interfaces = [], i, l; if (interfacesNames && interfacesNames.length > 0) { for (i = 0, l = interfacesNames.length; i < l; ++i) { var interface_ = findInScopes(class_, interfacesNames[i]); interfaces.push(interface_); if (!interface_) { continue; } if (!interface_.derived) { interface_.derived = []; } interface_.derived.push(class_); } if (interfaces.length > 0) { class_.interfaces = interfaces; } } } } }
function setWeight(ast) { var queue = [], tocheck = {}; var id, scopeId, class_; // queue most inner and non-inherited for (id in declaredClasses) { if (declaredClasses.hasOwnProperty(id)) { class_ = declaredClasses[id]; if (!class_.inScope && !class_.derived) { queue.push(id); class_.weight = 0; } else { var dependsOn = []; if (class_.inScope) { for (scopeId in class_.inScope) { if (class_.inScope.hasOwnProperty(scopeId)) { dependsOn.push(class_.inScope[scopeId]); } } } if (class_.derived) { dependsOn = dependsOn.concat(class_.derived); } tocheck[id] = dependsOn; } } } function removeDependentAndCheck(targetId, from) { var dependsOn = tocheck[targetId]; if (!dependsOn) { return false; // no need to process } var i = dependsOn.indexOf(from); if (i < 0) { return false; } dependsOn.splice(i, 1); if (dependsOn.length > 0) { return false; } delete tocheck[targetId]; return true; } while (queue.length > 0) { id = queue.shift(); class_ = declaredClasses[id]; if (class_.scopeId && removeDependentAndCheck(class_.scopeId, class_)) { queue.push(class_.scopeId); declaredClasses[class_.scopeId].weight = class_.weight + 1; } if (class_.base && removeDependentAndCheck(class_.base.classId, class_)) { queue.push(class_.base.classId); class_.base.weight = class_.weight + 1; } if (class_.interfaces) { var i, l; for (i = 0, l = class_.interfaces.length; i < l; ++i) { if (!class_.interfaces[i] || !removeDependentAndCheck(class_.interfaces[i].classId, class_)) { continue; } queue.push(class_.interfaces[i].classId); class_.interfaces[i].weight = class_.weight + 1; } } } }
var transformed = transformMain(); generateMetadata(transformed); setWeight(transformed);
var redendered = transformed.toString();
// remove empty extra lines with space redendered = redendered.replace(/\s*\n(?:[\t ]*\n)+/g, "\n\n");
// convert character codes to characters redendered = redendered.replace(/__x([0-9A-F]{4})/g, function(all, hexCode) { return String.fromCharCode(parseInt(hexCode,16)); });
return injectStrings(redendered, strings); }// Parser ends
function preprocessCode(aCode, sketch) { // Parse out @pjs directive, if any. var dm = new RegExp(/\/\*\s*@pjs\s+((?:[^\*]|\*+[^\*\/])*)\*\//g).exec(aCode); if (dm && dm.length === 2) { // masks contents of a JSON to be replaced later // to protect the contents from further parsing var jsonItems = [], directives = dm.splice(1, 2)[0].replace(/\{([\s\S]*?)\}/g, (function() { return function(all, item) { jsonItems.push(item); return "{" + (jsonItems.length-1) + "}"; }; }())).replace('\n', ).replace('\r', ).split(";");
// We'll L/RTrim, and also remove any surrounding double quotes (e.g., just take string contents) var clean = function(s) { return s.replace(/^\s*["']?/, ).replace(/["']?\s*$/, ); };
for (var i = 0, dl = directives.length; i < dl; i++) { var pair = directives[i].split('='); if (pair && pair.length === 2) { var key = clean(pair[0]), value = clean(pair[1]), list = []; // A few directives require work beyond storying key/value pairings if (key === "preload") { list = value.split(','); // All pre-loaded images will get put in imageCache, keyed on filename for (var j = 0, jl = list.length; j < jl; j++) { var imageName = clean(list[j]); sketch.imageCache.add(imageName); } // fonts can be declared as a string containing a url, // or a JSON object, containing a font name, and a url } else if (key === "font") { list = value.split(","); for (var x = 0, xl = list.length; x < xl; x++) { var fontName = clean(list[x]), index = /^\{(\d*?)\}$/.exec(fontName); // if index is not null, send JSON, otherwise, send string PFont.preloading.add(index ? JSON.parse("{" + jsonItems[index[1]] + "}") : fontName); } } else if (key === "pauseOnBlur") { sketch.options.pauseOnBlur = value === "true"; } else if (key === "globalKeyEvents") { sketch.options.globalKeyEvents = value === "true"; } else if (key.substring(0, 6) === "param-") { sketch.params[key.substring(6)] = value; } else { sketch.options[key] = value; } } } } return aCode; }
// Parse/compiles Processing (Java-like) syntax to JavaScript syntax Processing.compile = function(pdeCode) { var sketch = new Processing.Sketch(); var code = preprocessCode(pdeCode, sketch); var compiledPde = parseProcessing(code); sketch.sourceCode = compiledPde; return sketch; };
// the logger for println() var PjsConsole = function (document) { var e = {}, added = false; e.BufferMax = 200; e.wrapper = document.createElement("div"); e.wrapper.setAttribute("style", "opacity:.75;display:block;position:fixed;bottom:0px;left:0px;right:0px;height:50px;background-color:#aaa"); e.dragger = document.createElement("div"); e.dragger.setAttribute("style", "display:block;border:3px black raised;cursor:n-resize;position:absolute;top:0px;left:0px;right:0px;height:5px;background-color:#333"); e.closer = document.createElement("div"); e.closer.onmouseover = function () { e.closer.style.setProperty("background-color", "#ccc"); }; e.closer.onmouseout = function () { e.closer.style.setProperty("background-color", "#ddd"); }; e.closer.innerHTML = "✖"; e.closer.setAttribute("style", "opacity:.5;display:block;border:3px black raised;position:absolute;top:10px;right:30px;height:20px;width:20px;background-color:#ddd;color:#000;line-height:20px;text-align:center;cursor:pointer;"); e.javaconsole = document.createElement("div"); e.javaconsole.setAttribute("style", "overflow-x: auto;display:block;position:absolute;left:10px;right:0px;bottom:5px;top:10px;overflow-y:scroll;height:40px;"); e.wrapper.appendChild(e.dragger); e.wrapper.appendChild(e.javaconsole); e.wrapper.appendChild(e.closer); e.dragger.onmousedown = function (t) { e.divheight = e.wrapper.style.height; if (document.selection) document.selection.empty(); else window.getSelection().removeAllRanges(); var n = t.screenY; window.onmousemove = function (t) { e.wrapper.style.height = parseFloat(e.divheight) + (n - t.screenY) + "px"; e.javaconsole.style.height = parseFloat(e.divheight) + (n - t.screenY) - 10 + "px"; }; window.onmouseup = function (t) { if (document.selection) document.selection.empty(); else window.getSelection().removeAllRanges(); e.wrapper.style.height = parseFloat(e.divheight) + (n - t.screenY) + "px"; e.javaconsole.style.height = parseFloat(e.divheight) + (n - t.screenY) - 10 + "px"; window.onmousemove = null; window.onmouseup = null; }; }; e.BufferArray = []; e.print = e.log = function (t) { // var oldheight = e.javaconsole.scrollHeight-e.javaconsole.scrollTop; if (e.BufferArray[e.BufferArray.length - 1]) e.BufferArray[e.BufferArray.length - 1] += (t) + ""; else e.BufferArray.push(t); e.javaconsole.innerHTML = e.BufferArray.join(); if (e.wrapper.style.visibility === "hidden") { e.wrapper.style.visibility = "visible"; } //if (e.BufferArray.length > e.BufferMax) e.BufferArray.splice(0, 1); //else e.javaconsole.scrollTop = oldheight; if (e.wrapper.style.visibility === "hidden") { e.wrapper.style.visibility = "visible"; } }; e.println = function (t) { if(!added) { document.body.appendChild(e.wrapper); } e.print(t); e.BufferArray.push('
'); e.javaconsole.innerHTML = e.BufferArray.join(); if (e.wrapper.style.visibility === "hidden") { e.wrapper.style.visibility = "visible"; } if (e.BufferArray.length > e.BufferMax) e.BufferArray.splice(0, 1); else e.javaconsole.scrollTop = e.javaconsole.scrollHeight; if (e.wrapper.style.visibility === "hidden") { e.wrapper.style.visibility = "visible"; } }; e.showconsole = function () { e.wrapper.style.visibility = "visible"; }; e.hideconsole = function () { e.wrapper.style.visibility = "hidden"; }; e.closer.onclick = function () { e.hideconsole(); }; e.hideconsole(); return e; };
Processing.logger = new PjsConsole(document);
// done return Processing;
};
},{}],26:[function(require,module,exports){ /**
* Processing.js object */ module.exports = function(options, undef) { var defaultScope = options.defaultScope, extend = options.extend, Browser = options.Browser, ajax = Browser.ajax, navigator = Browser.navigator, window = Browser.window, XMLHttpRequest = window.XMLHttpRequest, document = Browser.document, noop = options.noop,
PConstants = defaultScope.PConstants; PFont = defaultScope.PFont, PShapeSVG = defaultScope.PShapeSVG, PVector = defaultScope.PVector, Char = Character = defaultScope.Char, ObjectIterator = defaultScope.ObjectIterator, XMLElement = defaultScope.XMLElement, XML = defaultScope.XML;
// fascinating "read only" jshint error if we don't start a new var block here. var HTMLCanvasElement = window.HTMLCanvasElement, HTMLImageElement = window.HTMLImageElement, localStorage = window.localStorage;
var isDOMPresent = ("document" in this) && !("fake" in this.document);
// document.head polyfill for the benefit of Firefox 3.6 if (!document.head) { document.head = document.getElementsByTagName('head')[0]; }
var Float32Array = setupTypedArray("Float32Array", "WebGLFloatArray"), Int32Array = setupTypedArray("Int32Array", "WebGLIntArray"), Uint16Array = setupTypedArray("Uint16Array", "WebGLUnsignedShortArray"), Uint8Array = setupTypedArray("Uint8Array", "WebGLUnsignedByteArray");
// 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; } }; }
/* IE9+ quirks mode check - ticket #1606 */ if (document.documentMode >= 9 && !document.doctype) { throw("The doctype directive is missing. The recommended doctype in Internet Explorer is the HTML5 doctype: <!DOCTYPE html>"); }
// Manage multiple Processing instances var processingInstances = []; var processingInstanceIds = {};
/** * instance tracking - adding new instances */ 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); };
/** * instance tracking - removal */ var removeInstance = function(id) { processingInstances.splice(processingInstanceIds[id], 1); delete processingInstanceIds[id]; };
/** * The Processing object */ var Processing = this.Processing = function(aCanvas, aCode, aFunctions) {
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 (!('getContext' in curElement)) { throw("called Processing constructor without passing canvas element reference or id."); }
function unimplemented(s) { Processing.debug('Unimplemented - ' + s); }
//////////////////////////////////////////////////////////////////////////// // JavaScript event binding and releasing ////////////////////////////////////////////////////////////////////////////
var eventHandlers = [];
function attachEventHandler(elem, type, fn) { if (elem.addEventListener) { elem.addEventListener(type, fn, false); } else { elem.attachEvent("on" + type, fn); } eventHandlers.push({elem: elem, type: type, fn: fn}); }
function detachEventHandler(eventHandler) { var elem = eventHandler.elem, type = eventHandler.type, fn = eventHandler.fn; if (elem.removeEventListener) { elem.removeEventListener(type, fn, false); } else if (elem.detachEvent) { elem.detachEvent("on" + type, fn); } }
function removeFirstArgument(args) { return Array.prototype.slice.call(args, 1); }
// 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;
p.Char = p.Character = Char;
// add in the Processing API functions extend.withCommonFunctions(p); extend.withMath(p); extend.withProxyFunctions(p, removeFirstArgument); extend.withTouch(p, curElement, attachEventHandler, document, PConstants);
// custom functions and properties are added here if(aFunctions) { Object.keys(aFunctions).forEach(function(name) { p[name] = aFunctions[name]; }); }
// PJS specific (non-p5) methods and properties to externalize p.externals = { canvas: curElement, context: undef, sketch: undef, window: window };
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 = noop; // needed to remove function checks p.keyReleased = noop; p.keyTyped = noop; p.draw = undef; p.setup = undef;
// Remapped vars p.__mousePressed = false; p.__keyPressed = false; p.__frameRate = 60;
// The current animation frame p.frameCount = 0;
// The height/width of the canvas p.width = 100; p.height = 100;
// "Private" variables used to maintain state var curContext, curSketch, drawing, // hold a Drawing2D or Drawing3D object 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 ];
// 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 = [];
// The vertices for the box cannot be specified using a triangle strip since each // side of the cube must have its own set of normals. // 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 vertexShaderSrcUnlitShape = "varying vec4 vFrontColor;" +
"attribute vec3 aVertex;" + "attribute vec4 aColor;" +
"uniform mat4 uView;" + "uniform mat4 uProjection;" + "uniform float uPointSize;" +
"void main(void) {" + " vFrontColor = aColor;" + " gl_PointSize = uPointSize;" + " gl_Position = uProjection * uView * vec4(aVertex, 1.0);" + "}";
var fragmentShaderSrcUnlitShape = "#ifdef GL_ES\n" + "precision highp float;\n" + "#endif\n" +
"varying vec4 vFrontColor;" + "uniform bool uSmooth;" +
"void main(void){" + " if(uSmooth == true){" + " float dist = distance(gl_PointCoord, vec2(0.5));" + " if(dist > 0.5){" + " discard;" + " }" + " }" + " gl_FragColor = vFrontColor;" + "}";
// Shader for rect, text, box outlines, sphere outlines, point() and line(). var vertexShaderSrc2D = "varying vec4 vFrontColor;" +
"attribute vec3 aVertex;" + "attribute vec2 aTextureCoord;" + "uniform vec4 uColor;" +
"uniform mat4 uModel;" + "uniform mat4 uView;" + "uniform mat4 uProjection;" + "uniform float uPointSize;" + "varying vec2 vTextureCoord;"+
"void main(void) {" + " gl_PointSize = uPointSize;" + " vFrontColor = uColor;" + " gl_Position = uProjection * uView * uModel * vec4(aVertex, 1.0);" + " vTextureCoord = aTextureCoord;" + "}";
var fragmentShaderSrc2D = "#ifdef GL_ES\n" + "precision highp float;\n" + "#endif\n" +
"varying vec4 vFrontColor;" + "varying vec2 vTextureCoord;"+
"uniform sampler2D uSampler;"+ "uniform int uIsDrawingText;"+ "uniform bool uSmooth;" +
"void main(void){" + // WebGL does not support POINT_SMOOTH, so we do it ourselves " if(uSmooth == true){" + " float dist = distance(gl_PointCoord, vec2(0.5));" + " if(dist > 0.5){" + " discard;" + " }" + " }" +
" if(uIsDrawingText == 1){" + " float alpha = texture2D(uSampler, vTextureCoord).a;"+ " gl_FragColor = vec4(vFrontColor.rgb * alpha, alpha);"+ " }" + " else{" + " gl_FragColor = vFrontColor;" + " }" + "}";
var webglMaxTempsWorkaround = /Windows/.test(navigator.userAgent);
// Vertex shader for boxes and spheres. var vertexShaderSrc3D = "varying vec4 vFrontColor;" +
"attribute vec3 aVertex;" + "attribute vec3 aNormal;" + "attribute vec4 aColor;" + "attribute vec2 aTexture;" + "varying vec2 vTexture;" +
"uniform vec4 uColor;" +
"uniform bool uUsingMat;" + "uniform vec3 uSpecular;" + "uniform vec3 uMaterialEmissive;" + "uniform vec3 uMaterialAmbient;" + "uniform vec3 uMaterialSpecular;" + "uniform float uShininess;" +
"uniform mat4 uModel;" + "uniform mat4 uView;" + "uniform mat4 uProjection;" + "uniform mat4 uNormalTransform;" +
"uniform int uLightCount;" + "uniform vec3 uFalloff;" +
// 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 uLights0;" + "uniform Light uLights1;" + "uniform Light uLights2;" + "uniform Light uLights3;" + "uniform Light uLights4;" + "uniform Light uLights5;" + "uniform Light uLights6;" + "uniform Light uLights7;" +
// GLSL does not support switch. "Light getLight(int index){" + " if(index == 0) return uLights0;" + " if(index == 1) return uLights1;" + " if(index == 2) return uLights2;" + " if(index == 3) return uLights3;" + " if(index == 4) return uLights4;" + " if(index == 5) return uLights5;" + " if(index == 6) return uLights6;" + // Do not use a conditional for the last return statement // because some video cards will fail and complain that // "not all paths return". " return uLights7;" + "}" +
"void AmbientLight( inout vec3 totalAmbient, in vec3 ecPos, in Light light ) {" + // Get the vector from the light to the vertex and // get the distance from the current vector to the light position. " float d = length( light.position - ecPos );" + " float attenuation = 1.0 / ( uFalloff[0] + ( uFalloff[1] * d ) + ( uFalloff[2] * d * d ));" + " totalAmbient += light.color * attenuation;" + "}" +
/* col - accumulated color spec - accumulated specular highlight vertNormal - Normal of the vertex ecPos - eye coordinate position light - light structure */ "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, uShininess );" + " }" +
" col += light.color * nDotVP;" + " spec += uSpecular * powerFactor;" + "}" +
/* col - accumulated color spec - accumulated specular highlight vertNormal - Normal of the vertex ecPos - eye coordinate position light - light structure */ "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 / ( uFalloff[0] + ( uFalloff[1] * d ) + ( uFalloff[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, uShininess );" + " }" +
" spec += uSpecular * powerFactor * attenuation;" + " col += light.color * nDotVP * attenuation;" + "}" +
/* col - accumulated color spec - accumulated specular highlight vertNormal - Normal of the vertex ecPos - eye coordinate position light - light structure */ "void SpotLight( inout vec3 col, inout vec3 spec, in vec3 vertNormal, in vec3 ecPos, in Light light ) {" + " float spotAttenuation;" + " float powerFactor = 0.0;" +
// 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 / ( uFalloff[0] + ( uFalloff[1] * d ) + ( uFalloff[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 = pow( nDotHV, uShininess );" + " }" +
" spec += uSpecular * powerFactor * attenuation;" + " col += light.color * nDotVP * attenuation;" + "}" +
"void main(void) {" + " vec3 finalAmbient = vec3( 0.0 );" + " vec3 finalDiffuse = vec3( 0.0 );" + " vec3 finalSpecular = vec3( 0.0 );" +
" vec4 col = uColor;" +
" if ( uColor[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( uNormalTransform * vec4( aNormal, 0.0 ) ));" +
" vec4 ecPos4 = uView * uModel * vec4(aVertex, 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( uLightCount == 0 ) {" + " vFrontColor = col + vec4(uMaterialSpecular, 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 actually on. This gives us a // significant performance increase with high vertex counts. " if( i >= uLightCount ){" + " 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( uUsingMat == false ) {" + " vFrontColor = vec4(" + " vec3( col ) * finalAmbient +" + " vec3( col ) * finalDiffuse +" + " vec3( col ) * finalSpecular," + " col[3] );" + " }" + " else{" + " vFrontColor = vec4( " + " uMaterialEmissive + " + " (vec3(col) * uMaterialAmbient * finalAmbient ) + " + " (vec3(col) * finalDiffuse) + " + " (uMaterialSpecular * finalSpecular), " + " col[3] );" + " }" + " }" +
" vTexture.xy = aTexture.xy;" + " gl_Position = uProjection * uView * uModel * vec4( aVertex, 1.0 );" + "}";
var fragmentShaderSrc3D = "#ifdef GL_ES\n" + "precision highp float;\n" + "#endif\n" +
"varying vec4 vFrontColor;" +
"uniform sampler2D uSampler;" + "uniform bool uUsingTexture;" + "varying vec2 vTexture;" +
// In Processing, when a texture is used, the fill color is ignored // vec4(1.0,1.0,1.0,0.5) "void main(void){" + " if( uUsingTexture ){" + " gl_FragColor = vec4(texture2D(uSampler, vTexture.xy)) * vFrontColor;" + " }"+ " else{" + " gl_FragColor = vFrontColor;" + " }" + "}";
//////////////////////////////////////////////////////////////////////////// // 3D Functions ////////////////////////////////////////////////////////////////////////////
/* * Sets a uniform variable in a program object to a particular * value. Before calling this function, ensure the correct * program object has been installed as part of the current * rendering state by calling useProgram. * * On some systems, if the variable exists in the shader but isn't used, * the compiler will optimize it out and this function will fail. * * @param {String} cacheId * @param {WebGLProgram} programObj program object returned from * createProgramObject * @param {String} varName the name of the variable in the shader * @param {float | Array} varValue either a scalar value or an Array * * @returns none * * @see uniformi * @see uniformMatrix */ function uniformf(cacheId, programObj, varName, varValue) { var varLocation = curContextCache.locations[cacheId]; if(varLocation === undef) { varLocation = curContext.getUniformLocation(programObj, varName); curContextCache.locations[cacheId] = varLocation; } // the variable won't be found if it was optimized out. if (varLocation !== null) { if (varValue.length === 4) { curContext.uniform4fv(varLocation, varValue); } else if (varValue.length === 3) { curContext.uniform3fv(varLocation, varValue); } else if (varValue.length === 2) { curContext.uniform2fv(varLocation, varValue); } else { curContext.uniform1f(varLocation, varValue); } } }
/** * Sets a uniform int or int array in a program object to a particular * value. Before calling this function, ensure the correct * program object has been installed as part of the current * rendering state. * * On some systems, if the variable exists in the shader but isn't used, * the compiler will optimize it out and this function will fail. * * @param {String} cacheId * @param {WebGLProgram} programObj program object returned from * createProgramObject * @param {String} varName the name of the variable in the shader * @param {int | Array} varValue either a scalar value or an Array * * @returns none * * @see uniformf * @see uniformMatrix */ function uniformi(cacheId, programObj, varName, varValue) { var varLocation = curContextCache.locations[cacheId]; if(varLocation === undef) { varLocation = curContext.getUniformLocation(programObj, varName); curContextCache.locations[cacheId] = varLocation; } // the variable won't be found if it was optimized out. if (varLocation !== null) { if (varValue.length === 4) { curContext.uniform4iv(varLocation, varValue); } else if (varValue.length === 3) { curContext.uniform3iv(varLocation, varValue); } else if (varValue.length === 2) { curContext.uniform2iv(varLocation, varValue); } else { curContext.uniform1i(varLocation, varValue); } } }
/** * Sets the value of a uniform matrix variable in a program * object. Before calling this function, ensure the correct * program object has been installed as part of the current * rendering state. * * On some systems, if the variable exists in the shader but * isn't used, the compiler will optimize it out and this * function will fail. * * @param {String} cacheId * @param {WebGLProgram} programObj program object returned from * createProgramObject * @param {String} varName the name of the variable in the shader * @param {boolean} transpose must be false * @param {Array} matrix an array of 4, 9 or 16 values * * @returns none * * @see uniformi * @see uniformf */ function uniformMatrix(cacheId, programObj, varName, transpose, matrix) { var varLocation = curContextCache.locations[cacheId]; if(varLocation === undef) { varLocation = curContext.getUniformLocation(programObj, varName); curContextCache.locations[cacheId] = varLocation; } // The variable won't be found if it was optimized out. if (varLocation !== -1) { if (matrix.length === 16) { curContext.uniformMatrix4fv(varLocation, transpose, matrix); } else if (matrix.length === 9) { curContext.uniformMatrix3fv(varLocation, transpose, matrix); } else { curContext.uniformMatrix2fv(varLocation, transpose, matrix); } } }
/** * Binds the VBO, sets the vertex attribute data for the program * object and enables the attribute. * * On some systems, if the attribute exists in the shader but * isn't used, the compiler will optimize it out and this * function will fail. * * @param {String} cacheId * @param {WebGLProgram} programObj program object returned from * createProgramObject * @param {String} varName the name of the variable in the shader * @param {int} size the number of components per vertex attribute * @param {WebGLBuffer} VBO Vertex Buffer Object * * @returns none * * @see disableVertexAttribPointer */ function vertexAttribPointer(cacheId, programObj, varName, size, VBO) { var varLocation = curContextCache.attributes[cacheId]; if(varLocation === undef) { varLocation = curContext.getAttribLocation(programObj, varName); curContextCache.attributes[cacheId] = varLocation; } if (varLocation !== -1) { curContext.bindBuffer(curContext.ARRAY_BUFFER, VBO); curContext.vertexAttribPointer(varLocation, size, curContext.FLOAT, false, 0, 0); curContext.enableVertexAttribArray(varLocation); } }
/** * Disables a program object attribute from being sent to WebGL. * * @param {String} cacheId * @param {WebGLProgram} programObj program object returned from * createProgramObject * @param {String} varName name of the attribute * * @returns none * * @see vertexAttribPointer */ function disableVertexAttribPointer(cacheId, programObj, varName){ var varLocation = curContextCache.attributes[cacheId]; if(varLocation === undef) { varLocation = curContext.getAttribLocation(programObj, varName); curContextCache.attributes[cacheId] = varLocation; } if (varLocation !== -1) { curContext.disableVertexAttribArray(varLocation); } }
/** * Creates a WebGL program object. * * @param {String} vetexShaderSource * @param {String} fragmentShaderSource * * @returns {WebGLProgram} A program object */ var createProgramObject = function(curContext, vetexShaderSource, fragmentShaderSource) { var vertexShaderObject = curContext.createShader(curContext.VERTEX_SHADER); curContext.shaderSource(vertexShaderObject, vetexShaderSource); curContext.compileShader(vertexShaderObject); if (!curContext.getShaderParameter(vertexShaderObject, curContext.COMPILE_STATUS)) { throw curContext.getShaderInfoLog(vertexShaderObject); }
var fragmentShaderObject = curContext.createShader(curContext.FRAGMENT_SHADER); curContext.shaderSource(fragmentShaderObject, fragmentShaderSource); curContext.compileShader(fragmentShaderObject); if (!curContext.getShaderParameter(fragmentShaderObject, curContext.COMPILE_STATUS)) { throw curContext.getShaderInfoLog(fragmentShaderObject); }
var programObject = curContext.createProgram(); curContext.attachShader(programObject, vertexShaderObject); curContext.attachShader(programObject, fragmentShaderObject); curContext.linkProgram(programObject); if (!curContext.getProgramParameter(programObject, curContext.LINK_STATUS)) { throw "Error linking shaders."; }
return programObject; };
//////////////////////////////////////////////////////////////////////////// // 2D/3D drawing handling //////////////////////////////////////////////////////////////////////////// var imageModeCorner = function(x, y, w, h, whAreSizes) { return { x: x, y: y, w: w, h: h }; }; var imageModeConvert = imageModeCorner;
var imageModeCorners = function(x, y, w, h, whAreSizes) { return { x: x, y: y, w: whAreSizes ? w : w - x, h: whAreSizes ? h : h - y }; };
var imageModeCenter = function(x, y, w, h, whAreSizes) { return { x: x - w / 2, y: y - h / 2, w: w, h: h }; };
// Objects for shared, 2D and 3D contexts var DrawingShared = function(){}; var Drawing2D = function(){}; var Drawing3D = function(){}; var DrawingPre = function(){};
// Setup the prototype chain Drawing2D.prototype = new DrawingShared(); Drawing2D.prototype.constructor = Drawing2D; Drawing3D.prototype = new DrawingShared(); Drawing3D.prototype.constructor = Drawing3D; DrawingPre.prototype = new DrawingShared(); DrawingPre.prototype.constructor = DrawingPre;
// A no-op function for when the user calls 3D functions from a 2D sketch // We can change this to a throw or console.error() later if we want DrawingShared.prototype.a3DOnlyFunction = noop;
/** * The shape() function displays shapes to the screen. * Processing currently works with SVG shapes only. * The shape parameter specifies the shape to display and the x * and y parameters define the location of the shape from its * upper-left corner. * The shape is displayed at its original size unless the width * and height parameters specify a different size. * The shapeMode() function changes the way the parameters work. * A call to shapeMode(CORNERS), for example, will change the width * and height parameters to define the x and y values of the opposite corner * of the shape. *
* Note complex shapes may draw awkwardly with P2D, P3D, and OPENGL. Those * renderers do not yet support shapes that have holes or complicated breaks. * * @param {PShape} shape the shape to display * @param {int|float} x x-coordinate of the shape * @param {int|float} y y-coordinate of the shape * @param {int|float} width width to display the shape * @param {int|float} height height to display the shape * * @see PShape * @see loadShape() * @see shapeMode() */ p.shape = function(shape, x, y, width, height) { if (arguments.length >= 1 && arguments[0] !== null) { if (shape.isVisible()) { p.pushMatrix(); if (curShapeMode === PConstants.CENTER) { if (arguments.length === 5) { p.translate(x - width/2, y - height/2); p.scale(width / shape.getWidth(), height / shape.getHeight()); } else if (arguments.length === 3) { p.translate(x - shape.getWidth()/2, - shape.getHeight()/2); } else { p.translate(-shape.getWidth()/2, -shape.getHeight()/2); } } else if (curShapeMode === PConstants.CORNER) { if (arguments.length === 5) { p.translate(x, y); p.scale(width / shape.getWidth(), height / shape.getHeight()); } else if (arguments.length === 3) { p.translate(x, y); } } else if (curShapeMode === PConstants.CORNERS) { if (arguments.length === 5) { width -= x; height -= y; p.translate(x, y); p.scale(width / shape.getWidth(), height / shape.getHeight()); } else if (arguments.length === 3) { p.translate(x, y); } } shape.draw(p); if ((arguments.length === 1 && curShapeMode === PConstants.CENTER ) || arguments.length > 1) { p.popMatrix(); } } } };
/** * The shapeMode() function modifies the location from which shapes draw. * The default mode is shapeMode(CORNER), which specifies the * location to be the upper left corner of the shape and uses the third * and fourth parameters of shape() to specify the width and height. * The syntax shapeMode(CORNERS) uses the first and second parameters * of shape() to set the location of one corner and uses the third * and fourth parameters to set the opposite corner. * The syntax shapeMode(CENTER) draws the shape from its center point * and uses the third and forth parameters of shape() to specify the * width and height. * The parameter must be written in "ALL CAPS" because Processing syntax * is case sensitive. * * @param {int} mode One of CORNER, CORNERS, CENTER * * @see shape() * @see rectMode() */ p.shapeMode = function (mode) { curShapeMode = mode; };
/** * The loadShape() function loads vector shapes into a variable of type PShape. Currently, only SVG files may be loaded. * In most cases, loadShape() should be used inside setup() because loading shapes inside draw() will reduce the speed of a sketch. * * @param {String} filename an SVG file * * @return {PShape} a object of type PShape or null * @see PShape * @see PApplet#shape() * @see PApplet#shapeMode() */ p.loadShape = function (filename) { if (arguments.length === 1) { if (filename.indexOf(".svg") > -1) { return new PShapeSVG(null, filename); } } return null; };
/** * Processing 2.0 function for loading XML files. * * @param {String} uri The uri for the xml file to load. * * @return {XML} An XML object representing the xml data. */ p.loadXML = function(uri) { return new XML(p, uri); };
//////////////////////////////////////////////////////////////////////////// // 2D Matrix ////////////////////////////////////////////////////////////////////////////
/** * Helper function for printMatrix(). Finds the largest scalar * in the matrix, then number of digits left of the decimal. * Call from PMatrix2D and PMatrix3D's print() function. */ var printMatrixHelper = function(elements) { var big = 0; for (var i = 0; i < elements.length; i++) { if (i !== 0) { big = Math.max(big, Math.abs(elements[i])); } else { big = Math.abs(elements[i]); } }
var digits = (big + "").indexOf("."); if (digits === 0) { digits = 1; } else if (digits === -1) { digits = (big + "").length; }
return digits; }; /** * PMatrix2D is a 3x2 affine matrix implementation. The constructor accepts another PMatrix2D or a list of six float elements. * If no parameters are provided the matrix is set to the identity matrix. * * @param {PMatrix2D} matrix the initial matrix to set to * @param {float} m00 the first element of the matrix * @param {float} m01 the second element of the matrix * @param {float} m02 the third element of the matrix * @param {float} m10 the fourth element of the matrix * @param {float} m11 the fifth element of the matrix * @param {float} m12 the sixth element of the matrix */ var PMatrix2D = p.PMatrix2D = function() { if (arguments.length === 0) { this.reset(); } else if (arguments.length === 1 && arguments[0] instanceof PMatrix2D) { this.set(arguments[0].array()); } else if (arguments.length === 6) { this.set(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]); } }; /** * PMatrix2D methods */ PMatrix2D.prototype = { /** * @member PMatrix2D * The set() function sets the matrix elements. The function accepts either another PMatrix2D, an array of elements, or a list of six floats. * * @param {PMatrix2D} matrix the matrix to set this matrix to * @param {float[]} elements an array of elements to set this matrix to * @param {float} m00 the first element of the matrix * @param {float} m01 the third element of the matrix * @param {float} m10 the fourth element of the matrix * @param {float} m11 the fith element of the matrix * @param {float} m12 the sixth element of the matrix */ set: function() { if (arguments.length === 6) { var a = arguments; this.set([a[0], a[1], a[2], a[3], a[4], a[5]]); } else if (arguments.length === 1 && arguments[0] instanceof PMatrix2D) { this.elements = arguments[0].array(); } else if (arguments.length === 1 && arguments[0] instanceof Array) { this.elements = arguments[0].slice(); } }, /** * @member PMatrix2D * The get() function returns a copy of this PMatrix2D. * * @return {PMatrix2D} a copy of this PMatrix2D */ get: function() { var outgoing = new PMatrix2D(); outgoing.set(this.elements); return outgoing; }, /** * @member PMatrix2D * The reset() function sets this PMatrix2D to the identity matrix. */ reset: function() { this.set([1, 0, 0, 0, 1, 0]); }, /** * @member PMatrix2D * The array() function returns a copy of the element values. * @addon * * @return {float[]} returns a copy of the element values */ array: function array() { return this.elements.slice(); }, /** * @member PMatrix2D * The translate() function translates this matrix by moving the current coordinates to the location specified by tx and ty. * * @param {float} tx the x-axis coordinate to move to * @param {float} ty the y-axis coordinate to move to */ translate: function(tx, ty) { this.elements[2] = tx * this.elements[0] + ty * this.elements[1] + this.elements[2]; this.elements[5] = tx * this.elements[3] + ty * this.elements[4] + this.elements[5]; }, /** * @member PMatrix2D * The invTranslate() function translates this matrix by moving the current coordinates to the negative location specified by tx and ty. * * @param {float} tx the x-axis coordinate to move to * @param {float} ty the y-axis coordinate to move to */ invTranslate: function(tx, ty) { this.translate(-tx, -ty); }, /** * @member PMatrix2D * The transpose() function is not used in processingjs. */ transpose: function() { // Does nothing in Processing. }, /** * @member PMatrix2D * The mult() function multiplied this matrix. * If two array elements are passed in the function will multiply a two element vector against this matrix. * If target is null or not length four, a new float array will be returned. * The values for vec and target can be the same (though that's less efficient). * If two PVectors are passed in the function multiply the x and y coordinates of a PVector against this matrix. * * @param {PVector} source, target the PVectors used to multiply this matrix * @param {float[]} source, target the arrays used to multiply this matrix * * @return {PVector|float[]} returns a PVector or an array representing the new matrix */ mult: function(source, target) { var x, y; if (source instanceof PVector) { x = source.x; y = source.y; if (!target) { target = new PVector(); } } else if (source instanceof Array) { x = source[0]; y = source[1]; if (!target) { target = []; } } if (target instanceof Array) { target[0] = this.elements[0] * x + this.elements[1] * y + this.elements[2]; target[1] = this.elements[3] * x + this.elements[4] * y + this.elements[5]; } else if (target instanceof PVector) { target.x = this.elements[0] * x + this.elements[1] * y + this.elements[2]; target.y = this.elements[3] * x + this.elements[4] * y + this.elements[5]; target.z = 0; } return target; }, /** * @member PMatrix2D * The multX() function calculates the x component of a vector from a transformation. * * @param {float} x the x component of the vector being transformed * @param {float} y the y component of the vector being transformed * * @return {float} returnes the result of the calculation */ multX: function(x, y) { return (x * this.elements[0] + y * this.elements[1] + this.elements[2]); }, /** * @member PMatrix2D * The multY() function calculates the y component of a vector from a transformation. * * @param {float} x the x component of the vector being transformed * @param {float} y the y component of the vector being transformed * * @return {float} returnes the result of the calculation */ multY: function(x, y) { return (x * this.elements[3] + y * this.elements[4] + this.elements[5]); }, /** * @member PMatrix2D * The skewX() function skews the matrix along the x-axis the amount specified by the angle parameter. * Angles should be specified in radians (values from 0 to PI*2) or converted to radians with the radians() function. * * @param {float} angle angle of skew specified in radians */ skewX: function(angle) { this.apply(1, 0, 1, angle, 0, 0); }, /** * @member PMatrix2D * The skewY() function skews the matrix along the y-axis the amount specified by the angle parameter. * Angles should be specified in radians (values from 0 to PI*2) or converted to radians with the radians() function. * * @param {float} angle angle of skew specified in radians */ skewY: function(angle) { this.apply(1, 0, 1, 0, angle, 0); }, /** * @member PMatrix2D * The shearX() function shears the matrix along the x-axis the amount specified by the angle parameter. * Angles should be specified in radians (values from 0 to PI*2) or converted to radians with the radians() function. * * @param {float} angle angle of skew specified in radians */ shearX: function(angle) { this.apply(1, 0, 1, Math.tan(angle) , 0, 0); }, /** * @member PMatrix2D * The shearY() function shears the matrix along the y-axis the amount specified by the angle parameter. * Angles should be specified in radians (values from 0 to PI*2) or converted to radians with the radians() function. * * @param {float} angle angle of skew specified in radians */ shearY: function(angle) { this.apply(1, 0, 1, 0, Math.tan(angle), 0); }, /** * @member PMatrix2D * The determinant() function calvculates the determinant of this matrix. * * @return {float} the determinant of the matrix */ determinant: function() { return (this.elements[0] * this.elements[4] - this.elements[1] * this.elements[3]); }, /** * @member PMatrix2D * The invert() function inverts this matrix * * @return {boolean} true if successful */ invert: function() { var d = this.determinant(); if (Math.abs( d ) > PConstants.MIN_INT) { var old00 = this.elements[0]; var old01 = this.elements[1]; var old02 = this.elements[2]; var old10 = this.elements[3]; var old11 = this.elements[4]; var old12 = this.elements[5]; this.elements[0] = old11 / d; this.elements[3] = -old10 / d; this.elements[1] = -old01 / d; this.elements[4] = old00 / d; this.elements[2] = (old01 * old12 - old11 * old02) / d; this.elements[5] = (old10 * old02 - old00 * old12) / d; return true; } return false; }, /** * @member PMatrix2D * The scale() function increases or decreases the size of a shape by expanding and contracting vertices. When only one parameter is specified scale will occur in all dimensions. * This is equivalent to a two parameter call. * * @param {float} sx the amount to scale on the x-axis * @param {float} sy the amount to scale on the y-axis */ scale: function(sx, sy) { if (sx && !sy) { sy = sx; } if (sx && sy) { this.elements[0] *= sx; this.elements[1] *= sy; this.elements[3] *= sx; this.elements[4] *= sy; } }, /** * @member PMatrix2D * The invScale() function decreases or increases the size of a shape by contracting and expanding vertices. When only one parameter is specified scale will occur in all dimensions. * This is equivalent to a two parameter call. * * @param {float} sx the amount to scale on the x-axis * @param {float} sy the amount to scale on the y-axis */ invScale: function(sx, sy) { if (sx && !sy) { sy = sx; } this.scale(1 / sx, 1 / sy); }, /** * @member PMatrix2D * The apply() function multiplies the current matrix by the one specified through the parameters. Note that either a PMatrix2D or a list of floats can be passed in. * * @param {PMatrix2D} matrix the matrix to apply this matrix to * @param {float} m00 the first element of the matrix * @param {float} m01 the third element of the matrix * @param {float} m10 the fourth element of the matrix * @param {float} m11 the fith element of the matrix * @param {float} m12 the sixth element of the matrix */ apply: function() { var source; if (arguments.length === 1 && arguments[0] instanceof PMatrix2D) { source = arguments[0].array(); } else if (arguments.length === 6) { source = Array.prototype.slice.call(arguments); } else if (arguments.length === 1 && arguments[0] instanceof Array) { source = arguments[0]; }
var result = [0, 0, this.elements[2], 0, 0, this.elements[5]]; var e = 0; for (var row = 0; row < 2; row++) { for (var col = 0; col < 3; col++, e++) { result[e] += this.elements[row * 3 + 0] * source[col + 0] + this.elements[row * 3 + 1] * source[col + 3]; } } this.elements = result.slice(); }, /** * @member PMatrix2D * The preApply() function applies another matrix to the left of this one. Note that either a PMatrix2D or elements of a matrix can be passed in. * * @param {PMatrix2D} matrix the matrix to apply this matrix to * @param {float} m00 the first element of the matrix * @param {float} m01 the third element of the matrix * @param {float} m10 the fourth element of the matrix * @param {float} m11 the fith element of the matrix * @param {float} m12 the sixth element of the matrix */ preApply: function() { var source; if (arguments.length === 1 && arguments[0] instanceof PMatrix2D) { source = arguments[0].array(); } else if (arguments.length === 6) { source = Array.prototype.slice.call(arguments); } else if (arguments.length === 1 && arguments[0] instanceof Array) { source = arguments[0]; } var result = [0, 0, source[2], 0, 0, source[5]]; result[2] = source[2] + this.elements[2] * source[0] + this.elements[5] * source[1]; result[5] = source[5] + this.elements[2] * source[3] + this.elements[5] * source[4]; result[0] = this.elements[0] * source[0] + this.elements[3] * source[1]; result[3] = this.elements[0] * source[3] + this.elements[3] * source[4]; result[1] = this.elements[1] * source[0] + this.elements[4] * source[1]; result[4] = this.elements[1] * source[3] + this.elements[4] * source[4]; this.elements = result.slice(); }, /** * @member PMatrix2D * The rotate() function rotates the matrix. * * @param {float} angle the angle of rotation in radiants */ rotate: function(angle) { var c = Math.cos(angle); var s = Math.sin(angle); var temp1 = this.elements[0]; var temp2 = this.elements[1]; this.elements[0] = c * temp1 + s * temp2; this.elements[1] = -s * temp1 + c * temp2; temp1 = this.elements[3]; temp2 = this.elements[4]; this.elements[3] = c * temp1 + s * temp2; this.elements[4] = -s * temp1 + c * temp2; }, /** * @member PMatrix2D * The rotateZ() function rotates the matrix. * * @param {float} angle the angle of rotation in radiants */ rotateZ: function(angle) { this.rotate(angle); }, /** * @member PMatrix2D * The invRotateZ() function rotates the matrix in opposite direction. * * @param {float} angle the angle of rotation in radiants */ invRotateZ: function(angle) { this.rotateZ(angle - Math.PI); }, /** * @member PMatrix2D * The print() function prints out the elements of this matrix */ print: function() { var digits = printMatrixHelper(this.elements); var output = "" + p.nfs(this.elements[0], digits, 4) + " " + p.nfs(this.elements[1], digits, 4) + " " + p.nfs(this.elements[2], digits, 4) + "\n" + p.nfs(this.elements[3], digits, 4) + " " + p.nfs(this.elements[4], digits, 4) + " " + p.nfs(this.elements[5], digits, 4) + "\n\n"; p.println(output); } };
/** * PMatrix3D is a 4x4 matrix implementation. The constructor accepts another PMatrix3D or a list of six or sixteen float elements. * If no parameters are provided the matrix is set to the identity matrix. */ var PMatrix3D = p.PMatrix3D = function() { // When a matrix is created, it is set to an identity matrix this.reset(); }; /** * PMatrix3D methods */ PMatrix3D.prototype = { /** * @member PMatrix2D * The set() function sets the matrix elements. The function accepts either another PMatrix3D, an array of elements, or a list of six or sixteen floats. * * @param {PMatrix3D} matrix the initial matrix to set to * @param {float[]} elements an array of elements to set this matrix to * @param {float} m00 the first element of the matrix * @param {float} m01 the second element of the matrix * @param {float} m02 the third element of the matrix * @param {float} m03 the fourth element of the matrix * @param {float} m10 the fifth element of the matrix * @param {float} m11 the sixth element of the matrix * @param {float} m12 the seventh element of the matrix * @param {float} m13 the eight element of the matrix * @param {float} m20 the nineth element of the matrix * @param {float} m21 the tenth element of the matrix * @param {float} m22 the eleventh element of the matrix * @param {float} m23 the twelveth element of the matrix * @param {float} m30 the thirteenth element of the matrix * @param {float} m31 the fourtheenth element of the matrix * @param {float} m32 the fivetheenth element of the matrix * @param {float} m33 the sixteenth element of the matrix */ set: function() { if (arguments.length === 16) { this.elements = Array.prototype.slice.call(arguments); } else if (arguments.length === 1 && arguments[0] instanceof PMatrix3D) { this.elements = arguments[0].array(); } else if (arguments.length === 1 && arguments[0] instanceof Array) { this.elements = arguments[0].slice(); } }, /** * @member PMatrix3D * The get() function returns a copy of this PMatrix3D. * * @return {PMatrix3D} a copy of this PMatrix3D */ get: function() { var outgoing = new PMatrix3D(); outgoing.set(this.elements); return outgoing; }, /** * @member PMatrix3D * The reset() function sets this PMatrix3D to the identity matrix. */ reset: function() { this.elements = [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]; }, /** * @member PMatrix3D * The array() function returns a copy of the element values. * @addon * * @return {float[]} returns a copy of the element values */ array: function array() { return this.elements.slice(); }, /** * @member PMatrix3D * The translate() function translates this matrix by moving the current coordinates to the location specified by tx, ty, and tz. * * @param {float} tx the x-axis coordinate to move to * @param {float} ty the y-axis coordinate to move to * @param {float} tz the z-axis coordinate to move to */ translate: function(tx, ty, tz) { if (tz === undef) { tz = 0; }
this.elements[3] += tx * this.elements[0] + ty * this.elements[1] + tz * this.elements[2]; this.elements[7] += tx * this.elements[4] + ty * this.elements[5] + tz * this.elements[6]; this.elements[11] += tx * this.elements[8] + ty * this.elements[9] + tz * this.elements[10]; this.elements[15] += tx * this.elements[12] + ty * this.elements[13] + tz * this.elements[14]; }, /** * @member PMatrix3D * The transpose() function transpose this matrix. */ transpose: function() { var temp = this.elements[4]; this.elements[4] = this.elements[1]; this.elements[1] = temp;
temp = this.elements[8]; this.elements[8] = this.elements[2]; this.elements[2] = temp;
temp = this.elements[6]; this.elements[6] = this.elements[9]; this.elements[9] = temp;
temp = this.elements[3]; this.elements[3] = this.elements[12]; this.elements[12] = temp;
temp = this.elements[7]; this.elements[7] = this.elements[13]; this.elements[13] = temp;
temp = this.elements[11]; this.elements[11] = this.elements[14]; this.elements[14] = temp; }, /** * @member PMatrix3D * The mult() function multiplied this matrix. * If two array elements are passed in the function will multiply a two element vector against this matrix. * If target is null or not length four, a new float array will be returned. * The values for vec and target can be the same (though that's less efficient). * If two PVectors are passed in the function multiply the x and y coordinates of a PVector against this matrix. * * @param {PVector} source, target the PVectors used to multiply this matrix * @param {float[]} source, target the arrays used to multiply this matrix * * @return {PVector|float[]} returns a PVector or an array representing the new matrix */ mult: function(source, target) { var x, y, z, w; if (source instanceof PVector) { x = source.x; y = source.y; z = source.z; w = 1; if (!target) { target = new PVector(); } } else if (source instanceof Array) { x = source[0]; y = source[1]; z = source[2]; w = source[3] || 1;
if ( !target || (target.length !== 3 && target.length !== 4) ) { target = [0, 0, 0]; } }
if (target instanceof Array) { if (target.length === 3) { target[0] = this.elements[0] * x + this.elements[1] * y + this.elements[2] * z + this.elements[3]; target[1] = this.elements[4] * x + this.elements[5] * y + this.elements[6] * z + this.elements[7]; target[2] = this.elements[8] * x + this.elements[9] * y + this.elements[10] * z + this.elements[11]; } else if (target.length === 4) { target[0] = this.elements[0] * x + this.elements[1] * y + this.elements[2] * z + this.elements[3] * w; target[1] = this.elements[4] * x + this.elements[5] * y + this.elements[6] * z + this.elements[7] * w; target[2] = this.elements[8] * x + this.elements[9] * y + this.elements[10] * z + this.elements[11] * w; target[3] = this.elements[12] * x + this.elements[13] * y + this.elements[14] * z + this.elements[15] * w; } } if (target instanceof PVector) { target.x = this.elements[0] * x + this.elements[1] * y + this.elements[2] * z + this.elements[3]; target.y = this.elements[4] * x + this.elements[5] * y + this.elements[6] * z + this.elements[7]; target.z = this.elements[8] * x + this.elements[9] * y + this.elements[10] * z + this.elements[11]; } return target; }, /** * @member PMatrix3D * The preApply() function applies another matrix to the left of this one. Note that either a PMatrix3D or elements of a matrix can be passed in. * * @param {PMatrix3D} matrix the matrix to apply this matrix to * @param {float} m00 the first element of the matrix * @param {float} m01 the second element of the matrix * @param {float} m02 the third element of the matrix * @param {float} m03 the fourth element of the matrix * @param {float} m10 the fifth element of the matrix * @param {float} m11 the sixth element of the matrix * @param {float} m12 the seventh element of the matrix * @param {float} m13 the eight element of the matrix * @param {float} m20 the nineth element of the matrix * @param {float} m21 the tenth element of the matrix * @param {float} m22 the eleventh element of the matrix * @param {float} m23 the twelveth element of the matrix * @param {float} m30 the thirteenth element of the matrix * @param {float} m31 the fourtheenth element of the matrix * @param {float} m32 the fivetheenth element of the matrix * @param {float} m33 the sixteenth element of the matrix */ preApply: function() { var source; if (arguments.length === 1 && arguments[0] instanceof PMatrix3D) { source = arguments[0].array(); } else if (arguments.length === 16) { source = Array.prototype.slice.call(arguments); } else if (arguments.length === 1 && arguments[0] instanceof Array) { source = arguments[0]; }
var result = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var e = 0; for (var row = 0; row < 4; row++) { for (var col = 0; col < 4; col++, e++) { result[e] += this.elements[col + 0] * source[row * 4 + 0] + this.elements[col + 4] * source[row * 4 + 1] + this.elements[col + 8] * source[row * 4 + 2] + this.elements[col + 12] * source[row * 4 + 3]; } } this.elements = result.slice(); }, /** * @member PMatrix3D * The apply() function multiplies the current matrix by the one specified through the parameters. Note that either a PMatrix3D or a list of floats can be passed in. * * @param {PMatrix3D} matrix the matrix to apply this matrix to * @param {float} m00 the first element of the matrix * @param {float} m01 the second element of the matrix * @param {float} m02 the third element of the matrix * @param {float} m03 the fourth element of the matrix * @param {float} m10 the fifth element of the matrix * @param {float} m11 the sixth element of the matrix * @param {float} m12 the seventh element of the matrix * @param {float} m13 the eight element of the matrix * @param {float} m20 the nineth element of the matrix * @param {float} m21 the tenth element of the matrix * @param {float} m22 the eleventh element of the matrix * @param {float} m23 the twelveth element of the matrix * @param {float} m30 the thirteenth element of the matrix * @param {float} m31 the fourtheenth element of the matrix * @param {float} m32 the fivetheenth element of the matrix * @param {float} m33 the sixteenth element of the matrix */ apply: function() { var source; if (arguments.length === 1 && arguments[0] instanceof PMatrix3D) { source = arguments[0].array(); } else if (arguments.length === 16) { source = Array.prototype.slice.call(arguments); } else if (arguments.length === 1 && arguments[0] instanceof Array) { source = arguments[0]; }
var result = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var e = 0; for (var row = 0; row < 4; row++) { for (var col = 0; col < 4; col++, e++) { result[e] += this.elements[row * 4 + 0] * source[col + 0] + this.elements[row * 4 + 1] * source[col + 4] + this.elements[row * 4 + 2] * source[col + 8] + this.elements[row * 4 + 3] * source[col + 12]; } } this.elements = result.slice(); }, /** * @member PMatrix3D * The rotate() function rotates the matrix. * * @param {float} angle the angle of rotation in radiants */ rotate: function(angle, v0, v1, v2) { if (arguments.length < 4) { this.rotateZ(angle); } else { var v = new PVector(v0, v1, v2); var m = v.mag(); if (m === 0) { return; } else if (m != 1) { v.normalize(); v0 = v.x; v1 = v.y; v2 = v.z; } var c = p.cos(angle); var s = p.sin(angle); var t = 1.0 - c;
this.apply((t * v0 * v0) + c, (t * v0 * v1) - (s * v2), (t * v0 * v2) + (s * v1), 0, (t * v0 * v1) + (s * v2), (t * v1 * v1) + c, (t * v1 * v2) - (s * v0), 0, (t * v0 * v2) - (s * v1), (t * v1 * v2) + (s * v0), (t * v2 * v2) + c, 0, 0, 0, 0, 1); } }, /** * @member PMatrix3D * The invApply() function applies the inverted matrix to this matrix. * * @param {float} m00 the first element of the matrix * @param {float} m01 the second element of the matrix * @param {float} m02 the third element of the matrix * @param {float} m03 the fourth element of the matrix * @param {float} m10 the fifth element of the matrix * @param {float} m11 the sixth element of the matrix * @param {float} m12 the seventh element of the matrix * @param {float} m13 the eight element of the matrix * @param {float} m20 the nineth element of the matrix * @param {float} m21 the tenth element of the matrix * @param {float} m22 the eleventh element of the matrix * @param {float} m23 the twelveth element of the matrix * @param {float} m30 the thirteenth element of the matrix * @param {float} m31 the fourtheenth element of the matrix * @param {float} m32 the fivetheenth element of the matrix * @param {float} m33 the sixteenth element of the matrix * * @return {boolean} returns true if the operation was successful. */ invApply: function() { if (inverseCopy === undef) { inverseCopy = new PMatrix3D(); } var a = arguments; inverseCopy.set(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15]);
if (!inverseCopy.invert()) { return false; } this.preApply(inverseCopy); return true; }, /** * @member PMatrix3D * The rotateZ() function rotates the matrix. * * @param {float} angle the angle of rotation in radiants */ rotateX: function(angle) { var c = p.cos(angle); var s = p.sin(angle); this.apply([1, 0, 0, 0, 0, c, -s, 0, 0, s, c, 0, 0, 0, 0, 1]); }, /** * @member PMatrix3D * The rotateY() function rotates the matrix. * * @param {float} angle the angle of rotation in radiants */ rotateY: function(angle) { var c = p.cos(angle); var s = p.sin(angle); this.apply([c, 0, s, 0, 0, 1, 0, 0, -s, 0, c, 0, 0, 0, 0, 1]); }, /** * @member PMatrix3D * The rotateZ() function rotates the matrix. * * @param {float} angle the angle of rotation in radiants */ rotateZ: function(angle) { var c = Math.cos(angle); var s = Math.sin(angle); this.apply([c, -s, 0, 0, s, c, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]); }, /** * @member PMatrix3D * The scale() function increases or decreases the size of a matrix by expanding and contracting vertices. When only one parameter is specified scale will occur in all dimensions. * This is equivalent to a three parameter call. * * @param {float} sx the amount to scale on the x-axis * @param {float} sy the amount to scale on the y-axis * @param {float} sz the amount to scale on the z-axis */ scale: function(sx, sy, sz) { if (sx && !sy && !sz) { sy = sz = sx; } else if (sx && sy && !sz) { sz = 1; }
if (sx && sy && sz) { this.elements[0] *= sx; this.elements[1] *= sy; this.elements[2] *= sz; this.elements[4] *= sx; this.elements[5] *= sy; this.elements[6] *= sz; this.elements[8] *= sx; this.elements[9] *= sy; this.elements[10] *= sz; this.elements[12] *= sx; this.elements[13] *= sy; this.elements[14] *= sz; } }, /** * @member PMatrix3D * The skewX() function skews the matrix along the x-axis the amount specified by the angle parameter. * Angles should be specified in radians (values from 0 to PI*2) or converted to radians with the radians() function. * * @param {float} angle angle of skew specified in radians */ skewX: function(angle) { var t = Math.tan(angle); this.apply(1, t, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); }, /** * @member PMatrix3D * The skewY() function skews the matrix along the y-axis the amount specified by the angle parameter. * Angles should be specified in radians (values from 0 to PI*2) or converted to radians with the radians() function. * * @param {float} angle angle of skew specified in radians */ skewY: function(angle) { var t = Math.tan(angle); this.apply(1, 0, 0, 0, t, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); }, /** * @member PMatrix3D * The shearX() function shears the matrix along the x-axis the amount specified by the angle parameter. * Angles should be specified in radians (values from 0 to PI*2) or converted to radians with the radians() function. * * @param {float} angle angle of shear specified in radians */ shearX: function(angle) { var t = Math.tan(angle); this.apply(1, t, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); }, /** * @member PMatrix3D * The shearY() function shears the matrix along the y-axis the amount specified by the angle parameter. * Angles should be specified in radians (values from 0 to PI*2) or converted to radians with the radians() function. * * @param {float} angle angle of shear specified in radians */ shearY: function(angle) { var t = Math.tan(angle); this.apply(1, 0, 0, 0, t, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); }, multX: function(x, y, z, w) { if (!z) { return this.elements[0] * x + this.elements[1] * y + this.elements[3]; } if (!w) { return this.elements[0] * x + this.elements[1] * y + this.elements[2] * z + this.elements[3]; } return this.elements[0] * x + this.elements[1] * y + this.elements[2] * z + this.elements[3] * w; }, multY: function(x, y, z, w) { if (!z) { return this.elements[4] * x + this.elements[5] * y + this.elements[7]; } if (!w) { return this.elements[4] * x + this.elements[5] * y + this.elements[6] * z + this.elements[7]; } return this.elements[4] * x + this.elements[5] * y + this.elements[6] * z + this.elements[7] * w; }, multZ: function(x, y, z, w) { if (!w) { return this.elements[8] * x + this.elements[9] * y + this.elements[10] * z + this.elements[11]; } return this.elements[8] * x + this.elements[9] * y + this.elements[10] * z + this.elements[11] * w; }, multW: function(x, y, z, w) { if (!w) { return this.elements[12] * x + this.elements[13] * y + this.elements[14] * z + this.elements[15]; } return this.elements[12] * x + this.elements[13] * y + this.elements[14] * z + this.elements[15] * w; }, /** * @member PMatrix3D * The invert() function inverts this matrix * * @return {boolean} true if successful */ invert: function() { var fA0 = this.elements[0] * this.elements[5] - this.elements[1] * this.elements[4]; var fA1 = this.elements[0] * this.elements[6] - this.elements[2] * this.elements[4]; var fA2 = this.elements[0] * this.elements[7] - this.elements[3] * this.elements[4]; var fA3 = this.elements[1] * this.elements[6] - this.elements[2] * this.elements[5]; var fA4 = this.elements[1] * this.elements[7] - this.elements[3] * this.elements[5]; var fA5 = this.elements[2] * this.elements[7] - this.elements[3] * this.elements[6]; var fB0 = this.elements[8] * this.elements[13] - this.elements[9] * this.elements[12]; var fB1 = this.elements[8] * this.elements[14] - this.elements[10] * this.elements[12]; var fB2 = this.elements[8] * this.elements[15] - this.elements[11] * this.elements[12]; var fB3 = this.elements[9] * this.elements[14] - this.elements[10] * this.elements[13]; var fB4 = this.elements[9] * this.elements[15] - this.elements[11] * this.elements[13]; var fB5 = this.elements[10] * this.elements[15] - this.elements[11] * this.elements[14];
// Determinant var fDet = fA0 * fB5 - fA1 * fB4 + fA2 * fB3 + fA3 * fB2 - fA4 * fB1 + fA5 * fB0;
// Account for a very small value // return false if not successful. if (Math.abs(fDet) <= 1e-9) { return false; }
var kInv = []; kInv[0] = +this.elements[5] * fB5 - this.elements[6] * fB4 + this.elements[7] * fB3; kInv[4] = -this.elements[4] * fB5 + this.elements[6] * fB2 - this.elements[7] * fB1; kInv[8] = +this.elements[4] * fB4 - this.elements[5] * fB2 + this.elements[7] * fB0; kInv[12] = -this.elements[4] * fB3 + this.elements[5] * fB1 - this.elements[6] * fB0; kInv[1] = -this.elements[1] * fB5 + this.elements[2] * fB4 - this.elements[3] * fB3; kInv[5] = +this.elements[0] * fB5 - this.elements[2] * fB2 + this.elements[3] * fB1; kInv[9] = -this.elements[0] * fB4 + this.elements[1] * fB2 - this.elements[3] * fB0; kInv[13] = +this.elements[0] * fB3 - this.elements[1] * fB1 + this.elements[2] * fB0; kInv[2] = +this.elements[13] * fA5 - this.elements[14] * fA4 + this.elements[15] * fA3; kInv[6] = -this.elements[12] * fA5 + this.elements[14] * fA2 - this.elements[15] * fA1; kInv[10] = +this.elements[12] * fA4 - this.elements[13] * fA2 + this.elements[15] * fA0; kInv[14] = -this.elements[12] * fA3 + this.elements[13] * fA1 - this.elements[14] * fA0; kInv[3] = -this.elements[9] * fA5 + this.elements[10] * fA4 - this.elements[11] * fA3; kInv[7] = +this.elements[8] * fA5 - this.elements[10] * fA2 + this.elements[11] * fA1; kInv[11] = -this.elements[8] * fA4 + this.elements[9] * fA2 - this.elements[11] * fA0; kInv[15] = +this.elements[8] * fA3 - this.elements[9] * fA1 + this.elements[10] * fA0;
// Inverse using Determinant var fInvDet = 1.0 / fDet; kInv[0] *= fInvDet; kInv[1] *= fInvDet; kInv[2] *= fInvDet; kInv[3] *= fInvDet; kInv[4] *= fInvDet; kInv[5] *= fInvDet; kInv[6] *= fInvDet; kInv[7] *= fInvDet; kInv[8] *= fInvDet; kInv[9] *= fInvDet; kInv[10] *= fInvDet; kInv[11] *= fInvDet; kInv[12] *= fInvDet; kInv[13] *= fInvDet; kInv[14] *= fInvDet; kInv[15] *= fInvDet;
this.elements = kInv.slice(); return true; }, toString: function() { var str = ""; for (var i = 0; i < 15; i++) { str += this.elements[i] + ", "; } str += this.elements[15]; return str; }, /** * @member PMatrix3D * The print() function prints out the elements of this matrix */ print: function() { var digits = printMatrixHelper(this.elements);
var output = "" + p.nfs(this.elements[0], digits, 4) + " " + p.nfs(this.elements[1], digits, 4) + " " + p.nfs(this.elements[2], digits, 4) + " " + p.nfs(this.elements[3], digits, 4) + "\n" + p.nfs(this.elements[4], digits, 4) + " " + p.nfs(this.elements[5], digits, 4) + " " + p.nfs(this.elements[6], digits, 4) + " " + p.nfs(this.elements[7], digits, 4) + "\n" + p.nfs(this.elements[8], digits, 4) + " " + p.nfs(this.elements[9], digits, 4) + " " + p.nfs(this.elements[10], digits, 4) + " " + p.nfs(this.elements[11], digits, 4) + "\n" + p.nfs(this.elements[12], digits, 4) + " " + p.nfs(this.elements[13], digits, 4) + " " + p.nfs(this.elements[14], digits, 4) + " " + p.nfs(this.elements[15], digits, 4) + "\n\n"; p.println(output); }, invTranslate: function(tx, ty, tz) { this.preApply(1, 0, 0, -tx, 0, 1, 0, -ty, 0, 0, 1, -tz, 0, 0, 0, 1); }, invRotateX: function(angle) { var c = Math.cos(-angle); var s = Math.sin(-angle); this.preApply([1, 0, 0, 0, 0, c, -s, 0, 0, s, c, 0, 0, 0, 0, 1]); }, invRotateY: function(angle) { var c = Math.cos(-angle); var s = Math.sin(-angle); this.preApply([c, 0, s, 0, 0, 1, 0, 0, -s, 0, c, 0, 0, 0, 0, 1]); }, invRotateZ: function(angle) { var c = Math.cos(-angle); var s = Math.sin(-angle); this.preApply([c, -s, 0, 0, s, c, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]); }, invScale: function(x, y, z) { this.preApply([1 / x, 0, 0, 0, 0, 1 / y, 0, 0, 0, 0, 1 / z, 0, 0, 0, 0, 1]); } };
/** * @private * The matrix stack stores the transformations and translations that occur within the space. */ var PMatrixStack = p.PMatrixStack = function() { this.matrixStack = []; };
/** * @member PMatrixStack * load pushes the matrix given in the function into the stack * * @param {Object | Array} matrix the matrix to be pushed into the stack */ PMatrixStack.prototype.load = function() { var tmpMatrix = drawing.$newPMatrix();
if (arguments.length === 1) { tmpMatrix.set(arguments[0]); } else { tmpMatrix.set(arguments); } this.matrixStack.push(tmpMatrix); };
Drawing2D.prototype.$newPMatrix = function() { return new PMatrix2D(); };
Drawing3D.prototype.$newPMatrix = function() { return new PMatrix3D(); };
/** * @member PMatrixStack * push adds a duplicate of the top of the stack onto the stack - uses the peek function */ PMatrixStack.prototype.push = function() { this.matrixStack.push(this.peek()); };
/** * @member PMatrixStack * pop removes returns the matrix at the top of the stack * * @returns {Object} the matrix at the top of the stack */ PMatrixStack.prototype.pop = function() { return this.matrixStack.pop(); };
/** * @member PMatrixStack * peek returns but doesn't remove the matrix at the top of the stack * * @returns {Object} the matrix at the top of the stack */ PMatrixStack.prototype.peek = function() { var tmpMatrix = drawing.$newPMatrix();
tmpMatrix.set(this.matrixStack[this.matrixStack.length - 1]); return tmpMatrix; };
/** * @member PMatrixStack * this function multiplies the matrix at the top of the stack with the matrix given as a parameter * * @param {Object | Array} matrix the matrix to be multiplied into the stack */ PMatrixStack.prototype.mult = function(matrix) { this.matrixStack[this.matrixStack.length - 1].apply(matrix); };
//////////////////////////////////////////////////////////////////////////// // Array handling ////////////////////////////////////////////////////////////////////////////
/** * The split() function breaks a string into pieces using a character or string * as the divider. The delim parameter specifies the character or characters that * mark the boundaries between each piece. A String[] array is returned that contains * each of the pieces. * If the result is a set of numbers, you can convert the String[] array to to a float[] * or int[] array using the datatype conversion functions int() and float() (see example above). * The splitTokens() function works in a similar fashion, except that it splits using a range * of characters instead of a specific character or sequence. * * @param {String} str the String to be split * @param {String} delim the character or String used to separate the data * * @returns {string[]} The new string array * * @see splitTokens * @see join * @see trim */ p.split = function(str, delim) { return str.split(delim); };
/** * The splitTokens() function splits a String at one or many character "tokens." The tokens * parameter specifies the character or characters to be used as a boundary. * If no tokens character is specified, any whitespace character is used to split. * Whitespace characters include tab (\t), line feed (\n), carriage return (\r), form * feed (\f), and space. To convert a String to an array of integers or floats, use the * datatype conversion functions int() and float() to convert the array of Strings. * * @param {String} str the String to be split * @param {Char[]} tokens list of individual characters that will be used as separators * * @returns {string[]} The new string array * * @see split * @see join * @see trim */ p.splitTokens = function(str, tokens) { if (tokens === undef) { return str.split(/\s+/g); }
var chars = tokens.split(/()/g), buffer = "", len = str.length, i, c, tokenized = [];
for (i = 0; i < len; i++) { c = str[i]; if (chars.indexOf(c) > -1) { if (buffer !== "") { tokenized.push(buffer); } buffer = ""; } else { buffer += c; } }
if (buffer !== "") { tokenized.push(buffer); }
return tokenized; };
/** * Expands an array by one element and adds data to the new position. The datatype of * the element parameter must be the same as the datatype of the array. * When using an array of objects, the data returned from the function must be cast to * the object array's data type. For example: SomeClass[] items = (SomeClass[]) * append(originalArray, element). * * @param {boolean[]|byte[]|char[]|int[]|float[]|String[]|array of objects} array boolean[], * byte[], char[], int[], float[], or String[], or an array of objects * @param {boolean[]|byte[]|char[]|int[]|float[]|String[]|array of objects} element new data for the array * * @returns Array (the same datatype as the input) * * @see shorten * @see expand */ p.append = function(array, element) { array[array.length] = element; return array; };
/** * Concatenates two arrays. For example, concatenating the array { 1, 2, 3 } and the * array { 4, 5, 6 } yields { 1, 2, 3, 4, 5, 6 }. Both parameters must be arrays of the * same datatype. * When using an array of objects, the data returned from the function must be cast to the * object array's data type. For example: SomeClass[] items = (SomeClass[]) concat(array1, array2). * * @param {boolean[]|byte[]|char[]|int[]|float[]|String[]|array of objects} array1 boolean[], * byte[], char[], int[], float[], String[], or an array of objects * @param {boolean[]|byte[]|char[]|int[]|float[]|String[]|array of objects} array2 boolean[], * byte[], char[], int[], float[], String[], or an array of objects * * @returns Array (the same datatype as the input) * * @see splice */ p.concat = function(array1, array2) { return array1.concat(array2); };
/** * Sorts an array of numbers from smallest to largest and puts an array of * words in alphabetical order. The original array is not modified, a * re-ordered array is returned. The count parameter states the number of * elements to sort. For example if there are 12 elements in an array and * if count is the value 5, only the first five elements on the array will * be sorted. Alphabetical ordering is case insensitive. * * @param {String[] | int[] | float[]} array Array of elements to sort * @param {int} numElem Number of elements to sort * * @returns {String[] | int[] | float[]} Array (same datatype as the input) * * @see reverse */ p.sort = function(array, numElem) { var ret = [];
// depending on the type used (int, float) or string // we'll need to use a different compare function if (array.length > 0) { // copy since we need to return another array var elemsToCopy = numElem > 0 ? numElem : array.length; for (var i = 0; i < elemsToCopy; i++) { ret.push(array[i]); } if (typeof array[0] === "string") { ret.sort(); } // int or float else { ret.sort(function(a, b) { return a - b; }); }
// copy on the rest of the elements that were not sorted in case the user // only wanted a subset of an array to be sorted. if (numElem > 0) { for (var j = ret.length; j < array.length; j++) { ret.push(array[j]); } } } return ret; };
/** * Inserts a value or array of values into an existing array. The first two parameters must * be of the same datatype. The array parameter defines the array which will be modified * and the second parameter defines the data which will be inserted. When using an array * of objects, the data returned from the function must be cast to the object array's data * type. For example: SomeClass[] items = (SomeClass[]) splice(array1, array2, index). * * @param {boolean[]|byte[]|char[]|int[]|float[]|String[]|array of objects} array boolean[], * byte[], char[], int[], float[], String[], or an array of objects * @param {boolean|byte|char|int|float|String|boolean[]|byte[]|char[]|int[]|float[]|String[]|array of objects} * value boolean, byte, char, int, float, String, boolean[], byte[], char[], int[], * float[], String[], or other Object: value or an array of objects to be spliced in * @param {int} index position in the array from which to insert data * * @returns Array (the same datatype as the input) * * @see contract * @see subset */ p.splice = function(array, value, index) {
// Trying to splice an empty array into "array" in P5 won't do // anything, just return the original. if(value.length === 0) { return array; }
// If the second argument was an array, we'll need to iterate over all // the "value" elements and add one by one because // array.splice(index, 0, value); // would create a multi-dimensional array which isn't what we want. if(value instanceof Array) { for(var i = 0, j = index; i < value.length; j++,i++) { array.splice(j, 0, value[i]); } } else { array.splice(index, 0, value); }
return array; };
/** * Extracts an array of elements from an existing array. The array parameter defines the * array from which the elements will be copied and the offset and length parameters determine * which elements to extract. If no length is given, elements will be extracted from the offset * to the end of the array. When specifying the offset remember the first array element is 0. * This function does not change the source array. * When using an array of objects, the data returned from the function must be cast to the * object array's data type. * * @param {boolean[]|byte[]|char[]|int[]|float[]|String[]|array of objects} array boolean[], * byte[], char[], int[], float[], String[], or an array of objects * @param {int} offset position to begin * @param {int} length number of values to extract * * @returns Array (the same datatype as the input) * * @see splice */ p.subset = function(array, offset, length) { var end = (length !== undef) ? offset + length : array.length; return array.slice(offset, end); };
/** * Combines an array of Strings into one String, each separated by the character(s) used for * the separator parameter. To join arrays of ints or floats, it's necessary to first convert * them to strings using nf() or nfs(). * * @param {Array} array array of Strings * @param {char|String} separator char or String to be placed between each item * * @returns {String} The combined string * * @see split * @see trim * @see nf * @see nfs */ p.join = function(array, seperator) { return array.join(seperator); };
/** * Decreases an array by one element and returns the shortened array. When using an * array of objects, the data returned from the function must be cast to the object array's * data type. For example: SomeClass[] items = (SomeClass[]) shorten(originalArray). * * @param {boolean[]|byte[]|char[]|int[]|float[]|String[]|array of objects} array * boolean[], byte[], char[], int[], float[], or String[], or an array of objects * * @returns Array (the same datatype as the input) * * @see append * @see expand */ p.shorten = function(ary) { var newary = [];
// copy array into new array var len = ary.length; for (var i = 0; i < len; i++) { newary[i] = ary[i]; } newary.pop();
return newary; };
/** * Increases the size of an array. By default, this function doubles the size of the array, * but the optional newSize parameter provides precise control over the increase in size. * When using an array of objects, the data returned from the function must be cast to the * object array's data type. For example: SomeClass[] items = (SomeClass[]) expand(originalArray). * * @param {boolean[]|byte[]|char[]|int[]|float[]|String[]|array of objects} ary * boolean[], byte[], char[], int[], float[], String[], or an array of objects * @param {int} newSize positive int: new size for the array * * @returns Array (the same datatype as the input) * * @see contract */ p.expand = function(ary, targetSize) { var temp = ary.slice(0), newSize = targetSize || ary.length * 2; temp.length = newSize; return temp; };
/** * Copies an array (or part of an array) to another array. The src array is copied to the * dst array, beginning at the position specified by srcPos and into the position specified * by dstPos. The number of elements to copy is determined by length. The simplified version * with two arguments copies an entire array to another of the same size. It is equivalent * to "arrayCopy(src, 0, dst, 0, src.length)". This function is far more efficient for copying * array data than iterating through a for and copying each element. * * @param {Array} src an array of any data type: the source array * @param {Array} dest an array of any data type (as long as it's the same as src): the destination array * @param {int} srcPos starting position in the source array * @param {int} destPos starting position in the destination array * @param {int} length number of array elements to be copied * * @returns none */ p.arrayCopy = function() { // src, srcPos, dest, destPos, length) { var src, srcPos = 0, dest, destPos = 0, length;
if (arguments.length === 2) { // recall itself and copy src to dest from start index 0 to 0 of src.length src = arguments[0]; dest = arguments[1]; length = src.length; } else if (arguments.length === 3) { // recall itself and copy src to dest from start index 0 to 0 of length src = arguments[0]; dest = arguments[1]; length = arguments[2]; } else if (arguments.length === 5) { src = arguments[0]; srcPos = arguments[1]; dest = arguments[2]; destPos = arguments[3]; length = arguments[4]; }
// copy src to dest from index srcPos to index destPos of length recursivly on objects for (var i = srcPos, j = destPos; i < length + srcPos; i++, j++) { if (dest[j] !== undef) { dest[j] = src[i]; } else { throw "array index out of bounds exception"; } } };
/** * Reverses the order of an array. * * @param {boolean[]|byte[]|char[]|int[]|float[]|String[]} array * boolean[], byte[], char[], int[], float[], or String[] * * @returns Array (the same datatype as the input) * * @see sort */ p.reverse = function(array) { return array.reverse(); };
//////////////////////////////////////////////////////////////////////////// // Color functions ////////////////////////////////////////////////////////////////////////////
// helper functions for internal blending modes p.mix = function(a, b, f) { return a + (((b - a) * f) >> 8); };
p.peg = function(n) { return (n < 0) ? 0 : ((n > 255) ? 255 : n); };
// blending modes /** * These are internal blending modes used for BlendColor() * * @param {Color} c1 First Color to blend * @param {Color} c2 Second Color to blend * * @returns {Color} The blended Color * * @see BlendColor * @see Blend */ p.modes = (function() { var ALPHA_MASK = PConstants.ALPHA_MASK, RED_MASK = PConstants.RED_MASK, GREEN_MASK = PConstants.GREEN_MASK, BLUE_MASK = PConstants.BLUE_MASK, min = Math.min, max = Math.max;
function applyMode(c1, f, ar, ag, ab, br, bg, bb, cr, cg, cb) { var a = min(((c1 & 0xff000000) >>> 24) + f, 0xff) << 24;
var r = (ar + (((cr - ar) * f) >> 8)); r = ((r < 0) ? 0 : ((r > 255) ? 255 : r)) << 16;
var g = (ag + (((cg - ag) * f) >> 8)); g = ((g < 0) ? 0 : ((g > 255) ? 255 : g)) << 8;
var b = ab + (((cb - ab) * f) >> 8); b = (b < 0) ? 0 : ((b > 255) ? 255 : b);
return (a | r | g | b); }
return { replace: function(c1, c2) { return c2; }, blend: function(c1, c2) { var f = (c2 & ALPHA_MASK) >>> 24, ar = (c1 & RED_MASK), ag = (c1 & GREEN_MASK), ab = (c1 & BLUE_MASK), br = (c2 & RED_MASK), bg = (c2 & GREEN_MASK), bb = (c2 & BLUE_MASK);
return (min(((c1 & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | (ar + (((br - ar) * f) >> 8)) & RED_MASK | (ag + (((bg - ag) * f) >> 8)) & GREEN_MASK | (ab + (((bb - ab) * f) >> 8)) & BLUE_MASK); }, add: function(c1, c2) { var f = (c2 & ALPHA_MASK) >>> 24; return (min(((c1 & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | min(((c1 & RED_MASK) + ((c2 & RED_MASK) >> 8) * f), RED_MASK) & RED_MASK | min(((c1 & GREEN_MASK) + ((c2 & GREEN_MASK) >> 8) * f), GREEN_MASK) & GREEN_MASK | min((c1 & BLUE_MASK) + (((c2 & BLUE_MASK) * f) >> 8), BLUE_MASK)); }, subtract: function(c1, c2) { var f = (c2 & ALPHA_MASK) >>> 24; return (min(((c1 & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | max(((c1 & RED_MASK) - ((c2 & RED_MASK) >> 8) * f), GREEN_MASK) & RED_MASK | max(((c1 & GREEN_MASK) - ((c2 & GREEN_MASK) >> 8) * f), BLUE_MASK) & GREEN_MASK | max((c1 & BLUE_MASK) - (((c2 & BLUE_MASK) * f) >> 8), 0)); }, lightest: function(c1, c2) { var f = (c2 & ALPHA_MASK) >>> 24; return (min(((c1 & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | max(c1 & RED_MASK, ((c2 & RED_MASK) >> 8) * f) & RED_MASK | max(c1 & GREEN_MASK, ((c2 & GREEN_MASK) >> 8) * f) & GREEN_MASK | max(c1 & BLUE_MASK, ((c2 & BLUE_MASK) * f) >> 8)); }, darkest: function(c1, c2) { var f = (c2 & ALPHA_MASK) >>> 24, ar = (c1 & RED_MASK), ag = (c1 & GREEN_MASK), ab = (c1 & BLUE_MASK), br = min(c1 & RED_MASK, ((c2 & RED_MASK) >> 8) * f), bg = min(c1 & GREEN_MASK, ((c2 & GREEN_MASK) >> 8) * f), bb = min(c1 & BLUE_MASK, ((c2 & BLUE_MASK) * f) >> 8);
return (min(((c1 & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | (ar + (((br - ar) * f) >> 8)) & RED_MASK | (ag + (((bg - ag) * f) >> 8)) & GREEN_MASK | (ab + (((bb - ab) * f) >> 8)) & BLUE_MASK); }, difference: function(c1, c2) { var f = (c2 & ALPHA_MASK) >>> 24, ar = (c1 & RED_MASK) >> 16, ag = (c1 & GREEN_MASK) >> 8, ab = (c1 & BLUE_MASK), br = (c2 & RED_MASK) >> 16, bg = (c2 & GREEN_MASK) >> 8, bb = (c2 & BLUE_MASK), cr = (ar > br) ? (ar - br) : (br - ar), cg = (ag > bg) ? (ag - bg) : (bg - ag), cb = (ab > bb) ? (ab - bb) : (bb - ab);
return applyMode(c1, f, ar, ag, ab, br, bg, bb, cr, cg, cb); }, exclusion: function(c1, c2) { var f = (c2 & ALPHA_MASK) >>> 24, ar = (c1 & RED_MASK) >> 16, ag = (c1 & GREEN_MASK) >> 8, ab = (c1 & BLUE_MASK), br = (c2 & RED_MASK) >> 16, bg = (c2 & GREEN_MASK) >> 8, bb = (c2 & BLUE_MASK), cr = ar + br - ((ar * br) >> 7), cg = ag + bg - ((ag * bg) >> 7), cb = ab + bb - ((ab * bb) >> 7);
return applyMode(c1, f, ar, ag, ab, br, bg, bb, cr, cg, cb); }, multiply: function(c1, c2) { var f = (c2 & ALPHA_MASK) >>> 24, ar = (c1 & RED_MASK) >> 16, ag = (c1 & GREEN_MASK) >> 8, ab = (c1 & BLUE_MASK), br = (c2 & RED_MASK) >> 16, bg = (c2 & GREEN_MASK) >> 8, bb = (c2 & BLUE_MASK), cr = (ar * br) >> 8, cg = (ag * bg) >> 8, cb = (ab * bb) >> 8;
return applyMode(c1, f, ar, ag, ab, br, bg, bb, cr, cg, cb); }, screen: function(c1, c2) { var f = (c2 & ALPHA_MASK) >>> 24, ar = (c1 & RED_MASK) >> 16, ag = (c1 & GREEN_MASK) >> 8, ab = (c1 & BLUE_MASK), br = (c2 & RED_MASK) >> 16, bg = (c2 & GREEN_MASK) >> 8, bb = (c2 & BLUE_MASK), cr = 255 - (((255 - ar) * (255 - br)) >> 8), cg = 255 - (((255 - ag) * (255 - bg)) >> 8), cb = 255 - (((255 - ab) * (255 - bb)) >> 8);
return applyMode(c1, f, ar, ag, ab, br, bg, bb, cr, cg, cb); }, hard_light: function(c1, c2) { var f = (c2 & ALPHA_MASK) >>> 24, ar = (c1 & RED_MASK) >> 16, ag = (c1 & GREEN_MASK) >> 8, ab = (c1 & BLUE_MASK), br = (c2 & RED_MASK) >> 16, bg = (c2 & GREEN_MASK) >> 8, bb = (c2 & BLUE_MASK), cr = (br < 128) ? ((ar * br) >> 7) : (255 - (((255 - ar) * (255 - br)) >> 7)), cg = (bg < 128) ? ((ag * bg) >> 7) : (255 - (((255 - ag) * (255 - bg)) >> 7)), cb = (bb < 128) ? ((ab * bb) >> 7) : (255 - (((255 - ab) * (255 - bb)) >> 7));
return applyMode(c1, f, ar, ag, ab, br, bg, bb, cr, cg, cb); }, soft_light: function(c1, c2) { var f = (c2 & ALPHA_MASK) >>> 24, ar = (c1 & RED_MASK) >> 16, ag = (c1 & GREEN_MASK) >> 8, ab = (c1 & BLUE_MASK), br = (c2 & RED_MASK) >> 16, bg = (c2 & GREEN_MASK) >> 8, bb = (c2 & BLUE_MASK), cr = ((ar * br) >> 7) + ((ar * ar) >> 8) - ((ar * ar * br) >> 15), cg = ((ag * bg) >> 7) + ((ag * ag) >> 8) - ((ag * ag * bg) >> 15), cb = ((ab * bb) >> 7) + ((ab * ab) >> 8) - ((ab * ab * bb) >> 15);
return applyMode(c1, f, ar, ag, ab, br, bg, bb, cr, cg, cb); }, overlay: function(c1, c2) { var f = (c2 & ALPHA_MASK) >>> 24, ar = (c1 & RED_MASK) >> 16, ag = (c1 & GREEN_MASK) >> 8, ab = (c1 & BLUE_MASK), br = (c2 & RED_MASK) >> 16, bg = (c2 & GREEN_MASK) >> 8, bb = (c2 & BLUE_MASK), cr = (ar < 128) ? ((ar * br) >> 7) : (255 - (((255 - ar) * (255 - br)) >> 7)), cg = (ag < 128) ? ((ag * bg) >> 7) : (255 - (((255 - ag) * (255 - bg)) >> 7)), cb = (ab < 128) ? ((ab * bb) >> 7) : (255 - (((255 - ab) * (255 - bb)) >> 7));
return applyMode(c1, f, ar, ag, ab, br, bg, bb, cr, cg, cb); }, dodge: function(c1, c2) { var f = (c2 & ALPHA_MASK) >>> 24, ar = (c1 & RED_MASK) >> 16, ag = (c1 & GREEN_MASK) >> 8, ab = (c1 & BLUE_MASK), br = (c2 & RED_MASK) >> 16, bg = (c2 & GREEN_MASK) >> 8, bb = (c2 & BLUE_MASK);
var cr = 255; if (br !== 255) { cr = (ar << 8) / (255 - br); cr = (cr < 0) ? 0 : ((cr > 255) ? 255 : cr); }
var cg = 255; if (bg !== 255) { cg = (ag << 8) / (255 - bg); cg = (cg < 0) ? 0 : ((cg > 255) ? 255 : cg); }
var cb = 255; if (bb !== 255) { cb = (ab << 8) / (255 - bb); cb = (cb < 0) ? 0 : ((cb > 255) ? 255 : cb); }
return applyMode(c1, f, ar, ag, ab, br, bg, bb, cr, cg, cb); }, burn: function(c1, c2) { var f = (c2 & ALPHA_MASK) >>> 24, ar = (c1 & RED_MASK) >> 16, ag = (c1 & GREEN_MASK) >> 8, ab = (c1 & BLUE_MASK), br = (c2 & RED_MASK) >> 16, bg = (c2 & GREEN_MASK) >> 8, bb = (c2 & BLUE_MASK);
var cr = 0; if (br !== 0) { cr = ((255 - ar) << 8) / br; cr = 255 - ((cr < 0) ? 0 : ((cr > 255) ? 255 : cr)); }
var cg = 0; if (bg !== 0) { cg = ((255 - ag) << 8) / bg; cg = 255 - ((cg < 0) ? 0 : ((cg > 255) ? 255 : cg)); }
var cb = 0; if (bb !== 0) { cb = ((255 - ab) << 8) / bb; cb = 255 - ((cb < 0) ? 0 : ((cb > 255) ? 255 : cb)); }
return applyMode(c1, f, ar, ag, ab, br, bg, bb, cr, cg, cb); } }; }());
function color$4(aValue1, aValue2, aValue3, aValue4) { var r, g, b, a;
if (curColorMode === PConstants.HSB) { var rgb = p.color.toRGB(aValue1, aValue2, aValue3); r = rgb[0]; g = rgb[1]; b = rgb[2]; } else { r = Math.round(255 * (aValue1 / colorModeX)); g = Math.round(255 * (aValue2 / colorModeY)); b = Math.round(255 * (aValue3 / colorModeZ)); }
a = Math.round(255 * (aValue4 / colorModeA));
// Limit values less than 0 and greater than 255 r = (r < 0) ? 0 : r; g = (g < 0) ? 0 : g; b = (b < 0) ? 0 : b; a = (a < 0) ? 0 : a; r = (r > 255) ? 255 : r; g = (g > 255) ? 255 : g; b = (b > 255) ? 255 : b; a = (a > 255) ? 255 : a;
// Create color int return (a << 24) & PConstants.ALPHA_MASK | (r << 16) & PConstants.RED_MASK | (g << 8) & PConstants.GREEN_MASK | b & PConstants.BLUE_MASK; }
function color$2(aValue1, aValue2) { var a;
// Color int and alpha if (aValue1 & PConstants.ALPHA_MASK) { a = Math.round(255 * (aValue2 / colorModeA)); // Limit values less than 0 and greater than 255 a = (a > 255) ? 255 : a; a = (a < 0) ? 0 : a;
return aValue1 - (aValue1 & PConstants.ALPHA_MASK) + ((a << 24) & PConstants.ALPHA_MASK); } // Grayscale and alpha if (curColorMode === PConstants.RGB) { return color$4(aValue1, aValue1, aValue1, aValue2); } if (curColorMode === PConstants.HSB) { return color$4(0, 0, (aValue1 / colorModeX) * colorModeZ, aValue2); } }
function color$1(aValue1) { // Grayscale if (aValue1 <= colorModeX && aValue1 >= 0) { if (curColorMode === PConstants.RGB) { return color$4(aValue1, aValue1, aValue1, colorModeA); } if (curColorMode === PConstants.HSB) { return color$4(0, 0, (aValue1 / colorModeX) * colorModeZ, colorModeA); } } // Color int if (aValue1) { if (aValue1 > 2147483647) { // Java Overflow aValue1 -= 4294967296; } return aValue1; } }
/** * Creates colors for storing in variables of the color datatype. The parameters are * interpreted as RGB or HSB values depending on the current colorMode(). The default * mode is RGB values from 0 to 255 and therefore, the function call color(255, 204, 0) * will return a bright yellow color. More about how colors are stored can be found in * the reference for the color datatype. * * @param {int|float} aValue1 red or hue or grey values relative to the current color range. * Also can be color value in hexadecimal notation (i.e. #FFCC00 or 0xFFFFCC00) * @param {int|float} aValue2 green or saturation values relative to the current color range * @param {int|float} aValue3 blue or brightness values relative to the current color range * @param {int|float} aValue4 relative to current color range. Represents alpha * * @returns {color} the color * * @see colorMode */ p.color = function(aValue1, aValue2, aValue3, aValue4) {
// 4 arguments: (R, G, B, A) or (H, S, B, A) if (aValue1 !== undef && aValue2 !== undef && aValue3 !== undef && aValue4 !== undef) { return color$4(aValue1, aValue2, aValue3, aValue4); }
// 3 arguments: (R, G, B) or (H, S, B) if (aValue1 !== undef && aValue2 !== undef && aValue3 !== undef) { return color$4(aValue1, aValue2, aValue3, colorModeA); }
// 2 arguments: (Color, A) or (Grayscale, A) if (aValue1 !== undef && aValue2 !== undef) { return color$2(aValue1, aValue2); }
// 1 argument: (Grayscale) or (Color) if (typeof aValue1 === "number") { return color$1(aValue1); }
// Default return color$4(colorModeX, colorModeY, colorModeZ, colorModeA); };
// Ease of use function to extract the colour bits into a string p.color.toString = function(colorInt) { return "rgba(" + ((colorInt & PConstants.RED_MASK) >>> 16) + "," + ((colorInt & PConstants.GREEN_MASK) >>> 8) + "," + ((colorInt & PConstants.BLUE_MASK)) + "," + ((colorInt & PConstants.ALPHA_MASK) >>> 24) / 255 + ")"; };
// Easy of use function to pack rgba values into a single bit-shifted color int. p.color.toInt = function(r, g, b, a) { return (a << 24) & PConstants.ALPHA_MASK | (r << 16) & PConstants.RED_MASK | (g << 8) & PConstants.GREEN_MASK | b & PConstants.BLUE_MASK; };
// Creates a simple array in [R, G, B, A] format, [255, 255, 255, 255] p.color.toArray = function(colorInt) { return [(colorInt & PConstants.RED_MASK) >>> 16, (colorInt & PConstants.GREEN_MASK) >>> 8, colorInt & PConstants.BLUE_MASK, (colorInt & PConstants.ALPHA_MASK) >>> 24]; };
// Creates a WebGL color array in [R, G, B, A] format. WebGL wants the color ranges between 0 and 1, [1, 1, 1, 1] p.color.toGLArray = function(colorInt) { return [((colorInt & PConstants.RED_MASK) >>> 16) / 255, ((colorInt & PConstants.GREEN_MASK) >>> 8) / 255, (colorInt & PConstants.BLUE_MASK) / 255, ((colorInt & PConstants.ALPHA_MASK) >>> 24) / 255]; };
// HSB conversion function from Mootools, MIT Licensed p.color.toRGB = function(h, s, b) { // Limit values greater than range h = (h > colorModeX) ? colorModeX : h; s = (s > colorModeY) ? colorModeY : s; b = (b > colorModeZ) ? colorModeZ : b;
h = (h / colorModeX) * 360; s = (s / colorModeY) * 100; b = (b / colorModeZ) * 100;
var br = Math.round(b / 100 * 255);
if (s === 0) { // Grayscale return [br, br, br]; } var hue = h % 360; var f = hue % 60; var p = Math.round((b * (100 - s)) / 10000 * 255); var q = Math.round((b * (6000 - s * f)) / 600000 * 255); var t = Math.round((b * (6000 - s * (60 - f))) / 600000 * 255); switch (Math.floor(hue / 60)) { case 0: return [br, t, p]; case 1: return [q, br, p]; case 2: return [p, br, t]; case 3: return [p, q, br]; case 4: return [t, p, br]; case 5: return [br, p, q]; } };
function colorToHSB(colorInt) { var red, green, blue;
red = ((colorInt & PConstants.RED_MASK) >>> 16) / 255; green = ((colorInt & PConstants.GREEN_MASK) >>> 8) / 255; blue = (colorInt & PConstants.BLUE_MASK) / 255;
var max = p.max(p.max(red,green), blue), min = p.min(p.min(red,green), blue), hue, saturation;
if (min === max) { return [0, 0, max*colorModeZ]; } saturation = (max - min) / max;
if (red === max) { hue = (green - blue) / (max - min); } else if (green === max) { hue = 2 + ((blue - red) / (max - min)); } else { hue = 4 + ((red - green) / (max - min)); }
hue /= 6;
if (hue < 0) { hue += 1; } else if (hue > 1) { hue -= 1; } return [hue*colorModeX, saturation*colorModeY, max*colorModeZ]; }
/** * Extracts the brightness value from a color. * * @param {color} colInt any value of the color datatype * * @returns {float} The brightness color value. * * @see red * @see green * @see blue * @see hue * @see saturation */ p.brightness = function(colInt){ return colorToHSB(colInt)[2]; };
/** * Extracts the saturation value from a color. * * @param {color} colInt any value of the color datatype * * @returns {float} The saturation color value. * * @see red * @see green * @see blue * @see hue * @see brightness */ p.saturation = function(colInt){ return colorToHSB(colInt)[1]; };
/** * Extracts the hue value from a color. * * @param {color} colInt any value of the color datatype * * @returns {float} The hue color value. * * @see red * @see green * @see blue * @see saturation * @see brightness */ p.hue = function(colInt){ return colorToHSB(colInt)[0]; };
/** * Extracts the red value from a color, scaled to match current colorMode(). * This value is always returned as a float so be careful not to assign it to an int value. * * @param {color} aColor any value of the color datatype * * @returns {float} The red color value. * * @see green * @see blue * @see alpha * @see >> right shift * @see hue * @see saturation * @see brightness */ p.red = function(aColor) { return ((aColor & PConstants.RED_MASK) >>> 16) / 255 * colorModeX; };
/** * Extracts the green value from a color, scaled to match current colorMode(). * This value is always returned as a float so be careful not to assign it to an int value. * * @param {color} aColor any value of the color datatype * * @returns {float} The green color value. * * @see red * @see blue * @see alpha * @see >> right shift * @see hue * @see saturation * @see brightness */ p.green = function(aColor) { return ((aColor & PConstants.GREEN_MASK) >>> 8) / 255 * colorModeY; };
/** * Extracts the blue value from a color, scaled to match current colorMode(). * This value is always returned as a float so be careful not to assign it to an int value. * * @param {color} aColor any value of the color datatype * * @returns {float} The blue color value. * * @see red * @see green * @see alpha * @see >> right shift * @see hue * @see saturation * @see brightness */ p.blue = function(aColor) { return (aColor & PConstants.BLUE_MASK) / 255 * colorModeZ; };
/** * Extracts the alpha value from a color, scaled to match current colorMode(). * This value is always returned as a float so be careful not to assign it to an int value. * * @param {color} aColor any value of the color datatype * * @returns {float} The alpha color value. * * @see red * @see green * @see blue * @see >> right shift * @see hue * @see saturation * @see brightness */ p.alpha = function(aColor) { return ((aColor & PConstants.ALPHA_MASK) >>> 24) / 255 * colorModeA; };
/** * Calculates a color or colors between two colors at a specific increment. * The amt parameter is the amount to interpolate between the two values where 0.0 * equal to the first point, 0.1 is very near the first point, 0.5 is half-way in between, etc. * * @param {color} c1 interpolate from this color * @param {color} c2 interpolate to this color * @param {float} amt between 0.0 and 1.0 * * @returns {float} The blended color. * * @see blendColor * @see color */ p.lerpColor = function(c1, c2, amt) { var r, g, b, a, r1, g1, b1, a1, r2, g2, b2, a2; var hsb1, hsb2, rgb, h, s; var colorBits1 = p.color(c1); var colorBits2 = p.color(c2);
if (curColorMode === PConstants.HSB) { // Special processing for HSB mode. // Get HSB and Alpha values for Color 1 and 2 hsb1 = colorToHSB(colorBits1); a1 = ((colorBits1 & PConstants.ALPHA_MASK) >>> 24) / colorModeA; hsb2 = colorToHSB(colorBits2); a2 = ((colorBits2 & PConstants.ALPHA_MASK) >>> 24) / colorModeA;
// Return lerp value for each channel, for HSB components h = p.lerp(hsb1[0], hsb2[0], amt); s = p.lerp(hsb1[1], hsb2[1], amt); b = p.lerp(hsb1[2], hsb2[2], amt); rgb = p.color.toRGB(h, s, b); // ... and for Alpha-range a = p.lerp(a1, a2, amt) * colorModeA;
return (a << 24) & PConstants.ALPHA_MASK | (rgb[0] << 16) & PConstants.RED_MASK | (rgb[1] << 8) & PConstants.GREEN_MASK | rgb[2] & PConstants.BLUE_MASK; }
// Get RGBA values for Color 1 to floats r1 = (colorBits1 & PConstants.RED_MASK) >>> 16; g1 = (colorBits1 & PConstants.GREEN_MASK) >>> 8; b1 = (colorBits1 & PConstants.BLUE_MASK); a1 = ((colorBits1 & PConstants.ALPHA_MASK) >>> 24) / colorModeA;
// Get RGBA values for Color 2 to floats r2 = (colorBits2 & PConstants.RED_MASK) >>> 16; g2 = (colorBits2 & PConstants.GREEN_MASK) >>> 8; b2 = (colorBits2 & PConstants.BLUE_MASK); a2 = ((colorBits2 & PConstants.ALPHA_MASK) >>> 24) / colorModeA;
// Return lerp value for each channel, INT for color, Float for Alpha-range r = p.lerp(r1, r2, amt) | 0; g = p.lerp(g1, g2, amt) | 0; b = p.lerp(b1, b2, amt) | 0; a = p.lerp(a1, a2, amt) * colorModeA;
return (a << 24) & PConstants.ALPHA_MASK | (r << 16) & PConstants.RED_MASK | (g << 8) & PConstants.GREEN_MASK | b & PConstants.BLUE_MASK; };
/** * Changes the way Processing interprets color data. By default, fill(), stroke(), and background() * colors are set by values between 0 and 255 using the RGB color model. It is possible to change the * numerical range used for specifying colors and to switch color systems. For example, calling colorMode(RGB, 1.0) * will specify that values are specified between 0 and 1. The limits for defining colors are altered by setting the * parameters range1, range2, range3, and range 4. * * @param {MODE} mode Either RGB or HSB, corresponding to Red/Green/Blue and Hue/Saturation/Brightness * @param {int|float} range range for all color elements * @param {int|float} range1 range for the red or hue depending on the current color mode * @param {int|float} range2 range for the green or saturation depending on the current color mode * @param {int|float} range3 range for the blue or brightness depending on the current color mode * @param {int|float} range4 range for the alpha * * @returns none * * @see background * @see fill * @see stroke */ p.colorMode = function() { // mode, range1, range2, range3, range4 curColorMode = arguments[0]; if (arguments.length > 1) { colorModeX = arguments[1]; colorModeY = arguments[2] || arguments[1]; colorModeZ = arguments[3] || arguments[1]; colorModeA = arguments[4] || arguments[1]; } };
/** * Blends two color values together based on the blending mode given as the MODE parameter. * The possible modes are described in the reference for the blend() function. * * @param {color} c1 color: the first color to blend * @param {color} c2 color: the second color to blend * @param {MODE} MODE Either BLEND, ADD, SUBTRACT, DARKEST, LIGHTEST, DIFFERENCE, EXCLUSION, MULTIPLY, * SCREEN, OVERLAY, HARD_LIGHT, SOFT_LIGHT, DODGE, or BURN * * @returns {float} The blended color. * * @see blend * @see color */ p.blendColor = function(c1, c2, mode) { if (mode === PConstants.REPLACE) { return p.modes.replace(c1, c2); } else if (mode === PConstants.BLEND) { return p.modes.blend(c1, c2); } else if (mode === PConstants.ADD) { return p.modes.add(c1, c2); } else if (mode === PConstants.SUBTRACT) { return p.modes.subtract(c1, c2); } else if (mode === PConstants.LIGHTEST) { return p.modes.lightest(c1, c2); } else if (mode === PConstants.DARKEST) { return p.modes.darkest(c1, c2); } else if (mode === PConstants.DIFFERENCE) { return p.modes.difference(c1, c2); } else if (mode === PConstants.EXCLUSION) { return p.modes.exclusion(c1, c2); } else if (mode === PConstants.MULTIPLY) { return p.modes.multiply(c1, c2); } else if (mode === PConstants.SCREEN) { return p.modes.screen(c1, c2); } else if (mode === PConstants.HARD_LIGHT) { return p.modes.hard_light(c1, c2); } else if (mode === PConstants.SOFT_LIGHT) { return p.modes.soft_light(c1, c2); } else if (mode === PConstants.OVERLAY) { return p.modes.overlay(c1, c2); } else if (mode === PConstants.DODGE) { return p.modes.dodge(c1, c2); } else if (mode === PConstants.BURN) { return p.modes.burn(c1, c2); } };
//////////////////////////////////////////////////////////////////////////// // Canvas-Matrix manipulation ////////////////////////////////////////////////////////////////////////////
function saveContext() { curContext.save(); }
function restoreContext() { curContext.restore(); isStrokeDirty = true; isFillDirty = true; }
/** * Prints the current matrix to the text window. * * @returns none * * @see pushMatrix * @see popMatrix * @see resetMatrix * @see applyMatrix */ p.printMatrix = function() { modelView.print(); };
/** * Specifies an amount to displace objects within the display window. The x parameter specifies left/right translation, * the y parameter specifies up/down translation, and the z parameter specifies translations toward/away from the screen. * Using this function with the z parameter requires using the P3D or OPENGL parameter in combination with size as shown * in the above example. Transformations apply to everything that happens after and subsequent calls to the function * accumulates the effect. For example, calling translate(50, 0) and then translate(20, 0) is the same as translate(70, 0). * If translate() is called within draw(), the transformation is reset when the loop begins again. * This function can be further controlled by the pushMatrix() and popMatrix(). * * @param {int|float} x left/right translation * @param {int|float} y up/down translation * @param {int|float} z forward/back translation * * @returns none * * @see pushMatrix * @see popMatrix * @see scale * @see rotate * @see rotateX * @see rotateY * @see rotateZ */ Drawing2D.prototype.translate = function(x, y) { modelView.translate(x, y); modelViewInv.invTranslate(x, y); curContext.translate(x, y); };
Drawing3D.prototype.translate = function(x, y, z) { modelView.translate(x, y, z); modelViewInv.invTranslate(x, y, z); };
/** * Increases or decreases the size of a shape by expanding and contracting vertices. Objects always scale from their * relative origin to the coordinate system. Scale values are specified as decimal percentages. For example, the * function call scale(2.0) increases the dimension of a shape by 200%. Transformations apply to everything that * happens after and subsequent calls to the function multiply the effect. For example, calling scale(2.0) and * then scale(1.5) is the same as scale(3.0). If scale() is called within draw(), the transformation is reset when * the loop begins again. Using this fuction with the z parameter requires passing P3D or OPENGL into the size() * parameter as shown in the example above. This function can be further controlled by pushMatrix() and popMatrix(). * * @param {int|float} size percentage to scale the object * @param {int|float} x percentage to scale the object in the x-axis * @param {int|float} y percentage to scale the object in the y-axis * @param {int|float} z percentage to scale the object in the z-axis * * @returns none * * @see pushMatrix * @see popMatrix * @see translate * @see rotate * @see rotateX * @see rotateY * @see rotateZ */ Drawing2D.prototype.scale = function(x, y) { modelView.scale(x, y); modelViewInv.invScale(x, y); curContext.scale(x, y || x); };
Drawing3D.prototype.scale = function(x, y, z) { modelView.scale(x, y, z); modelViewInv.invScale(x, y, z); };
/** * helper function for applying a transfrom matrix to a 2D context. */ Drawing2D.prototype.transform = function(pmatrix) { var e = pmatrix.array(); curContext.transform(e[0],e[3],e[1],e[4],e[2],e[5]); };
/** * helper function for applying a transfrom matrix to a 3D context. * not currently implemented. */ Drawing3D.prototype.transformm = function(pmatrix3d) { throw("p.transform is currently not supported in 3D mode"); };
/** * Pushes the current transformation matrix onto the matrix stack. Understanding pushMatrix() and popMatrix() * requires understanding the concept of a matrix stack. The pushMatrix() function saves the current coordinate * system to the stack and popMatrix() restores the prior coordinate system. pushMatrix() and popMatrix() are * used in conjuction with the other transformation methods and may be embedded to control the scope of * the transformations. * * @returns none * * @see popMatrix * @see translate * @see rotate * @see rotateX * @see rotateY * @see rotateZ */ Drawing2D.prototype.pushMatrix = function() { userMatrixStack.load(modelView); userReverseMatrixStack.load(modelViewInv); saveContext(); };
Drawing3D.prototype.pushMatrix = function() { userMatrixStack.load(modelView); userReverseMatrixStack.load(modelViewInv); };
/** * Pops the current transformation matrix off the matrix stack. Understanding pushing and popping requires * understanding the concept of a matrix stack. The pushMatrix() function saves the current coordinate system to * the stack and popMatrix() restores the prior coordinate system. pushMatrix() and popMatrix() are used in * conjuction with the other transformation methods and may be embedded to control the scope of the transformations. * * @returns none * * @see popMatrix * @see pushMatrix */ Drawing2D.prototype.popMatrix = function() { modelView.set(userMatrixStack.pop()); modelViewInv.set(userReverseMatrixStack.pop()); restoreContext(); };
Drawing3D.prototype.popMatrix = function() { modelView.set(userMatrixStack.pop()); modelViewInv.set(userReverseMatrixStack.pop()); };
/** * Replaces the current matrix with the identity matrix. The equivalent function in OpenGL is glLoadIdentity(). * * @returns none * * @see popMatrix * @see pushMatrix * @see applyMatrix * @see printMatrix */ Drawing2D.prototype.resetMatrix = function() { modelView.reset(); modelViewInv.reset(); curContext.setTransform(1,0,0,1,0,0); };
Drawing3D.prototype.resetMatrix = function() { modelView.reset(); modelViewInv.reset(); };
/** * Multiplies the current matrix by the one specified through the parameters. This is very slow because it will * try to calculate the inverse of the transform, so avoid it whenever possible. The equivalent function * in OpenGL is glMultMatrix(). * * @param {int|float} n00-n15 numbers which define the 4x4 matrix to be multiplied * * @returns none * * @see popMatrix * @see pushMatrix * @see resetMatrix * @see printMatrix */ DrawingShared.prototype.applyMatrix = function() { var a = arguments; modelView.apply(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15]); modelViewInv.invApply(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15]); };
Drawing2D.prototype.applyMatrix = function() { var a = arguments; for (var cnt = a.length; cnt < 16; cnt++) { a[cnt] = 0; } a[10] = a[15] = 1; DrawingShared.prototype.applyMatrix.apply(this, a); };
/** * Rotates a shape around the x-axis the amount specified by the angle parameter. Angles should be * specified in radians (values from 0 to PI*2) or converted to radians with the radians() function. * Objects are always rotated around their relative position to the origin and positive numbers * rotate objects in a counterclockwise direction. Transformations apply to everything that happens * after and subsequent calls to the function accumulates the effect. For example, calling rotateX(PI/2) * and then rotateX(PI/2) is the same as rotateX(PI). If rotateX() is called within the draw(), the * transformation is reset when the loop begins again. This function requires passing P3D or OPENGL * into the size() parameter as shown in the example above. * * @param {int|float} angleInRadians angle of rotation specified in radians * * @returns none * * @see rotateY * @see rotateZ * @see rotate * @see translate * @see scale * @see popMatrix * @see pushMatrix */ p.rotateX = function(angleInRadians) { modelView.rotateX(angleInRadians); modelViewInv.invRotateX(angleInRadians); };
/** * Rotates a shape around the z-axis the amount specified by the angle parameter. Angles should be * specified in radians (values from 0 to PI*2) or converted to radians with the radians() function. * Objects are always rotated around their relative position to the origin and positive numbers * rotate objects in a counterclockwise direction. Transformations apply to everything that happens * after and subsequent calls to the function accumulates the effect. For example, calling rotateZ(PI/2) * and then rotateZ(PI/2) is the same as rotateZ(PI). If rotateZ() is called within the draw(), the * transformation is reset when the loop begins again. This function requires passing P3D or OPENGL * into the size() parameter as shown in the example above. * * @param {int|float} angleInRadians angle of rotation specified in radians * * @returns none * * @see rotateX * @see rotateY * @see rotate * @see translate * @see scale * @see popMatrix * @see pushMatrix */ Drawing2D.prototype.rotateZ = function() { throw "rotateZ() is not supported in 2D mode. Use rotate(float) instead."; };
Drawing3D.prototype.rotateZ = function(angleInRadians) { modelView.rotateZ(angleInRadians); modelViewInv.invRotateZ(angleInRadians); };
/** * Rotates a shape around the y-axis the amount specified by the angle parameter. Angles should be * specified in radians (values from 0 to PI*2) or converted to radians with the radians() function. * Objects are always rotated around their relative position to the origin and positive numbers * rotate objects in a counterclockwise direction. Transformations apply to everything that happens * after and subsequent calls to the function accumulates the effect. For example, calling rotateY(PI/2) * and then rotateY(PI/2) is the same as rotateY(PI). If rotateY() is called within the draw(), the * transformation is reset when the loop begins again. This function requires passing P3D or OPENGL * into the size() parameter as shown in the example above. * * @param {int|float} angleInRadians angle of rotation specified in radians * * @returns none * * @see rotateX * @see rotateZ * @see rotate * @see translate * @see scale * @see popMatrix * @see pushMatrix */ p.rotateY = function(angleInRadians) { modelView.rotateY(angleInRadians); modelViewInv.invRotateY(angleInRadians); };
/** * Rotates a shape the amount specified by the angle parameter. Angles should be specified in radians * (values from 0 to TWO_PI) or converted to radians with the radians() function. Objects are always * rotated around their relative position to the origin and positive numbers rotate objects in a * clockwise direction. Transformations apply to everything that happens after and subsequent calls * to the function accumulates the effect. For example, calling rotate(HALF_PI) and then rotate(HALF_PI) * is the same as rotate(PI). All tranformations are reset when draw() begins again. Technically, * rotate() multiplies the current transformation matrix by a rotation matrix. This function can be * further controlled by the pushMatrix() and popMatrix(). * * @param {int|float} angleInRadians angle of rotation specified in radians * * @returns none * * @see rotateX * @see rotateY * @see rotateZ * @see rotate * @see translate * @see scale * @see popMatrix * @see pushMatrix */ Drawing2D.prototype.rotate = function(angleInRadians) { modelView.rotateZ(angleInRadians); modelViewInv.invRotateZ(angleInRadians); curContext.rotate(angleInRadians); };
Drawing3D.prototype.rotate = function(angleInRadians) { if (arguments.length < 4) { p.rotateZ(angleInRadians); } else { modelView.rotate(angleInRadians, arguments[1], arguments[2], arguments[3]); modelViewInv.rotate((-angleInRadians), arguments[1], arguments[2], arguments[3]); } };
/** * Shears a shape around the x-axis the amount specified by the angle parameter. * Angles should be specified in radians (values from 0 to PI*2) or converted to radians * with the radians() function. Objects are always sheared around their relative position * to the origin and positive numbers shear objects in a clockwise direction. Transformations * apply to everything that happens after and subsequent calls to the function accumulates the * effect. For example, calling shearX(PI/2) and then shearX(PI/2) is the same as shearX(PI) * * @param {int|float} angleInRadians angle of rotation specified in radians * * @returns none * * @see rotateX * @see rotateY * @see rotateZ * @see rotate * @see translate * @see scale * @see popMatrix * @see pushMatrix */
Drawing2D.prototype.shearX = function(angleInRadians) { modelView.shearX(angleInRadians); curContext.transform(1,0,angleInRadians,1,0,0); };
Drawing3D.prototype.shearX = function(angleInRadians) { modelView.shearX(angleInRadians); };
/** * Shears a shape around the y-axis the amount specified by the angle parameter. * Angles should be specified in radians (values from 0 to PI*2) or converted to * radians with the radians() function. Objects are always sheared around their * relative position to the origin and positive numbers shear objects in a * clockwise direction. Transformations apply to everything that happens after * and subsequent calls to the function accumulates the effect. For example, * calling shearY(PI/2) and then shearY(PI/2) is the same as shearY(PI). * * @param {int|float} angleInRadians angle of rotation specified in radians * * @returns none * * @see rotateX * @see rotateY * @see rotateZ * @see rotate * @see translate * @see scale * @see popMatrix * @see pushMatrix * @see shearX */
Drawing2D.prototype.shearY = function(angleInRadians) { modelView.shearY(angleInRadians); curContext.transform(1,angleInRadians,0,1,0,0); };
Drawing3D.prototype.shearY = function(angleInRadians) { modelView.shearY(angleInRadians); };
/** * The pushStyle() function saves the current style settings and popStyle() restores the prior settings. * Note that these functions are always used together. They allow you to change the style settings and later * return to what you had. When a new style is started with pushStyle(), it builds on the current style information. * The pushStyle() and popStyle() functions can be embedded to provide more control (see the second example * above for a demonstration.) * The style information controlled by the following functions are included in the style: fill(), stroke(), tint(), * strokeWeight(), strokeCap(), strokeJoin(), imageMode(), rectMode(), ellipseMode(), shapeMode(), colorMode(), * textAlign(), textFont(), textMode(), textSize(), textLeading(), emissive(), specular(), shininess(), ambient() * * @returns none * * @see popStyle */ p.pushStyle = function() { // Save the canvas state. saveContext();
p.pushMatrix();
var newState = { 'doFill': doFill, 'currentFillColor': currentFillColor, 'doStroke': doStroke, 'currentStrokeColor': currentStrokeColor, 'curTint': curTint, 'curRectMode': curRectMode, 'curColorMode': curColorMode, 'colorModeX': colorModeX, 'colorModeZ': colorModeZ, 'colorModeY': colorModeY, 'colorModeA': colorModeA, 'curTextFont': curTextFont, 'horizontalTextAlignment': horizontalTextAlignment, 'verticalTextAlignment': verticalTextAlignment, 'textMode': textMode, 'curFontName': curFontName, 'curTextSize': curTextSize, 'curTextAscent': curTextAscent, 'curTextDescent': curTextDescent, 'curTextLeading': curTextLeading };
styleArray.push(newState); };
/** * The pushStyle() function saves the current style settings and popStyle() restores the prior settings; these * functions are always used together. They allow you to change the style settings and later return to what you had. * When a new style is started with pushStyle(), it builds on the current style information. The pushStyle() and * popStyle() functions can be embedded to provide more control (see the second example above for a demonstration.) * * @returns none * * @see pushStyle */ p.popStyle = function() { var oldState = styleArray.pop();
if (oldState) { restoreContext();
p.popMatrix();
doFill = oldState.doFill; currentFillColor = oldState.currentFillColor; doStroke = oldState.doStroke; currentStrokeColor = oldState.currentStrokeColor; curTint = oldState.curTint; curRectMode = oldState.curRectMode; curColorMode = oldState.curColorMode; colorModeX = oldState.colorModeX; colorModeZ = oldState.colorModeZ; colorModeY = oldState.colorModeY; colorModeA = oldState.colorModeA; curTextFont = oldState.curTextFont; curFontName = oldState.curFontName; curTextSize = oldState.curTextSize; horizontalTextAlignment = oldState.horizontalTextAlignment; verticalTextAlignment = oldState.verticalTextAlignment; textMode = oldState.textMode; curTextAscent = oldState.curTextAscent; curTextDescent = oldState.curTextDescent; curTextLeading = oldState.curTextLeading; } else { throw "Too many popStyle() without enough pushStyle()"; } };
//////////////////////////////////////////////////////////////////////////// // Time based functions ////////////////////////////////////////////////////////////////////////////
/** * Processing communicates with the clock on your computer. * The year() function returns the current year as an integer (2003, 2004, 2005, etc). * * @returns {float} The current year. * * @see millis * @see second * @see minute * @see hour * @see day * @see month */ p.year = function() { return new Date().getFullYear(); }; /** * Processing communicates with the clock on your computer. * The month() function returns the current month as a value from 1 - 12. * * @returns {float} The current month. * * @see millis * @see second * @see minute * @see hour * @see day * @see year */ p.month = function() { return new Date().getMonth() + 1; }; /** * Processing communicates with the clock on your computer. * The day() function returns the current day as a value from 1 - 31. * * @returns {float} The current day. * * @see millis * @see second * @see minute * @see hour * @see month * @see year */ p.day = function() { return new Date().getDate(); }; /** * Processing communicates with the clock on your computer. * The hour() function returns the current hour as a value from 0 - 23. * * @returns {float} The current hour. * * @see millis * @see second * @see minute * @see month * @see day * @see year */ p.hour = function() { return new Date().getHours(); }; /** * Processing communicates with the clock on your computer. * The minute() function returns the current minute as a value from 0 - 59. * * @returns {float} The current minute. * * @see millis * @see second * @see month * @see hour * @see day * @see year */ p.minute = function() { return new Date().getMinutes(); }; /** * Processing communicates with the clock on your computer. * The second() function returns the current second as a value from 0 - 59. * * @returns {float} The current minute. * * @see millis * @see month * @see minute * @see hour * @see day * @see year */ p.second = function() { return new Date().getSeconds(); }; /** * Returns the number of milliseconds (thousandths of a second) since starting a sketch. * This information is often used for timing animation sequences. * * @returns {long} The number of milliseconds since starting the sketch. * * @see month * @see second * @see minute * @see hour * @see day * @see year */ p.millis = function() { return Date.now() - start; };
/** * Executes the code within draw() one time. This functions allows the program to update * the display window only when necessary, for example when an event registered by * mousePressed() or keyPressed() occurs. * In structuring a program, it only makes sense to call redraw() within events such as * mousePressed(). This is because redraw() does not run draw() immediately (it only sets * a flag that indicates an update is needed). * Calling redraw() within draw() has no effect because draw() is continuously called anyway. * * @returns none * * @see noLoop * @see loop */ function redrawHelper() { var sec = (Date.now() - timeSinceLastFPS) / 1000; framesSinceLastFPS++; var fps = framesSinceLastFPS / sec;
// recalculate FPS every half second for better accuracy. if (sec > 0.5) { timeSinceLastFPS = Date.now(); framesSinceLastFPS = 0; p.__frameRate = fps; }
p.frameCount++; }
Drawing2D.prototype.redraw = function() { redrawHelper();
curContext.lineWidth = lineWidth; var pmouseXLastEvent = p.pmouseX, pmouseYLastEvent = p.pmouseY; p.pmouseX = pmouseXLastFrame; p.pmouseY = pmouseYLastFrame;
saveContext(); p.draw(); restoreContext();
pmouseXLastFrame = p.mouseX; pmouseYLastFrame = p.mouseY; p.pmouseX = pmouseXLastEvent; p.pmouseY = pmouseYLastEvent; };
Drawing3D.prototype.redraw = function() { redrawHelper();
var pmouseXLastEvent = p.pmouseX, pmouseYLastEvent = p.pmouseY; p.pmouseX = pmouseXLastFrame; p.pmouseY = pmouseYLastFrame; // even if the color buffer isn't cleared with background(), // the depth buffer needs to be cleared regardless. curContext.clear(curContext.DEPTH_BUFFER_BIT); curContextCache = { attributes: {}, locations: {} }; // Delete all the lighting states and the materials the // user set in the last draw() call. p.noLights(); p.lightFalloff(1, 0, 0); p.shininess(1); p.ambient(255, 255, 255); p.specular(0, 0, 0); p.emissive(0, 0, 0); p.camera(); p.draw();
pmouseXLastFrame = p.mouseX; pmouseYLastFrame = p.mouseY; p.pmouseX = pmouseXLastEvent; p.pmouseY = pmouseYLastEvent; };
/** * Stops Processing from continuously executing the code within draw(). If loop() is * called, the code in draw() begin to run continuously again. If using noLoop() in * setup(), it should be the last line inside the block. * When noLoop() is used, it's not possible to manipulate or access the screen inside event * handling functions such as mousePressed() or keyPressed(). Instead, use those functions * to call redraw() or loop(), which will run draw(), which can update the screen properly. * This means that when noLoop() has been called, no drawing can happen, and functions like * saveFrame() or loadPixels() may not be used. * Note that if the sketch is resized, redraw() will be called to update the sketch, even * after noLoop() has been specified. Otherwise, the sketch would enter an odd state until * loop() was called. * * @returns none * * @see redraw * @see draw * @see loop */ p.noLoop = function() { doLoop = false; loopStarted = false; clearInterval(looping); curSketch.onPause(); };
/** * Causes Processing to continuously execute the code within draw(). If noLoop() is called, * the code in draw() stops executing. * * @returns none * * @see noLoop */ p.loop = function() { if (loopStarted) { return; }
timeSinceLastFPS = Date.now(); framesSinceLastFPS = 0;
looping = window.setInterval(function() { try { curSketch.onFrameStart(); p.redraw(); curSketch.onFrameEnd(); } catch(e_loop) { window.clearInterval(looping); throw e_loop; } }, curMsPerFrame); doLoop = true; loopStarted = true; curSketch.onLoop(); };
/** * Specifies the number of frames to be displayed every second. If the processor is not * fast enough to maintain the specified rate, it will not be achieved. For example, the * function call frameRate(30) will attempt to refresh 30 times a second. It is recommended * to set the frame rate within setup(). The default rate is 60 frames per second. * * @param {int} aRate number of frames per second. * * @returns none * * @see delay */ p.frameRate = function(aRate) { curFrameRate = aRate; curMsPerFrame = 1000 / curFrameRate;
// clear and reset interval if (doLoop) { p.noLoop(); p.loop(); } };
/** * Quits/stops/exits the program. * Rather than terminating immediately, exit() will cause the sketch to exit after draw() * has completed (or after setup() completes if called during the setup() method). * * @returns none */ p.exit = function() { // cleanup window.clearInterval(looping); removeInstance(p.externals.canvas.id); delete(curElement.onmousedown);
// Step through the libraries to detach them for (var lib in Processing.lib) { if (Processing.lib.hasOwnProperty(lib)) { if (Processing.lib[lib].hasOwnProperty("detach")) { Processing.lib[lib].detach(p); } } }
// clean up all event handling var i = eventHandlers.length; while (i--) { detachEventHandler(eventHandlers[i]); } curSketch.onExit(); };
//////////////////////////////////////////////////////////////////////////// // MISC functions ////////////////////////////////////////////////////////////////////////////
/** * Sets the cursor to a predefined symbol, an image, or turns it on if already hidden. * If you are trying to set an image as the cursor, it is recommended to make the size * 16x16 or 32x32 pixels. It is not possible to load an image as the cursor if you are * exporting your program for the Web. The values for parameters x and y must be less * than the dimensions of the image. * * @param {MODE} MODE either ARROW, CROSS, HAND, MOVE, TEXT, WAIT * @param {PImage} image any variable of type PImage * @param {int} x the horizonal active spot of the cursor * @param {int} y the vertical active spot of the cursor * * @returns none * * @see noCursor */ p.cursor = function() { if (arguments.length > 1 || (arguments.length === 1 && arguments[0] instanceof p.PImage)) { var image = arguments[0], x, y; if (arguments.length >= 3) { x = arguments[1]; y = arguments[2]; if (x < 0 || y < 0 || y >= image.height || x >= image.width) { throw "x and y must be non-negative and less than the dimensions of the image"; } } else { x = image.width >>> 1; y = image.height >>> 1; }
// see https://developer.mozilla.org/en/Using_URL_values_for_the_cursor_property var imageDataURL = image.toDataURL(); var style = "url(\"" + imageDataURL + "\") " + x + " " + y + ", default"; curCursor = curElement.style.cursor = style; } else if (arguments.length === 1) { var mode = arguments[0]; curCursor = curElement.style.cursor = mode; } else { curCursor = curElement.style.cursor = oldCursor; } };
/** * Hides the cursor from view. * * @returns none * * @see cursor */ p.noCursor = function() { curCursor = curElement.style.cursor = PConstants.NOCURSOR; };
/** * Links to a webpage either in the same window or in a new window. The complete URL * must be specified. * * @param {String} href complete url as a String in quotes * @param {String} target name of the window to load the URL as a string in quotes * * @returns none */ p.link = function(href, target) { if (target !== undef) { window.open(href, target); } else { window.location = href; } };
// PGraphics methods // These functions exist only for compatibility with P5 p.beginDraw = noop; p.endDraw = noop;
/** * This function takes content from a canvas and turns it into an ImageData object to be used with a PImage * * @returns {ImageData} ImageData object to attach to a PImage (1D array of pixel data) * * @see PImage */ Drawing2D.prototype.toImageData = function(x, y, w, h) { x = x !== undef ? x : 0; y = y !== undef ? y : 0; w = w !== undef ? w : p.width; h = h !== undef ? h : p.height; return curContext.getImageData(x, y, w, h); };
Drawing3D.prototype.toImageData = function(x, y, w, h) { x = x !== undef ? x : 0; y = y !== undef ? y : 0; w = w !== undef ? w : p.width; h = h !== undef ? h : p.height; var c = document.createElement("canvas"), ctx = c.getContext("2d"), obj = ctx.createImageData(w, h), uBuff = new Uint8Array(w * h * 4); curContext.readPixels(x, y, w, h, curContext.RGBA, curContext.UNSIGNED_BYTE, uBuff); for (var i=0, ul=uBuff.length, obj_data=obj.data; i < ul; i++) { obj_data[i] = uBuff[(h - 1 - Math.floor(i / 4 / w)) * w * 4 + (i % (w * 4))]; } return obj; };
/** * Displays message in the browser's status area. This is the text area in the lower * left corner of the browser. The status() function will only work when the * Processing program is running in a web browser. * * @param {String} text any valid String * * @returns none */ p.status = function(text) { window.status = text; };
//////////////////////////////////////////////////////////////////////////// // Binary Functions ////////////////////////////////////////////////////////////////////////////
/** * Converts a byte, char, int, or color to a String containing the equivalent binary * notation. For example color(0, 102, 153, 255) will convert to the String * "11111111000000000110011010011001". This function can help make your geeky debugging * sessions much happier. * * @param {byte|char|int|color} num byte, char, int, color: value to convert * @param {int} numBits number of digits to return * * @returns {String} * * @see unhex * @see hex * @see unbinary */ p.binary = function(num, numBits) { var bit; if (numBits > 0) { bit = numBits; } else if(num instanceof Char) { bit = 16; num |= 0; // making it int } else { // autodetect, skipping zeros bit = 32; while (bit > 1 && !((num >>> (bit - 1)) & 1)) { bit--; } } var result = ""; while (bit > 0) { result += ((num >>> (--bit)) & 1) ? "1" : "0"; } return result; };
/** * Converts a String representation of a binary number to its equivalent integer value. * For example, unbinary("00001000") will return 8. * * @param {String} binaryString String * * @returns {Int} * * @see hex * @see binary * @see unbinary */ p.unbinary = function(binaryString) { var i = binaryString.length - 1, mask = 1, result = 0; while (i >= 0) { var ch = binaryString[i--]; if (ch !== '0' && ch !== '1') { throw "the value passed into unbinary was not an 8 bit binary number"; } if (ch === '1') { result += mask; } mask <<= 1; } return result; };
var decimalToHex = function(d, padding) { //if there is no padding value added, default padding to 8 else go into while statement. padding = (padding === undef || padding === null) ? padding = 8 : padding; if (d < 0) { d = 0xFFFFFFFF + d + 1; } var hex = Number(d).toString(16).toUpperCase(); while (hex.length < padding) { hex = "0" + hex; } if (hex.length >= padding) { hex = hex.substring(hex.length - padding, hex.length); } return hex; };
// note: since we cannot keep track of byte, int types by default the returned string is 8 chars long // if no 2nd argument is passed. closest compromise we can use to match java implementation Feb 5 2010 // also the char parser has issues with chars that are not digits or letters IE: !@#$%^&* /** * Converts a byte, char, int, or color to a String containing the equivalent hexadecimal notation. * For example color(0, 102, 153, 255) will convert to the String "FF006699". This function can help * make your geeky debugging sessions much happier. * * @param {byte|char|int|Color} value the value to turn into a hex string * @param {int} digits the number of digits to return * * @returns {String} * * @see unhex * @see binary * @see unbinary */ p.hex = function(value, len) { if (arguments.length === 1) { if (value instanceof Char) { len = 4; } else { // int or byte, indistinguishable at the moment, default to 8 len = 8; } } return decimalToHex(value, len); };
function unhexScalar(hex) { var value = parseInt("0x" + hex, 16);
// correct for int overflow java expectation if (value > 2147483647) { value -= 4294967296; } return value; }
/** * Converts a String representation of a hexadecimal number to its equivalent integer value. * * @param {String} hex the hex string to convert to an int * * @returns {int} * * @see hex * @see binary * @see unbinary */ p.unhex = function(hex) { if (hex instanceof Array) { var arr = []; for (var i = 0; i < hex.length; i++) { arr.push(unhexScalar(hex[i])); } return arr; } return unhexScalar(hex); };
// Load a file or URL into strings /** * Reads the contents of a file or url and creates a String array of its individual lines. * The filename parameter can also be a URL to a file found online. If the file is not available or an error occurs, * null will be returned and an error message will be printed to the console. The error message does not halt * the program. * * @param {String} filename name of the file or url to load * * @returns {String[]} * * @see loadBytes * @see saveStrings * @see saveBytes */ p.loadStrings = function(filename) { if (localStorage[filename]) { return localStorage[filename].split("\n"); }
var filecontent = ajax(filename); if(typeof filecontent !== "string" || filecontent === "") { return []; }
// deal with the fact that Windows uses \r\n, Unix uses \n, // Mac uses \r, and we actually expect \n filecontent = filecontent.replace(/(\r\n?)/g,"\n").replace(/\n$/,"");
return filecontent.split("\n"); };
// Writes an array of strings to a file, one line per string /** * Writes an array of strings to a file, one line per string. This file is saved to the localStorage. * * @param {String} filename name of the file to save to localStorage * @param {String[]} strings string array to be written * * @see loadBytes * @see loadStrings * @see saveBytes */ p.saveStrings = function(filename, strings) { localStorage[filename] = strings.join('\n'); };
/** * Reads the contents of a file or url and places it in a byte array. If a file is specified, it must be located in the localStorage. * The filename parameter can also be a URL to a file found online. * * @param {String} filename name of a file in the localStorage or a URL. * * @returns {byte[]} * * @see loadStrings * @see saveStrings * @see saveBytes */ p.loadBytes = function(url) { var string = ajax(url); var ret = [];
for (var i = 0; i < string.length; i++) { ret.push(string.charCodeAt(i)); }
return ret; };
//////////////////////////////////////////////////////////////////////////// // String Functions //////////////////////////////////////////////////////////////////////////// /** * The matchAll() function is identical to match(), except that it returns an array of all matches in * the specified String, rather than just the first. * * @param {String} aString the String to search inside * @param {String} aRegExp the regexp to be used for matching * * @return {String[]} returns an array of matches * * @see #match */ p.matchAll = function(aString, aRegExp) { var results = [], latest; var regexp = new RegExp(aRegExp, "g"); while ((latest = regexp.exec(aString)) !== null) { results.push(latest); if (latest[0].length === 0) { ++regexp.lastIndex; } } return results.length > 0 ? results : null; }; /** * The match() function matches a string with a regular expression, and returns the match as an * array. The first index is the matching expression, and array elements * [1] and higher represent each of the groups (sequences found in parens). * * @param {String} str the String to be searched * @param {String} regexp the regexp to be used for matching * * @return {String[]} an array of matching strings */ p.match = function(str, regexp) { return str.match(regexp); };
//////////////////////////////////////////////////////////////////////////// // Other java specific functions ////////////////////////////////////////////////////////////////////////////
var logBuffer = [];
/** * The println() function writes to the console area of the Processing environment. * Each call to this function creates a new line of output. Individual elements can be separated with quotes ("") and joined with the string concatenation operator (+). * * @param {String} message the string to write to the console * * @see #join * @see #print */ p.println = function(message) { Processing.logger.println(message); }; /** * The print() function writes to the console area of the Processing environment. * * @param {String} message the string to write to the console * * @see #join */ p.print = function(message) { Processing.logger.print(message); };
// Alphanumeric chars arguments automatically converted to numbers when // passed in, and will come out as numbers. p.str = function(val) { if (val instanceof Array) { var arr = []; for (var i = 0; i < val.length; i++) { arr.push(val[i].toString() + ""); } return arr; } return (val.toString() + ""); };
// Conversion function booleanScalar(val) { if (typeof val === 'number') { return val !== 0; } if (typeof val === 'boolean') { return val; } if (typeof val === 'string') { return val.toLowerCase() === 'true'; } if (val instanceof Char) { // 1, T or t return val.code === 49 || val.code === 84 || val.code === 116; } }
/** * Converts the passed parameter to the function to its boolean value. * It will return an array of booleans if an array is passed in. * * @param {int, byte, string} val the parameter to be converted to boolean * @param {int[], byte[], string[]} val the array to be converted to boolean[] * * @return {boolean|boolean[]} returns a boolean or an array of booleans */ p.parseBoolean = function (val) { if (val instanceof Array) { var ret = []; for (var i = 0; i < val.length; i++) { ret.push(booleanScalar(val[i])); } return ret; } return booleanScalar(val); };
/** * Converts the passed parameter to the function to its byte value. * A byte is a number between -128 and 127. * It will return an array of bytes if an array is passed in. * * @param {int, char} what the parameter to be conveted to byte * @param {int[], char[]} what the array to be converted to byte[] * * @return {byte|byte[]} returns a byte or an array of bytes */ p.parseByte = function(what) { if (what instanceof Array) { var bytes = []; for (var i = 0; i < what.length; i++) { bytes.push((0 - (what[i] & 0x80)) | (what[i] & 0x7F)); } return bytes; } return (0 - (what & 0x80)) | (what & 0x7F); };
/** * Converts the passed parameter to the function to its char value. * It will return an array of chars if an array is passed in. * * @param {int, byte} key the parameter to be conveted to char * @param {int[], byte[]} key the array to be converted to char[] * * @return {char|char[]} returns a char or an array of chars */ p.parseChar = function(key) { if (typeof key === "number") { return new Char(String.fromCharCode(key & 0xFFFF)); } if (key instanceof Array) { var ret = []; for (var i = 0; i < key.length; i++) { ret.push(new Char(String.fromCharCode(key[i] & 0xFFFF))); } return ret; } throw "char() may receive only one argument of type int, byte, int[], or byte[]."; };
// Processing doc claims good argument types are: int, char, byte, boolean, // String, int[], char[], byte[], boolean[], String[]. // floats should not work. However, floats with only zeroes right of the // decimal will work because JS converts those to int. function floatScalar(val) { if (typeof val === 'number') { return val; } if (typeof val === 'boolean') { return val ? 1 : 0; } if (typeof val === 'string') { return parseFloat(val); } if (val instanceof Char) { return val.code; } }
/** * Converts the passed parameter to the function to its float value. * It will return an array of floats if an array is passed in. * * @param {int, char, boolean, string} val the parameter to be conveted to float * @param {int[], char[], boolean[], string[]} val the array to be converted to float[] * * @return {float|float[]} returns a float or an array of floats */ p.parseFloat = function(val) { if (val instanceof Array) { var ret = []; for (var i = 0; i < val.length; i++) { ret.push(floatScalar(val[i])); } return ret; } return floatScalar(val); };
function intScalar(val, radix) { if (typeof val === 'number') { return val & 0xFFFFFFFF; } if (typeof val === 'boolean') { return val ? 1 : 0; } if (typeof val === 'string') { var number = parseInt(val, radix || 10); // Default to decimal radix. return number & 0xFFFFFFFF; } if (val instanceof Char) { return val.code; } }
/** * Converts the passed parameter to the function to its int value. * It will return an array of ints if an array is passed in. * * @param {string, char, boolean, float} val the parameter to be conveted to int * @param {string[], char[], boolean[], float[]} val the array to be converted to int[] * @param {int} radix optional the radix of the number (for js compatibility) * * @return {int|int[]} returns a int or an array of ints */ p.parseInt = function(val, radix) { if (val instanceof Array) { var ret = []; for (var i = 0; i < val.length; i++) { if (typeof val[i] === 'string' && !/^\s*[+\-]?\d+\s*$/.test(val[i])) { ret.push(0); } else { ret.push(intScalar(val[i], radix)); } } return ret; } return intScalar(val, radix); };
p.__int_cast = function(val) { return 0|val; };
p.__instanceof = function(obj, type) { if (typeof type !== "function") { throw "Function is expected as type argument for instanceof operator"; }
if (typeof obj === "string") { // special case for strings return type === Object || type === String; }
if (obj instanceof type) { // fast check if obj is already of type instance return true; }
if (typeof obj !== "object" || obj === null) { return false; // not an object or null }
var objType = obj.constructor; if (type.$isInterface) { // expecting the interface // queueing interfaces from type and its base classes var interfaces = []; while (objType) { if (objType.$interfaces) { interfaces = interfaces.concat(objType.$interfaces); } objType = objType.$base; } while (interfaces.length > 0) { var i = interfaces.shift(); if (i === type) { return true; } // wide search in base interfaces if (i.$interfaces) { interfaces = interfaces.concat(i.$interfaces); } } return false; }
while (objType.hasOwnProperty("$base")) { objType = objType.$base; if (objType === type) { return true; // object was found } }
return false; };
/** * Defines the dimension of the display window in units of pixels. The size() function must * be the first line in setup(). If size() is not called, the default size of the window is * 100x100 pixels. The system variables width and height are set by the parameters passed to * the size() function. * * @param {int} aWidth width of the display window in units of pixels * @param {int} aHeight height of the display window in units of pixels * @param {MODE} aMode Either P2D, P3D, JAVA2D, or OPENGL * * @see createGraphics * @see screen */ DrawingShared.prototype.size = function(aWidth, aHeight, aMode) { if (doStroke) { p.stroke(0); }
if (doFill) { p.fill(255); }
// The default 2d context has already been created in the p.init() stage if // a 3d context was not specified. This is so that a 2d context will be // available if size() was not called. var savedProperties = { fillStyle: curContext.fillStyle, strokeStyle: curContext.strokeStyle, lineCap: curContext.lineCap, lineJoin: curContext.lineJoin }; // remove the style width and height properties to ensure that the canvas gets set to // aWidth and aHeight coming in if (curElement.style.length > 0 ) { curElement.style.removeProperty("width"); curElement.style.removeProperty("height"); }
curElement.width = p.width = aWidth || 100; curElement.height = p.height = aHeight || 100;
for (var prop in savedProperties) { if (savedProperties.hasOwnProperty(prop)) { curContext[prop] = savedProperties[prop]; } }
// make sure to set the default font the first time round. p.textFont(curTextFont);
// Set the background to whatever it was called last as if background() was called before size() // If background() hasn't been called before, set background() to a light gray p.background();
// set 5% for pixels to cache (or 1000) maxPixelsCached = Math.max(1000, aWidth * aHeight * 0.05);
// Externalize the context p.externals.context = curContext;
for (var i = 0; i < PConstants.SINCOS_LENGTH; i++) { sinLUT[i] = p.sin(i * (PConstants.PI / 180) * 0.5); cosLUT[i] = p.cos(i * (PConstants.PI / 180) * 0.5); } };
Drawing2D.prototype.size = function(aWidth, aHeight, aMode) { if (curContext === undef) { // size() was called without p.init() default context, i.e. p.createGraphics() curContext = curElement.getContext("2d"); userMatrixStack = new PMatrixStack(); userReverseMatrixStack = new PMatrixStack(); modelView = new PMatrix2D(); modelViewInv = new PMatrix2D(); }
DrawingShared.prototype.size.apply(this, arguments); };
Drawing3D.prototype.size = (function() { var size3DCalled = false;
return function size(aWidth, aHeight, aMode) { if (size3DCalled) { throw "Multiple calls to size() for 3D renders are not allowed."; } size3DCalled = true;
function getGLContext(canvas) { var ctxNames = ['experimental-webgl', 'webgl', 'webkit-3d'], gl;
for (var i=0, l=ctxNames.length; i<l; i++) { gl = canvas.getContext(ctxNames[i], {antialias: false, preserveDrawingBuffer: true}); if (gl) { break; } }
return gl; }
// Get the 3D rendering context. try { // If the HTML <canvas> dimensions differ from the // dimensions specified in the size() call in the sketch, for // 3D sketches, browsers will either not render or render the // scene incorrectly. To fix this, we need to adjust the // width and height attributes of the canvas. curElement.width = p.width = aWidth || 100; curElement.height = p.height = aHeight || 100; curContext = getGLContext(curElement); canTex = curContext.createTexture(); textTex = curContext.createTexture(); } catch(e_size) { Processing.debug(e_size); }
if (!curContext) { throw "WebGL context is not supported on this browser."; }
// Set defaults curContext.viewport(0, 0, curElement.width, curElement.height); curContext.enable(curContext.DEPTH_TEST); curContext.enable(curContext.BLEND); curContext.blendFunc(curContext.SRC_ALPHA, curContext.ONE_MINUS_SRC_ALPHA);
// Create the program objects to render 2D (points, lines) and // 3D (spheres, boxes) shapes. Because 2D shapes are not lit, // lighting calculations are ommitted from this program object. programObject2D = createProgramObject(curContext, vertexShaderSrc2D, fragmentShaderSrc2D);
programObjectUnlitShape = createProgramObject(curContext, vertexShaderSrcUnlitShape, fragmentShaderSrcUnlitShape);
// Set the default point and line width for the 2D and unlit shapes. p.strokeWeight(1);
// Now that the programs have been compiled, we can set the default // states for the lights. programObject3D = createProgramObject(curContext, vertexShaderSrc3D, fragmentShaderSrc3D); curContext.useProgram(programObject3D);
// Assume we aren't using textures by default. uniformi("usingTexture3d", programObject3D, "usingTexture", usingTexture);
// Set some defaults. p.lightFalloff(1, 0, 0); p.shininess(1); p.ambient(255, 255, 255); p.specular(0, 0, 0); p.emissive(0, 0, 0);
// Create buffers for 3D primitives boxBuffer = curContext.createBuffer(); curContext.bindBuffer(curContext.ARRAY_BUFFER, boxBuffer); curContext.bufferData(curContext.ARRAY_BUFFER, boxVerts, curContext.STATIC_DRAW);
boxNormBuffer = curContext.createBuffer(); curContext.bindBuffer(curContext.ARRAY_BUFFER, boxNormBuffer); curContext.bufferData(curContext.ARRAY_BUFFER, boxNorms, curContext.STATIC_DRAW);
boxOutlineBuffer = curContext.createBuffer(); curContext.bindBuffer(curContext.ARRAY_BUFFER, boxOutlineBuffer); curContext.bufferData(curContext.ARRAY_BUFFER, boxOutlineVerts, curContext.STATIC_DRAW);
// used to draw the rectangle and the outline rectBuffer = curContext.createBuffer(); curContext.bindBuffer(curContext.ARRAY_BUFFER, rectBuffer); curContext.bufferData(curContext.ARRAY_BUFFER, rectVerts, curContext.STATIC_DRAW);
rectNormBuffer = curContext.createBuffer(); curContext.bindBuffer(curContext.ARRAY_BUFFER, rectNormBuffer); curContext.bufferData(curContext.ARRAY_BUFFER, rectNorms, curContext.STATIC_DRAW);
// The sphere vertices are specified dynamically since the user // can change the level of detail. Everytime the user does that // using sphereDetail(), the new vertices are calculated. sphereBuffer = curContext.createBuffer();
lineBuffer = curContext.createBuffer();
// Shape buffers fillBuffer = curContext.createBuffer(); fillColorBuffer = curContext.createBuffer(); strokeColorBuffer = curContext.createBuffer(); shapeTexVBO = curContext.createBuffer();
pointBuffer = curContext.createBuffer(); curContext.bindBuffer(curContext.ARRAY_BUFFER, pointBuffer); curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array([0, 0, 0]), curContext.STATIC_DRAW);
textBuffer = curContext.createBuffer(); curContext.bindBuffer(curContext.ARRAY_BUFFER, textBuffer ); curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array([1,1,0,-1,1,0,-1,-1,0,1,-1,0]), curContext.STATIC_DRAW);
textureBuffer = curContext.createBuffer(); curContext.bindBuffer(curContext.ARRAY_BUFFER, textureBuffer); curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array([0,0,1,0,1,1,0,1]), curContext.STATIC_DRAW);
indexBuffer = curContext.createBuffer(); curContext.bindBuffer(curContext.ELEMENT_ARRAY_BUFFER, indexBuffer); curContext.bufferData(curContext.ELEMENT_ARRAY_BUFFER, new Uint16Array([0,1,2,2,3,0]), curContext.STATIC_DRAW);
cam = new PMatrix3D(); cameraInv = new PMatrix3D(); modelView = new PMatrix3D(); modelViewInv = new PMatrix3D(); projection = new PMatrix3D(); p.camera(); p.perspective();
userMatrixStack = new PMatrixStack(); userReverseMatrixStack = new PMatrixStack(); // used by both curve and bezier, so just init here curveBasisMatrix = new PMatrix3D(); curveToBezierMatrix = new PMatrix3D(); curveDrawMatrix = new PMatrix3D(); bezierDrawMatrix = new PMatrix3D(); bezierBasisInverse = new PMatrix3D(); bezierBasisMatrix = new PMatrix3D(); bezierBasisMatrix.set(-1, 3, -3, 1, 3, -6, 3, 0, -3, 3, 0, 0, 1, 0, 0, 0);
DrawingShared.prototype.size.apply(this, arguments); }; }());
//////////////////////////////////////////////////////////////////////////// // Lights ////////////////////////////////////////////////////////////////////////////
/** * Adds an ambient light. Ambient light doesn't come from a specific direction, * the rays have light have bounced around so much that objects are evenly lit * from all sides. Ambient lights are almost always used in combination with * other types of lights. Lights need to be included in the draw() to * remain persistent in a looping program. Placing them in the setup() * of a looping program will cause them to only have an effect the first time * through the loop. The effect of the parameters is determined by the current * color mode. * * @param {int | float} r red or hue value * @param {int | float} g green or hue value * @param {int | float} b blue or hue value * * @param {int | float} x x position of light (used for falloff) * @param {int | float} y y position of light (used for falloff) * @param {int | float} z z position of light (used for falloff) * * @returns none * * @see lights * @see directionalLight * @see pointLight * @see spotLight */ Drawing2D.prototype.ambientLight = DrawingShared.prototype.a3DOnlyFunction;
Drawing3D.prototype.ambientLight = function(r, g, b, x, y, z) { if (lightCount === PConstants.MAX_LIGHTS) { throw "can only create " + PConstants.MAX_LIGHTS + " lights"; }
var pos = new PVector(x, y, z); var view = new PMatrix3D(); view.scale(1, -1, 1); view.apply(modelView.array()); view.mult(pos, pos);
// Instead of calling p.color, we do the calculations ourselves to // reduce property lookups. var col = color$4(r, g, b, 0); var normalizedCol = [ ((col & PConstants.RED_MASK) >>> 16) / 255, ((col & PConstants.GREEN_MASK) >>> 8) / 255, (col & PConstants.BLUE_MASK) / 255 ];
curContext.useProgram(programObject3D); uniformf("uLights.color.3d." + lightCount, programObject3D, "uLights" + lightCount + ".color", normalizedCol); uniformf("uLights.position.3d." + lightCount, programObject3D, "uLights" + lightCount + ".position", pos.array()); uniformi("uLights.type.3d." + lightCount, programObject3D, "uLights" + lightCount + ".type", 0); uniformi("uLightCount3d", programObject3D, "uLightCount", ++lightCount); };
/** * Adds a directional light. Directional light comes from one direction and * is stronger when hitting a surface squarely and weaker if it hits at a * gentle angle. After hitting a surface, a directional lights scatters in * all directions. Lights need to be included in the draw() to remain * persistent in a looping program. Placing them in the setup() of a * looping program will cause them to only have an effect the first time * through the loop. The affect of the
r</b>,
g</b>, and
b</b> * parameters is determined by the current color mode. The nx, * ny, and nz parameters specify the direction the light is * facing. For example, setting ny to -1 will cause the geometry to be * lit from below (the light is facing directly upward). * * @param {int | float} r red or hue value * @param {int | float} g green or hue value * @param {int | float} b blue or hue value * * @param {int | float} nx direction along the x axis * @param {int | float} ny direction along the y axis * @param {int | float} nz direction along the z axis * * @returns none * * @see lights * @see ambientLight * @see pointLight * @see spotLight */ Drawing2D.prototype.directionalLight = DrawingShared.prototype.a3DOnlyFunction;
Drawing3D.prototype.directionalLight = function(r, g, b, nx, ny, nz) { if (lightCount === PConstants.MAX_LIGHTS) { throw "can only create " + PConstants.MAX_LIGHTS + " lights"; }
curContext.useProgram(programObject3D);
var mvm = new PMatrix3D(); mvm.scale(1, -1, 1); mvm.apply(modelView.array()); mvm = mvm.array();
// We need to multiply the direction by the model view matrix, but // the mult function checks the w component of the vector, if it isn't // present, it uses 1, so we manually multiply. var dir = [ mvm[0] * nx + mvm[4] * ny + mvm[8] * nz, mvm[1] * nx + mvm[5] * ny + mvm[9] * nz, mvm[2] * nx + mvm[6] * ny + mvm[10] * nz ];
// Instead of calling p.color, we do the calculations ourselves to // reduce property lookups. var col = color$4(r, g, b, 0); var normalizedCol = [ ((col & PConstants.RED_MASK) >>> 16) / 255, ((col & PConstants.GREEN_MASK) >>> 8) / 255, (col & PConstants.BLUE_MASK) / 255 ];
uniformf("uLights.color.3d." + lightCount, programObject3D, "uLights" + lightCount + ".color", normalizedCol); uniformf("uLights.position.3d." + lightCount, programObject3D, "uLights" + lightCount + ".position", dir); uniformi("uLights.type.3d." + lightCount, programObject3D, "uLights" + lightCount + ".type", 1); uniformi("uLightCount3d", programObject3D, "uLightCount", ++lightCount); };
/** * Sets the falloff rates for point lights, spot lights, and ambient lights. * The parameters are used to determine the falloff with the following equation: * * d = distance from light position to vertex position * falloff = 1 / (CONSTANT + d * LINEAR + (d*d) * QUADRATIC) * * Like fill(), it affects only the elements which are created after it in the * code. The default value if LightFalloff(1.0, 0.0, 0.0). Thinking about an * ambient light with a falloff can be tricky. It is used, for example, if you * wanted a region of your scene to be lit ambiently one color and another region * to be lit ambiently by another color, you would use an ambient light with location * and falloff. You can think of it as a point light that doesn't care which direction * a surface is facing. * * @param {int | float} constant constant value for determining falloff * @param {int | float} linear linear value for determining falloff * @param {int | float} quadratic quadratic value for determining falloff * * @returns none * * @see lights * @see ambientLight * @see pointLight * @see spotLight * @see lightSpecular */ Drawing2D.prototype.lightFalloff = DrawingShared.prototype.a3DOnlyFunction;
Drawing3D.prototype.lightFalloff = function(constant, linear, quadratic) { curContext.useProgram(programObject3D); uniformf("uFalloff3d", programObject3D, "uFalloff", [constant, linear, quadratic]); };
/** * Sets the specular color for lights. Like fill(), it affects only the * elements which are created after it in the code. Specular refers to light * which bounces off a surface in a perferred direction (rather than bouncing * in all directions like a diffuse light) and is used for creating highlights. * The specular quality of a light interacts with the specular material qualities * set through the specular() and shininess() functions. * * @param {int | float} r red or hue value * @param {int | float} g green or hue value * @param {int | float} b blue or hue value * * @returns none * * @see lights * @see ambientLight * @see pointLight * @see spotLight */ Drawing2D.prototype.lightSpecular = DrawingShared.prototype.a3DOnlyFunction;
Drawing3D.prototype.lightSpecular = function(r, g, b) {
// Instead of calling p.color, we do the calculations ourselves to // reduce property lookups. var col = color$4(r, g, b, 0); var normalizedCol = [ ((col & PConstants.RED_MASK) >>> 16) / 255, ((col & PConstants.GREEN_MASK) >>> 8) / 255, (col & PConstants.BLUE_MASK) / 255 ];
curContext.useProgram(programObject3D); uniformf("uSpecular3d", programObject3D, "uSpecular", normalizedCol); };
/** * Sets the default ambient light, directional light, falloff, and specular * values. The defaults are ambientLight(128, 128, 128) and * directionalLight(128, 128, 128, 0, 0, -1), lightFalloff(1, 0, 0), and * lightSpecular(0, 0, 0). Lights need to be included in the draw() to remain * persistent in a looping program. Placing them in the setup() of a looping * program will cause them to only have an effect the first time through the * loop. * * @returns none * * @see ambientLight * @see directionalLight * @see pointLight * @see spotLight * @see noLights * */ p.lights = function() { p.ambientLight(128, 128, 128); p.directionalLight(128, 128, 128, 0, 0, -1); p.lightFalloff(1, 0, 0); p.lightSpecular(0, 0, 0); };
/** * Adds a point light. Lights need to be included in the draw() to remain * persistent in a looping program. Placing them in the setup() of a * looping program will cause them to only have an effect the first time through * the loop. The affect of the r, g, and b parameters * is determined by the current color mode. The x, y, and z * parameters set the position of the light. * * @param {int | float} r red or hue value * @param {int | float} g green or hue value * @param {int | float} b blue or hue value * @param {int | float} x x coordinate of the light * @param {int | float} y y coordinate of the light * @param {int | float} z z coordinate of the light * * @returns none * * @see lights * @see directionalLight * @see ambientLight * @see spotLight */ Drawing2D.prototype.pointLight = DrawingShared.prototype.a3DOnlyFunction;
Drawing3D.prototype.pointLight = function(r, g, b, x, y, z) { if (lightCount === PConstants.MAX_LIGHTS) { throw "can only create " + PConstants.MAX_LIGHTS + " lights"; }
// Place the point in view space once instead of once per vertex // in the shader. var pos = new PVector(x, y, z); var view = new PMatrix3D(); view.scale(1, -1, 1); view.apply(modelView.array()); view.mult(pos, pos);
// Instead of calling p.color, we do the calculations ourselves to // reduce property lookups. var col = color$4(r, g, b, 0); var normalizedCol = [ ((col & PConstants.RED_MASK) >>> 16) / 255, ((col & PConstants.GREEN_MASK) >>> 8) / 255, (col & PConstants.BLUE_MASK) / 255 ];
curContext.useProgram(programObject3D); uniformf("uLights.color.3d." + lightCount, programObject3D, "uLights" + lightCount + ".color", normalizedCol); uniformf("uLights.position.3d." + lightCount, programObject3D, "uLights" + lightCount + ".position", pos.array()); uniformi("uLights.type.3d." + lightCount, programObject3D, "uLights" + lightCount + ".type", 2); uniformi("uLightCount3d", programObject3D, "uLightCount", ++lightCount); };
/** * Disable all lighting. Lighting is turned off by default and enabled with * the lights() method. This function can be used to disable lighting so * that 2D geometry (which does not require lighting) can be drawn after a * set of lighted 3D geometry. * * @returns none * * @see lights */ Drawing2D.prototype.noLights = DrawingShared.prototype.a3DOnlyFunction;
Drawing3D.prototype.noLights = function() { lightCount = 0; curContext.useProgram(programObject3D); uniformi("uLightCount3d", programObject3D, "uLightCount", lightCount); };
/** * Adds a spot light. Lights need to be included in the draw() to * remain persistent in a looping program. Placing them in the setup() * of a looping program will cause them to only have an effect the first time * through the loop. The affect of the r, g, and b parameters * is determined by the current color mode. The x, y, and z * parameters specify the position of the light and nx, ny, nz * specify the direction or light. The angle parameter affects angle of the * spotlight cone. * * @param {int | float} r red or hue value * @param {int | float} g green or hue value * @param {int | float} b blue or hue value * @param {int | float} x coordinate of the light * @param {int | float} y coordinate of the light * @param {int | float} z coordinate of the light * @param {int | float} nx direction along the x axis * @param {int | float} ny direction along the y axis * @param {int | float} nz direction along the z axis * @param {float} angle angle of the spotlight cone * @param {float} concentration exponent determining the center bias of the cone * * @returns none * * @see lights * @see directionalLight * @see ambientLight * @see pointLight */ Drawing2D.prototype.spotLight = DrawingShared.prototype.a3DOnlyFunction;
Drawing3D.prototype.spotLight = function(r, g, b, x, y, z, nx, ny, nz, angle, concentration) { if (lightCount === PConstants.MAX_LIGHTS) { throw "can only create " + PConstants.MAX_LIGHTS + " lights"; }
curContext.useProgram(programObject3D);
// multiply the position and direction by the model view matrix // once per object rather than once per vertex. var pos = new PVector(x, y, z); var mvm = new PMatrix3D(); mvm.scale(1, -1, 1); mvm.apply(modelView.array()); mvm.mult(pos, pos);
// Convert to array since we need to directly access the elements. mvm = mvm.array();
// We need to multiply the direction by the model view matrix, but // the mult function checks the w component of the vector, if it isn't // present, it uses 1, so we use a very small value as a work around. var dir = [ mvm[0] * nx + mvm[4] * ny + mvm[8] * nz, mvm[1] * nx + mvm[5] * ny + mvm[9] * nz, mvm[2] * nx + mvm[6] * ny + mvm[10] * nz ];
// Instead of calling p.color, we do the calculations ourselves to // reduce property lookups. var col = color$4(r, g, b, 0); var normalizedCol = [ ((col & PConstants.RED_MASK) >>> 16) / 255, ((col & PConstants.GREEN_MASK) >>> 8) / 255, (col & PConstants.BLUE_MASK) / 255 ];
uniformf("uLights.color.3d." + lightCount, programObject3D, "uLights" + lightCount + ".color", normalizedCol); uniformf("uLights.position.3d." + lightCount, programObject3D, "uLights" + lightCount + ".position", pos.array()); uniformf("uLights.direction.3d." + lightCount, programObject3D, "uLights" + lightCount + ".direction", dir); uniformf("uLights.concentration.3d." + lightCount, programObject3D, "uLights" + lightCount + ".concentration", concentration); uniformf("uLights.angle.3d." + lightCount, programObject3D, "uLights" + lightCount + ".angle", angle); uniformi("uLights.type.3d." + lightCount, programObject3D, "uLights" + lightCount + ".type", 3); uniformi("uLightCount3d", programObject3D, "uLightCount", ++lightCount); };
//////////////////////////////////////////////////////////////////////////// // Camera functions ////////////////////////////////////////////////////////////////////////////
/** * The beginCamera() and endCamera() functions enable advanced customization of the camera space. * The functions are useful if you want to more control over camera movement, however for most users, the camera() * function will be sufficient.
The camera functions will replace any transformations (such as rotate() * or translate()) that occur before them in draw(), but they will not automatically replace the camera * transform itself. For this reason, camera functions should be placed at the beginning of draw() (so that * transformations happen afterwards), and the camera() function can be used after beginCamera() if * you want to reset the camera before applying transformations.
This function sets the matrix mode to the * camera matrix so calls such as translate(), rotate(), applyMatrix() and resetMatrix() affect the camera. * beginCamera() should always be used with a following endCamera() and pairs of beginCamera() and * endCamera() cannot be nested. * * @see camera * @see endCamera * @see applyMatrix * @see resetMatrix * @see translate * @see rotate * @see scale */ Drawing2D.prototype.beginCamera = function() { throw ("beginCamera() is not available in 2D mode"); };
Drawing3D.prototype.beginCamera = function() { if (manipulatingCamera) { throw ("You cannot call beginCamera() again before calling endCamera()"); } manipulatingCamera = true; modelView = cameraInv; modelViewInv = cam; };
/** * The beginCamera() and endCamera() functions enable advanced customization of the camera space. * Please see the reference for beginCamera() for a description of how the functions are used. * * @see beginCamera */ Drawing2D.prototype.endCamera = function() { throw ("endCamera() is not available in 2D mode"); };
Drawing3D.prototype.endCamera = function() { if (!manipulatingCamera) { throw ("You cannot call endCamera() before calling beginCamera()"); } modelView.set(cam); modelViewInv.set(cameraInv); manipulatingCamera = false; };
/** * Sets the position of the camera through setting the eye position, the center of the scene, and which axis is facing * upward. Moving the eye position and the direction it is pointing (the center of the scene) allows the images to be * seen from different angles. The version without any parameters sets the camera to the default position, pointing to * the center of the display window with the Y axis as up. The default values are camera(width/2.0, height/2.0, * (height/2.0) / tan(PI*60.0 / 360.0), width/2.0, height/2.0, 0, 0, 1, 0). This function is similar to gluLookAt() * in OpenGL, but it first clears the current camera settings. * * @param {float} eyeX x-coordinate for the eye * @param {float} eyeY y-coordinate for the eye * @param {float} eyeZ z-coordinate for the eye * @param {float} centerX x-coordinate for the center of the scene * @param {float} centerY y-coordinate for the center of the scene * @param {float} centerZ z-coordinate for the center of the scene * @param {float} upX usually 0.0, 1.0, -1.0 * @param {float} upY usually 0.0, 1.0, -1.0 * @param {float} upZ usually 0.0, 1.0, -1.0 * * @see beginCamera * @see endCamera * @see frustum */ p.camera = function(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ) { if (eyeX === undef) { // Workaround if createGraphics is used. cameraX = p.width / 2; cameraY = p.height / 2; cameraZ = cameraY / Math.tan(cameraFOV / 2); eyeX = cameraX; eyeY = cameraY; eyeZ = cameraZ; centerX = cameraX; centerY = cameraY; centerZ = 0; upX = 0; upY = 1; upZ = 0; }
var z = new PVector(eyeX - centerX, eyeY - centerY, eyeZ - centerZ); var y = new PVector(upX, upY, upZ); z.normalize(); var x = PVector.cross(y, z); y = PVector.cross(z, x); x.normalize(); y.normalize();
var xX = x.x, xY = x.y, xZ = x.z;
var yX = y.x, yY = y.y, yZ = y.z;
var zX = z.x, zY = z.y, zZ = z.z;
cam.set(xX, xY, xZ, 0, yX, yY, yZ, 0, zX, zY, zZ, 0, 0, 0, 0, 1);
cam.translate(-eyeX, -eyeY, -eyeZ);
cameraInv.reset(); cameraInv.invApply(xX, xY, xZ, 0, yX, yY, yZ, 0, zX, zY, zZ, 0, 0, 0, 0, 1);
cameraInv.translate(eyeX, eyeY, eyeZ);
modelView.set(cam); modelViewInv.set(cameraInv); };
/** * Sets a perspective projection applying foreshortening, making distant objects appear smaller than closer ones. The * parameters define a viewing volume with the shape of truncated pyramid. Objects near to the front of the volume appear * their actual size, while farther objects appear smaller. This projection simulates the perspective of the world more * accurately than orthographic projection. The version of perspective without parameters sets the default perspective and * the version with four parameters allows the programmer to set the area precisely. The default values are: * perspective(PI/3.0, width/height, cameraZ/10.0, cameraZ*10.0) where cameraZ is ((height/2.0) / tan(PI*60.0/360.0)); * * @param {float} fov field-of-view angle (in radians) for vertical direction * @param {float} aspect ratio of width to height * @param {float} zNear z-position of nearest clipping plane * @param {float} zFar z-positions of farthest clipping plane */ p.perspective = function(fov, aspect, near, far) { if (arguments.length === 0) { //in case canvas is resized cameraY = curElement.height / 2; cameraZ = cameraY / Math.tan(cameraFOV / 2); cameraNear = cameraZ / 10; cameraFar = cameraZ * 10; cameraAspect = p.width / p.height; fov = cameraFOV; aspect = cameraAspect; near = cameraNear; far = cameraFar; }
var yMax, yMin, xMax, xMin; yMax = near * Math.tan(fov / 2); yMin = -yMax; xMax = yMax * aspect; xMin = yMin * aspect; p.frustum(xMin, xMax, yMin, yMax, near, far); };
/** * Sets a perspective matrix defined through the parameters. Works like glFrustum, except it wipes out the current * perspective matrix rather than muliplying itself with it. * * @param {float} left left coordinate of the clipping plane * @param {float} right right coordinate of the clipping plane * @param {float} bottom bottom coordinate of the clipping plane * @param {float} top top coordinate of the clipping plane * @param {float} near near coordinate of the clipping plane * @param {float} far far coordinate of the clipping plane * * @see beginCamera * @see camera * @see endCamera * @see perspective */ Drawing2D.prototype.frustum = function() { throw("Processing.js: frustum() is not supported in 2D mode"); };
Drawing3D.prototype.frustum = function(left, right, bottom, top, near, far) { frustumMode = true; projection = new PMatrix3D(); projection.set((2 * near) / (right - left), 0, (right + left) / (right - left), 0, 0, (2 * near) / (top - bottom), (top + bottom) / (top - bottom), 0, 0, 0, -(far + near) / (far - near), -(2 * far * near) / (far - near), 0, 0, -1, 0); var proj = new PMatrix3D(); proj.set(projection); proj.transpose(); curContext.useProgram(programObject2D); uniformMatrix("projection2d", programObject2D, "uProjection", false, proj.array()); curContext.useProgram(programObject3D); uniformMatrix("projection3d", programObject3D, "uProjection", false, proj.array()); curContext.useProgram(programObjectUnlitShape); uniformMatrix("uProjectionUS", programObjectUnlitShape, "uProjection", false, proj.array()); };
/** * Sets an orthographic projection and defines a parallel clipping volume. All objects with the same dimension appear * the same size, regardless of whether they are near or far from the camera. The parameters to this function specify * the clipping volume where left and right are the minimum and maximum x values, top and bottom are the minimum and * maximum y values, and near and far are the minimum and maximum z values. If no parameters are given, the default * is used: ortho(0, width, 0, height, -10, 10). * * @param {float} left left plane of the clipping volume * @param {float} right right plane of the clipping volume * @param {float} bottom bottom plane of the clipping volume * @param {float} top top plane of the clipping volume * @param {float} near maximum distance from the origin to the viewer * @param {float} far maximum distance from the origin away from the viewer */ p.ortho = function(left, right, bottom, top, near, far) { if (arguments.length === 0) { left = 0; right = p.width; bottom = 0; top = p.height; near = -10; far = 10; }
var x = 2 / (right - left); var y = 2 / (top - bottom); var z = -2 / (far - near);
var tx = -(right + left) / (right - left); var ty = -(top + bottom) / (top - bottom); var tz = -(far + near) / (far - near);
projection = new PMatrix3D(); projection.set(x, 0, 0, tx, 0, y, 0, ty, 0, 0, z, tz, 0, 0, 0, 1);
var proj = new PMatrix3D(); proj.set(projection); proj.transpose(); curContext.useProgram(programObject2D); uniformMatrix("projection2d", programObject2D, "uProjection", false, proj.array()); curContext.useProgram(programObject3D); uniformMatrix("projection3d", programObject3D, "uProjection", false, proj.array()); curContext.useProgram(programObjectUnlitShape); uniformMatrix("uProjectionUS", programObjectUnlitShape, "uProjection", false, proj.array()); frustumMode = false; }; /** * The printProjection() prints the current projection matrix to the text window. */ p.printProjection = function() { projection.print(); }; /** * The printCamera() function prints the current camera matrix. */ p.printCamera = function() { cam.print(); };
//////////////////////////////////////////////////////////////////////////// // Shapes //////////////////////////////////////////////////////////////////////////// /** * The box() function renders a box. A box is an extruded rectangle. A box with equal dimension on all sides is a cube. * Calling this function with only one parameter will create a cube. * * @param {int|float} w dimension of the box in the x-dimension * @param {int|float} h dimension of the box in the y-dimension * @param {int|float} d dimension of the box in the z-dimension */ Drawing2D.prototype.box = DrawingShared.prototype.a3DOnlyFunction;
Drawing3D.prototype.box = function(w, h, d) { // user can uniformly scale the box by // passing in only one argument. if (!h || !d) { h = d = w; }
// Modeling transformation var model = new PMatrix3D(); model.scale(w, h, d);
// Viewing transformation needs to have Y flipped // becuase that's what Processing does. var view = new PMatrix3D(); view.scale(1, -1, 1); view.apply(modelView.array()); view.transpose();
if (doFill) { curContext.useProgram(programObject3D); uniformMatrix("model3d", programObject3D, "uModel", false, model.array()); uniformMatrix("view3d", programObject3D, "uView", false, view.array()); // Fix stitching problems. (lines get occluded by triangles // since they share the same depth values). This is not entirely // working, but it's a start for drawing the outline. So // developers can start playing around with styles. curContext.enable(curContext.POLYGON_OFFSET_FILL); curContext.polygonOffset(1, 1); uniformf("color3d", programObject3D, "uColor", fillStyle);
// Calculating the normal matrix can be expensive, so only // do it if it's necessary. if(lightCount > 0){ // Create the normal transformation matrix. var v = new PMatrix3D(); v.set(view);
var m = new PMatrix3D(); m.set(model);
v.mult(m);
var normalMatrix = new PMatrix3D(); normalMatrix.set(v); normalMatrix.invert(); normalMatrix.transpose();
uniformMatrix("uNormalTransform3d", programObject3D, "uNormalTransform", false, normalMatrix.array()); vertexAttribPointer("aNormal3d", programObject3D, "aNormal", 3, boxNormBuffer); } else{ disableVertexAttribPointer("aNormal3d", programObject3D, "aNormal"); }
vertexAttribPointer("aVertex3d", programObject3D, "aVertex", 3, boxBuffer);
// Turn off per vertex colors. disableVertexAttribPointer("aColor3d", programObject3D, "aColor"); disableVertexAttribPointer("aTexture3d", programObject3D, "aTexture");
curContext.drawArrays(curContext.TRIANGLES, 0, boxVerts.length / 3); curContext.disable(curContext.POLYGON_OFFSET_FILL); }
// Draw the box outline. if (lineWidth > 0 && doStroke) { curContext.useProgram(programObject2D); uniformMatrix("uModel2d", programObject2D, "uModel", false, model.array()); uniformMatrix("uView2d", programObject2D, "uView", false, view.array()); uniformf("uColor2d", programObject2D, "uColor", strokeStyle); uniformi("uIsDrawingText2d", programObject2D, "uIsDrawingText", false); vertexAttribPointer("vertex2d", programObject2D, "aVertex", 3, boxOutlineBuffer); disableVertexAttribPointer("aTextureCoord2d", programObject2D, "aTextureCoord"); curContext.drawArrays(curContext.LINES, 0, boxOutlineVerts.length / 3); } };
/** * The initSphere() function is a helper function used by sphereDetail() * This function creates and stores sphere vertices every time the user changes sphere detail. * * @see #sphereDetail */ var initSphere = function() { var i; sphereVerts = [];
for (i = 0; i < sphereDetailU; i++) { sphereVerts.push(0); sphereVerts.push(-1); sphereVerts.push(0); sphereVerts.push(sphereX[i]); sphereVerts.push(sphereY[i]); sphereVerts.push(sphereZ[i]); } sphereVerts.push(0); sphereVerts.push(-1); sphereVerts.push(0); sphereVerts.push(sphereX[0]); sphereVerts.push(sphereY[0]); sphereVerts.push(sphereZ[0]);
var v1, v11, v2;
// middle rings var voff = 0; for (i = 2; i < sphereDetailV; i++) { v1 = v11 = voff; voff += sphereDetailU; v2 = voff; for (var j = 0; j < sphereDetailU; j++) { sphereVerts.push(sphereX[v1]); sphereVerts.push(sphereY[v1]); sphereVerts.push(sphereZ[v1++]); sphereVerts.push(sphereX[v2]); sphereVerts.push(sphereY[v2]); sphereVerts.push(sphereZ[v2++]); }
// close each ring v1 = v11; v2 = voff;
sphereVerts.push(sphereX[v1]); sphereVerts.push(sphereY[v1]); sphereVerts.push(sphereZ[v1]); sphereVerts.push(sphereX[v2]); sphereVerts.push(sphereY[v2]); sphereVerts.push(sphereZ[v2]); }
// add the northern cap for (i = 0; i < sphereDetailU; i++) { v2 = voff + i;
sphereVerts.push(sphereX[v2]); sphereVerts.push(sphereY[v2]); sphereVerts.push(sphereZ[v2]); sphereVerts.push(0); sphereVerts.push(1); sphereVerts.push(0); }
sphereVerts.push(sphereX[voff]); sphereVerts.push(sphereY[voff]); sphereVerts.push(sphereZ[voff]); sphereVerts.push(0); sphereVerts.push(1); sphereVerts.push(0);
//set the buffer data curContext.bindBuffer(curContext.ARRAY_BUFFER, sphereBuffer); curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array(sphereVerts), curContext.STATIC_DRAW); };
/** * The sphereDetail() function controls the detail used to render a sphere by adjusting the number of * vertices of the sphere mesh. The default resolution is 30, which creates * a fairly detailed sphere definition with vertices every 360/30 = 12 * degrees. If you're going to render a great number of spheres per frame, * it is advised to reduce the level of detail using this function. * The setting stays active until sphereDetail() is called again with * a new parameter and so should not be called prior to every * sphere() statement, unless you wish to render spheres with * different settings, e.g. using less detail for smaller spheres or ones * further away from the camera. To control the detail of the horizontal * and vertical resolution independently, use the version of the functions * with two parameters. Calling this function with one parameter sets the number of segments *(minimum of 3) used per full circle revolution. This is equivalent to calling the function with * two identical values. * * @param {int} ures number of segments used horizontally (longitudinally) per full circle revolution * @param {int} vres number of segments used vertically (latitudinally) from top to bottom * * @see #sphere() */ p.sphereDetail = function(ures, vres) { var i;
if (arguments.length === 1) { ures = vres = arguments[0]; }
if (ures < 3) { ures = 3; } // force a minimum res if (vres < 2) { vres = 2; } // force a minimum res // if it hasn't changed do nothing if ((ures === sphereDetailU) && (vres === sphereDetailV)) { return; }
var delta = PConstants.SINCOS_LENGTH / ures; var cx = new Float32Array(ures); var cz = new Float32Array(ures); // calc unit circle in XZ plane for (i = 0; i < ures; i++) { cx[i] = cosLUT[((i * delta) % PConstants.SINCOS_LENGTH) | 0]; cz[i] = sinLUT[((i * delta) % PConstants.SINCOS_LENGTH) | 0]; }
// computing vertexlist // vertexlist starts at south pole var vertCount = ures * (vres - 1) + 2; var currVert = 0;
// re-init arrays to store vertices sphereX = new Float32Array(vertCount); sphereY = new Float32Array(vertCount); sphereZ = new Float32Array(vertCount);
var angle_step = (PConstants.SINCOS_LENGTH * 0.5) / vres; var angle = angle_step;
// step along Y axis for (i = 1; i < vres; i++) { var curradius = sinLUT[(angle % PConstants.SINCOS_LENGTH) | 0]; var currY = -cosLUT[(angle % PConstants.SINCOS_LENGTH) | 0]; for (var j = 0; j < ures; j++) { sphereX[currVert] = cx[j] * curradius; sphereY[currVert] = currY; sphereZ[currVert++] = cz[j] * curradius; } angle += angle_step; } sphereDetailU = ures; sphereDetailV = vres;
// make the sphere verts and norms initSphere(); };
/** * The sphere() function draws a sphere with radius r centered at coordinate 0, 0, 0. * A sphere is a hollow ball made from tessellated triangles. * * @param {int|float} r the radius of the sphere */ Drawing2D.prototype.sphere = DrawingShared.prototype.a3DOnlyFunction;
Drawing3D.prototype.sphere = function() { var sRad = arguments[0];
if ((sphereDetailU < 3) || (sphereDetailV < 2)) { p.sphereDetail(30); }
// Modeling transformation. var model = new PMatrix3D(); model.scale(sRad, sRad, sRad);
// viewing transformation needs to have Y flipped // becuase that's what Processing does. var view = new PMatrix3D(); view.scale(1, -1, 1); view.apply(modelView.array()); view.transpose();
if (doFill) { // Calculating the normal matrix can be expensive, so only // do it if it's necessary. if(lightCount > 0){ // Create a normal transformation matrix. var v = new PMatrix3D(); v.set(view);
var m = new PMatrix3D(); m.set(model);
v.mult(m);
var normalMatrix = new PMatrix3D(); normalMatrix.set(v); normalMatrix.invert(); normalMatrix.transpose();
uniformMatrix("uNormalTransform3d", programObject3D, "uNormalTransform", false, normalMatrix.array()); vertexAttribPointer("aNormal3d", programObject3D, "aNormal", 3, sphereBuffer); } else{ disableVertexAttribPointer("aNormal3d", programObject3D, "aNormal"); }
curContext.useProgram(programObject3D); disableVertexAttribPointer("aTexture3d", programObject3D, "aTexture");
uniformMatrix("uModel3d", programObject3D, "uModel", false, model.array()); uniformMatrix("uView3d", programObject3D, "uView", false, view.array()); vertexAttribPointer("aVertex3d", programObject3D, "aVertex", 3, sphereBuffer);
// Turn off per vertex colors. disableVertexAttribPointer("aColor3d", programObject3D, "aColor");
// fix stitching problems. (lines get occluded by triangles // since they share the same depth values). This is not entirely // working, but it's a start for drawing the outline. So // developers can start playing around with styles. curContext.enable(curContext.POLYGON_OFFSET_FILL); curContext.polygonOffset(1, 1); uniformf("uColor3d", programObject3D, "uColor", fillStyle); curContext.drawArrays(curContext.TRIANGLE_STRIP, 0, sphereVerts.length / 3); curContext.disable(curContext.POLYGON_OFFSET_FILL); }
// Draw the sphere outline. if (lineWidth > 0 && doStroke) { curContext.useProgram(programObject2D); uniformMatrix("uModel2d", programObject2D, "uModel", false, model.array()); uniformMatrix("uView2d", programObject2D, "uView", false, view.array()); vertexAttribPointer("aVertex2d", programObject2D, "aVertex", 3, sphereBuffer); disableVertexAttribPointer("aTextureCoord2d", programObject2D, "aTextureCoord"); uniformf("uColor2d", programObject2D, "uColor", strokeStyle); uniformi("uIsDrawingText", programObject2D, "uIsDrawingText", false); curContext.drawArrays(curContext.LINE_STRIP, 0, sphereVerts.length / 3); } };
//////////////////////////////////////////////////////////////////////////// // Coordinates ////////////////////////////////////////////////////////////////////////////
/** * Returns the three-dimensional X, Y, Z position in model space. This returns * the X value for a given coordinate based on the current set of transformations * (scale, rotate, translate, etc.) The X value can be used to place an object * in space relative to the location of the original point once the transformations * are no longer in use.
*
* * @param {int | float} x 3D x coordinate to be mapped * @param {int | float} y 3D y coordinate to be mapped * @param {int | float} z 3D z coordinate to be mapped * * @returns {float} * * @see modelY * @see modelZ */ p.modelX = function(x, y, z) { var mv = modelView.array(); var ci = cameraInv.array();
var ax = mv[0] * x + mv[1] * y + mv[2] * z + mv[3]; var ay = mv[4] * x + mv[5] * y + mv[6] * z + mv[7]; var az = mv[8] * x + mv[9] * y + mv[10] * z + mv[11]; var aw = mv[12] * x + mv[13] * y + mv[14] * z + mv[15];
var ox = ci[0] * ax + ci[1] * ay + ci[2] * az + ci[3] * aw; var ow = ci[12] * ax + ci[13] * ay + ci[14] * az + ci[15] * aw;
return (ow !== 0) ? ox / ow : ox; };
/** * Returns the three-dimensional X, Y, Z position in model space. This returns * the Y value for a given coordinate based on the current set of transformations * (scale, rotate, translate, etc.) The Y value can be used to place an object in * space relative to the location of the original point once the transformations * are no longer in use.
*
* * @param {int | float} x 3D x coordinate to be mapped * @param {int | float} y 3D y coordinate to be mapped * @param {int | float} z 3D z coordinate to be mapped * * @returns {float} * * @see modelX * @see modelZ */ p.modelY = function(x, y, z) { var mv = modelView.array(); var ci = cameraInv.array();
var ax = mv[0] * x + mv[1] * y + mv[2] * z + mv[3]; var ay = mv[4] * x + mv[5] * y + mv[6] * z + mv[7]; var az = mv[8] * x + mv[9] * y + mv[10] * z + mv[11]; var aw = mv[12] * x + mv[13] * y + mv[14] * z + mv[15];
var oy = ci[4] * ax + ci[5] * ay + ci[6] * az + ci[7] * aw; var ow = ci[12] * ax + ci[13] * ay + ci[14] * az + ci[15] * aw;
return (ow !== 0) ? oy / ow : oy; };
/** * Returns the three-dimensional X, Y, Z position in model space. This returns * the Z value for a given coordinate based on the current set of transformations * (scale, rotate, translate, etc.) The Z value can be used to place an object in * space relative to the location of the original point once the transformations * are no longer in use. * * @param {int | float} x 3D x coordinate to be mapped * @param {int | float} y 3D y coordinate to be mapped * @param {int | float} z 3D z coordinate to be mapped * * @returns {float} * * @see modelX * @see modelY */ p.modelZ = function(x, y, z) { var mv = modelView.array(); var ci = cameraInv.array();
var ax = mv[0] * x + mv[1] * y + mv[2] * z + mv[3]; var ay = mv[4] * x + mv[5] * y + mv[6] * z + mv[7]; var az = mv[8] * x + mv[9] * y + mv[10] * z + mv[11]; var aw = mv[12] * x + mv[13] * y + mv[14] * z + mv[15];
var oz = ci[8] * ax + ci[9] * ay + ci[10] * az + ci[11] * aw; var ow = ci[12] * ax + ci[13] * ay + ci[14] * az + ci[15] * aw;
return (ow !== 0) ? oz / ow : oz; };
//////////////////////////////////////////////////////////////////////////// // Material Properties ////////////////////////////////////////////////////////////////////////////
/** * Sets the ambient reflectance for shapes drawn to the screen. This is * combined with the ambient light component of environment. The color * components set through the parameters define the reflectance. For example in * the default color mode, setting v1=255, v2=126, v3=0, would cause all the * red light to reflect and half of the green light to reflect. Used in combination * with emissive(), specular(), and shininess() in setting * the materal properties of shapes. * * @param {int | float} gray * * @returns none * * @see emissive * @see specular * @see shininess */ Drawing2D.prototype.ambient = DrawingShared.prototype.a3DOnlyFunction;
Drawing3D.prototype.ambient = function(v1, v2, v3) { curContext.useProgram(programObject3D); uniformi("uUsingMat3d", programObject3D, "uUsingMat", true); var col = p.color(v1, v2, v3); uniformf("uMaterialAmbient3d", programObject3D, "uMaterialAmbient", p.color.toGLArray(col).slice(0, 3)); };
/** * Sets the emissive color of the material used for drawing shapes * drawn to the screen. Used in combination with ambient(), specular(), * and shininess() in setting the material properties of shapes. * * Can be called in the following ways: * * emissive(gray) * @param {int | float} gray number specifying value between white and black * * emissive(color) * @param {color} color any value of the color datatype * * emissive(v1, v2, v3) * @param {int | float} v1 red or hue value * @param {int | float} v2 green or saturation value * @param {int | float} v3 blue or brightness value * * @returns none * * @see ambient * @see specular * @see shininess */ Drawing2D.prototype.emissive = DrawingShared.prototype.a3DOnlyFunction;
Drawing3D.prototype.emissive = function(v1, v2, v3) { curContext.useProgram(programObject3D); uniformi("uUsingMat3d", programObject3D, "uUsingMat", true); var col = p.color(v1, v2, v3); uniformf("uMaterialEmissive3d", programObject3D, "uMaterialEmissive", p.color.toGLArray(col).slice(0, 3)); };
/** * Sets the amount of gloss in the surface of shapes. Used in combination with * ambient(), specular(), and emissive() in setting the * material properties of shapes. * * @param {float} shine degree of shininess * * @returns none */ Drawing2D.prototype.shininess = DrawingShared.prototype.a3DOnlyFunction;
Drawing3D.prototype.shininess = function(shine) { curContext.useProgram(programObject3D); uniformi("uUsingMat3d", programObject3D, "uUsingMat", true); uniformf("uShininess3d", programObject3D, "uShininess", shine); };
/** * Sets the specular color of the materials used for shapes drawn to the screen, * which sets the color of hightlights. Specular refers to light which bounces * off a surface in a perferred direction (rather than bouncing in all directions * like a diffuse light). Used in combination with emissive(), ambient(), and * shininess() in setting the material properties of shapes. * * Can be called in the following ways: * * specular(gray) * @param {int | float} gray number specifying value between white and black * * specular(gray, alpha) * @param {int | float} gray number specifying value between white and black * @param {int | float} alpha opacity * * specular(color) * @param {color} color any value of the color datatype * * specular(v1, v2, v3) * @param {int | float} v1 red or hue value * @param {int | float} v2 green or saturation value * @param {int | float} v3 blue or brightness value * * specular(v1, v2, v3, alpha) * @param {int | float} v1 red or hue value * @param {int | float} v2 green or saturation value * @param {int | float} v3 blue or brightness value * @param {int | float} alpha opacity * * @returns none * * @see ambient * @see emissive * @see shininess */ Drawing2D.prototype.specular = DrawingShared.prototype.a3DOnlyFunction;
Drawing3D.prototype.specular = function(v1, v2, v3) { curContext.useProgram(programObject3D); uniformi("uUsingMat3d", programObject3D, "uUsingMat", true); var col = p.color(v1, v2, v3); uniformf("uMaterialSpecular3d", programObject3D, "uMaterialSpecular", p.color.toGLArray(col).slice(0, 3)); };
//////////////////////////////////////////////////////////////////////////// // Coordinates ////////////////////////////////////////////////////////////////////////////
/** * Takes a three-dimensional X, Y, Z position and returns the X value for * where it will appear on a (two-dimensional) screen. * * @param {int | float} x 3D x coordinate to be mapped * @param {int | float} y 3D y coordinate to be mapped * @param {int | float} z 3D z optional coordinate to be mapped * * @returns {float} * * @see screenY * @see screenZ */ p.screenX = function( x, y, z ) { var mv = modelView.array(); if( mv.length === 16 ) { var ax = mv[ 0]*x + mv[ 1]*y + mv[ 2]*z + mv[ 3]; var ay = mv[ 4]*x + mv[ 5]*y + mv[ 6]*z + mv[ 7]; var az = mv[ 8]*x + mv[ 9]*y + mv[10]*z + mv[11]; var aw = mv[12]*x + mv[13]*y + mv[14]*z + mv[15];
var pj = projection.array();
var ox = pj[ 0]*ax + pj[ 1]*ay + pj[ 2]*az + pj[ 3]*aw; var ow = pj[12]*ax + pj[13]*ay + pj[14]*az + pj[15]*aw;
if ( ow !== 0 ){ ox /= ow; } return p.width * ( 1 + ox ) / 2.0; } // We assume that we're in 2D return modelView.multX(x, y); };
/** * Takes a three-dimensional X, Y, Z position and returns the Y value for * where it will appear on a (two-dimensional) screen. * * @param {int | float} x 3D x coordinate to be mapped * @param {int | float} y 3D y coordinate to be mapped * @param {int | float} z 3D z optional coordinate to be mapped * * @returns {float} * * @see screenX * @see screenZ */ p.screenY = function screenY( x, y, z ) { var mv = modelView.array(); if( mv.length === 16 ) { var ax = mv[ 0]*x + mv[ 1]*y + mv[ 2]*z + mv[ 3]; var ay = mv[ 4]*x + mv[ 5]*y + mv[ 6]*z + mv[ 7]; var az = mv[ 8]*x + mv[ 9]*y + mv[10]*z + mv[11]; var aw = mv[12]*x + mv[13]*y + mv[14]*z + mv[15];
var pj = projection.array();
var oy = pj[ 4]*ax + pj[ 5]*ay + pj[ 6]*az + pj[ 7]*aw; var ow = pj[12]*ax + pj[13]*ay + pj[14]*az + pj[15]*aw;
if ( ow !== 0 ){ oy /= ow; } return p.height * ( 1 + oy ) / 2.0; } // We assume that we're in 2D return modelView.multY(x, y); };
/** * Takes a three-dimensional X, Y, Z position and returns the Z value for * where it will appear on a (two-dimensional) screen. * * @param {int | float} x 3D x coordinate to be mapped * @param {int | float} y 3D y coordinate to be mapped * @param {int | float} z 3D z coordinate to be mapped * * @returns {float} * * @see screenX * @see screenY */ p.screenZ = function screenZ( x, y, z ) { var mv = modelView.array(); if( mv.length !== 16 ) { return 0; }
var pj = projection.array();
var ax = mv[ 0]*x + mv[ 1]*y + mv[ 2]*z + mv[ 3]; var ay = mv[ 4]*x + mv[ 5]*y + mv[ 6]*z + mv[ 7]; var az = mv[ 8]*x + mv[ 9]*y + mv[10]*z + mv[11]; var aw = mv[12]*x + mv[13]*y + mv[14]*z + mv[15];
var oz = pj[ 8]*ax + pj[ 9]*ay + pj[10]*az + pj[11]*aw; var ow = pj[12]*ax + pj[13]*ay + pj[14]*az + pj[15]*aw;
if ( ow !== 0 ) { oz /= ow; } return ( oz + 1 ) / 2.0; };
//////////////////////////////////////////////////////////////////////////// // Style functions //////////////////////////////////////////////////////////////////////////// /** * The fill() function sets the color used to fill shapes. For example, if you run fill(204, 102, 0), all subsequent shapes will be filled with orange. * This color is either specified in terms of the RGB or HSB color depending on the current colorMode() *(the default color space is RGB, with each value in the range from 0 to 255). *
When using hexadecimal notation to specify a color, use "#" or "0x" before the values (e.g. #CCFFAA, 0xFFCCFFAA). * The # syntax uses six digits to specify a color (the way colors are specified in HTML and CSS). When using the hexadecimal notation starting with "0x", * the hexadecimal value must be specified with eight characters; the first two characters define the alpha component and the remainder the red, green, and blue components. *
The value for the parameter "gray" must be less than or equal to the current maximum value as specified by colorMode(). The default maximum value is 255. *
To change the color of an image (or a texture), use tint(). * * @param {int|float} gray number specifying value between white and black * @param {int|float} value1 red or hue value * @param {int|float} value2 green or saturation value * @param {int|float} value3 blue or brightness value * @param {int|float} alpha opacity of the fill * @param {Color} color any value of the color datatype * @param {int} hex color value in hexadecimal notation (i.e. #FFCC00 or 0xFFFFCC00) * * @see #noFill() * @see #stroke() * @see #tint() * @see #background() * @see #colorMode() */ DrawingShared.prototype.fill = function() { var color = p.color.apply(this, arguments); if(color === currentFillColor && doFill) { return; } doFill = true; currentFillColor = color; };
Drawing2D.prototype.fill = function() { DrawingShared.prototype.fill.apply(this, arguments); isFillDirty = true; };
Drawing3D.prototype.fill = function() { DrawingShared.prototype.fill.apply(this, arguments); fillStyle = p.color.toGLArray(currentFillColor); };
function executeContextFill() { if(doFill) { if(isFillDirty) { curContext.fillStyle = p.color.toString(currentFillColor); isFillDirty = false; } curContext.fill(); } }
/** * The noFill() function disables filling geometry. If both noStroke() and noFill() * are called, no shapes will be drawn to the screen. * * @see #fill() * */ p.noFill = function() { doFill = false; };
/** * The stroke() function sets the color used to draw lines and borders around shapes. This color * is either specified in terms of the RGB or HSB color depending on the * current colorMode() (the default color space is RGB, with each * value in the range from 0 to 255). *
When using hexadecimal notation to specify a color, use "#" or * "0x" before the values (e.g. #CCFFAA, 0xFFCCFFAA). The # syntax uses six * digits to specify a color (the way colors are specified in HTML and CSS). * When using the hexadecimal notation starting with "0x", the hexadecimal * value must be specified with eight characters; the first two characters * define the alpha component and the remainder the red, green, and blue * components. *
The value for the parameter "gray" must be less than or equal * to the current maximum value as specified by colorMode(). * The default maximum value is 255. * * @param {int|float} gray number specifying value between white and black * @param {int|float} value1 red or hue value * @param {int|float} value2 green or saturation value * @param {int|float} value3 blue or brightness value * @param {int|float} alpha opacity of the stroke * @param {Color} color any value of the color datatype * @param {int} hex color value in hexadecimal notation (i.e. #FFCC00 or 0xFFFFCC00) * * @see #fill() * @see #noStroke() * @see #tint() * @see #background() * @see #colorMode() */ DrawingShared.prototype.stroke = function() { var color = p.color.apply(this, arguments); if(color === currentStrokeColor && doStroke) { return; } doStroke = true; currentStrokeColor = color; };
Drawing2D.prototype.stroke = function() { DrawingShared.prototype.stroke.apply(this, arguments); isStrokeDirty = true; };
Drawing3D.prototype.stroke = function() { DrawingShared.prototype.stroke.apply(this, arguments); strokeStyle = p.color.toGLArray(currentStrokeColor); };
function executeContextStroke() { if(doStroke) { if(isStrokeDirty) { curContext.strokeStyle = p.color.toString(currentStrokeColor); isStrokeDirty = false; } curContext.stroke(); } }
/** * The noStroke() function disables drawing the stroke (outline). If both noStroke() and * noFill() are called, no shapes will be drawn to the screen. * * @see #stroke() */ p.noStroke = function() { doStroke = false; };
/** * The strokeWeight() function sets the width of the stroke used for lines, points, and the border around shapes. * All widths are set in units of pixels. * * @param {int|float} w the weight (in pixels) of the stroke */ DrawingShared.prototype.strokeWeight = function(w) { lineWidth = w; };
Drawing2D.prototype.strokeWeight = function(w) { DrawingShared.prototype.strokeWeight.apply(this, arguments); curContext.lineWidth = w; };
Drawing3D.prototype.strokeWeight = function(w) { DrawingShared.prototype.strokeWeight.apply(this, arguments);
// Processing groups the weight of points and lines under this one function, // but for WebGL, we need to set a uniform for points and call a function for line.
curContext.useProgram(programObject2D); uniformf("pointSize2d", programObject2D, "uPointSize", w);
curContext.useProgram(programObjectUnlitShape); uniformf("pointSizeUnlitShape", programObjectUnlitShape, "uPointSize", w);
curContext.lineWidth(w); };
/** * The strokeCap() function sets the style for rendering line endings. These ends are either squared, extended, or rounded and * specified with the corresponding parameters SQUARE, PROJECT, and ROUND. The default cap is ROUND. * This function is not available with the P2D, P3D, or OPENGL renderers * * @param {int} value Either SQUARE, PROJECT, or ROUND */ p.strokeCap = function(value) { drawing.$ensureContext().lineCap = value; };
/** * The strokeJoin() function sets the style of the joints which connect line segments. * These joints are either mitered, beveled, or rounded and specified with the corresponding parameters MITER, BEVEL, and ROUND. The default joint is MITER. * This function is not available with the P2D, P3D, or OPENGL renderers * * @param {int} value Either SQUARE, PROJECT, or ROUND */ p.strokeJoin = function(value) { drawing.$ensureContext().lineJoin = value; };
/** * The smooth() function draws all geometry with smooth (anti-aliased) edges. This will slow down the frame rate of the application, * but will enhance the visual refinement.
* Note that smooth() will also improve image quality of resized images, and noSmooth() will disable image (and font) smoothing altogether. * When working with a 3D sketch, smooth will draw points as circles rather than squares. * * @see #noSmooth() * @see #hint() * @see #size() */
Drawing2D.prototype.smooth = function() { renderSmooth = true; var style = curElement.style; style.setProperty("image-rendering", "optimizeQuality", "important"); style.setProperty("-ms-interpolation-mode", "bicubic", "important"); if (curContext.hasOwnProperty("mozImageSmoothingEnabled")) { curContext.mozImageSmoothingEnabled = true; } };
Drawing3D.prototype.smooth = function(){ renderSmooth = true; };
/** * The noSmooth() function draws all geometry with jagged (aliased) edges. * * @see #smooth() */
Drawing2D.prototype.noSmooth = function() { renderSmooth = false; var style = curElement.style; style.setProperty("image-rendering", "optimizeSpeed", "important"); style.setProperty("image-rendering", "-moz-crisp-edges", "important"); style.setProperty("image-rendering", "-webkit-optimize-contrast", "important"); style.setProperty("image-rendering", "optimize-contrast", "important"); style.setProperty("-ms-interpolation-mode", "nearest-neighbor", "important"); if (curContext.hasOwnProperty("mozImageSmoothingEnabled")) { curContext.mozImageSmoothingEnabled = false; } };
Drawing3D.prototype.noSmooth = function(){ renderSmooth = false; };
//////////////////////////////////////////////////////////////////////////// // Vector drawing functions //////////////////////////////////////////////////////////////////////////// /** * The point() function draws a point, a coordinate in space at the dimension of one pixel. * The first parameter is the horizontal value for the point, the second * value is the vertical value for the point, and the optional third value * is the depth value. Drawing this shape in 3D using the z * parameter requires the P3D or OPENGL parameter in combination with * size as shown in the above example. * * @param {int|float} x x-coordinate of the point * @param {int|float} y y-coordinate of the point * @param {int|float} z z-coordinate of the point * * @see #beginShape() */ Drawing2D.prototype.point = function(x, y) { if (!doStroke) { return; }
x = Math.round(x); y = Math.round(y); curContext.fillStyle = p.color.toString(currentStrokeColor); isFillDirty = true; // Draw a circle for any point larger than 1px if (lineWidth > 1) { curContext.beginPath(); curContext.arc(x, y, lineWidth / 2, 0, PConstants.TWO_PI, false); curContext.fill(); } else { curContext.fillRect(x, y, 1, 1); } };
Drawing3D.prototype.point = function(x, y, z) { var model = new PMatrix3D();
// move point to position model.translate(x, y, z || 0); model.transpose();
var view = new PMatrix3D(); view.scale(1, -1, 1); view.apply(modelView.array()); view.transpose();
curContext.useProgram(programObject2D); uniformMatrix("uModel2d", programObject2D, "uModel", false, model.array()); uniformMatrix("uView2d", programObject2D, "uView", false, view.array());
if (lineWidth > 0 && doStroke) { // this will be replaced with the new bit shifting color code uniformf("uColor2d", programObject2D, "uColor", strokeStyle); uniformi("uIsDrawingText2d", programObject2D, "uIsDrawingText", false); uniformi("uSmooth2d", programObject2D, "uSmooth", renderSmooth); vertexAttribPointer("aVertex2d", programObject2D, "aVertex", 3, pointBuffer); disableVertexAttribPointer("aTextureCoord2d", programObject2D, "aTextureCoord"); curContext.drawArrays(curContext.POINTS, 0, 1); } };
/** * Using the beginShape() and endShape() functions allow creating more complex forms. * beginShape() begins recording vertices for a shape and endShape() stops recording. * The value of the MODE parameter tells it which types of shapes to create from the provided vertices. * With no mode specified, the shape can be any irregular polygon. After calling the beginShape() function, * a series of vertex() commands must follow. To stop drawing the shape, call endShape(). * The vertex() function with two parameters specifies a position in 2D and the vertex() * function with three parameters specifies a position in 3D. Each shape will be outlined with the current * stroke color and filled with the fill color. * * @param {int} MODE either POINTS, LINES, TRIANGLES, TRIANGLE_FAN, TRIANGLE_STRIP, QUADS, and QUAD_STRIP. * * @see endShape * @see vertex * @see curveVertex * @see bezierVertex */ p.beginShape = function(type) { curShape = type; curvePoints = []; vertArray = []; };
/** * All shapes are constructed by connecting a series of vertices. vertex() is used to specify the vertex * coordinates for points, lines, triangles, quads, and polygons and is used exclusively within the beginShape() * and endShape() function.
Drawing a vertex in 3D using the z parameter requires the P3D or * OPENGL parameter in combination with size as shown in the above example.
This function is also used to map a * texture onto the geometry. The texture() function declares the texture to apply to the geometry and the u * and v coordinates set define the mapping of this texture to the form. By default, the coordinates used for * u and v are specified in relation to the image's size in pixels, but this relation can be changed with * textureMode(). * * @param {int | float} x x-coordinate of the vertex * @param {int | float} y y-coordinate of the vertex * @param {boolean} moveto flag to indicate whether this is a new subpath * * @see beginShape * @see endShape * @see bezierVertex * @see curveVertex * @see texture */
Drawing2D.prototype.vertex = function(x, y, moveTo) { var vert = [];
if (firstVert) { firstVert = false; } vert.isVert = true;
vert[0] = x; vert[1] = y; vert[2] = 0; vert[3] = 0; vert[4] = 0;
// fill and stroke color vert[5] = currentFillColor; vert[6] = currentStrokeColor;
vertArray.push(vert); if (moveTo) { vertArray[vertArray.length-1].moveTo = moveTo; } };
Drawing3D.prototype.vertex = function(x, y, z, u, v) { var vert = [];
if (firstVert) { firstVert = false; } vert.isVert = true;
if (v === undef && usingTexture) { v = u; u = z; z = 0; }
// Convert u and v to normalized coordinates if (u !== undef && v !== undef) { if (curTextureMode === PConstants.IMAGE) { u /= curTexture.width; v /= curTexture.height; } u = u > 1 ? 1 : u; u = u < 0 ? 0 : u; v = v > 1 ? 1 : v; v = v < 0 ? 0 : v; }
vert[0] = x; vert[1] = y; vert[2] = z || 0; vert[3] = u || 0; vert[4] = v || 0;
// fill rgba vert[5] = fillStyle[0]; vert[6] = fillStyle[1]; vert[7] = fillStyle[2]; vert[8] = fillStyle[3]; // stroke rgba vert[9] = strokeStyle[0]; vert[10] = strokeStyle[1]; vert[11] = strokeStyle[2]; vert[12] = strokeStyle[3]; //normals vert[13] = normalX; vert[14] = normalY; vert[15] = normalZ;
vertArray.push(vert); };
/** * @private * Renders 3D points created from calls to vertex and beginShape/endShape * * @param {Array} vArray an array of vertex coordinate * @param {Array} cArray an array of colours used for the vertices * * @see beginShape * @see endShape * @see vertex */ var point3D = function(vArray, cArray){ var view = new PMatrix3D(); view.scale(1, -1, 1); view.apply(modelView.array()); view.transpose();
curContext.useProgram(programObjectUnlitShape);
uniformMatrix("uViewUS", programObjectUnlitShape, "uView", false, view.array()); uniformi("uSmoothUS", programObjectUnlitShape, "uSmooth", renderSmooth);
vertexAttribPointer("aVertexUS", programObjectUnlitShape, "aVertex", 3, pointBuffer); curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array(vArray), curContext.STREAM_DRAW);
vertexAttribPointer("aColorUS", programObjectUnlitShape, "aColor", 4, fillColorBuffer); curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array(cArray), curContext.STREAM_DRAW);
curContext.drawArrays(curContext.POINTS, 0, vArray.length/3); };
/** * @private * Renders 3D lines created from calls to beginShape/vertex/endShape - based on the mode specified LINES, LINE_LOOP, etc. * * @param {Array} vArray an array of vertex coordinate * @param {String} mode either LINES, LINE_LOOP, or LINE_STRIP * @param {Array} cArray an array of colours used for the vertices * * @see beginShape * @see endShape * @see vertex */ var line3D = function(vArray, mode, cArray){ var ctxMode; if (mode === "LINES"){ ctxMode = curContext.LINES; } else if(mode === "LINE_LOOP"){ ctxMode = curContext.LINE_LOOP; } else{ ctxMode = curContext.LINE_STRIP; }
var view = new PMatrix3D(); view.scale(1, -1, 1); view.apply(modelView.array()); view.transpose();
curContext.useProgram(programObjectUnlitShape); uniformMatrix("uViewUS", programObjectUnlitShape, "uView", false, view.array()); vertexAttribPointer("aVertexUS", programObjectUnlitShape, "aVertex", 3, lineBuffer); curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array(vArray), curContext.STREAM_DRAW); vertexAttribPointer("aColorUS", programObjectUnlitShape, "aColor", 4, strokeColorBuffer); curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array(cArray), curContext.STREAM_DRAW); curContext.drawArrays(ctxMode, 0, vArray.length/3); };
/** * @private * Render filled shapes created from calls to beginShape/vertex/endShape - based on the mode specified TRIANGLES, etc. * * @param {Array} vArray an array of vertex coordinate * @param {String} mode either LINES, LINE_LOOP, or LINE_STRIP * @param {Array} cArray an array of colours used for the vertices * @param {Array} tArray an array of u,v coordinates for textures * * @see beginShape * @see endShape * @see vertex */ var fill3D = function(vArray, mode, cArray, tArray){ var ctxMode; if (mode === "TRIANGLES") { ctxMode = curContext.TRIANGLES; } else if(mode === "TRIANGLE_FAN") { ctxMode = curContext.TRIANGLE_FAN; } else { ctxMode = curContext.TRIANGLE_STRIP; }
var view = new PMatrix3D(); view.scale( 1, -1, 1 ); view.apply( modelView.array() ); view.transpose();
curContext.useProgram( programObject3D ); uniformMatrix( "model3d", programObject3D, "uModel", false, [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1] ); uniformMatrix( "view3d", programObject3D, "uView", false, view.array() ); curContext.enable( curContext.POLYGON_OFFSET_FILL ); curContext.polygonOffset( 1, 1 ); uniformf( "color3d", programObject3D, "uColor", [-1,0,0,0] ); vertexAttribPointer( "vertex3d", programObject3D, "aVertex", 3, fillBuffer ); curContext.bufferData( curContext.ARRAY_BUFFER, new Float32Array(vArray), curContext.STREAM_DRAW );
// if we are using a texture and a tint, then overwrite the // contents of the color buffer with the current tint if ( usingTexture && curTint !== null ){ curTint3d( cArray ); }
vertexAttribPointer( "aColor3d", programObject3D, "aColor", 4, fillColorBuffer ); curContext.bufferData( curContext.ARRAY_BUFFER, new Float32Array(cArray), curContext.STREAM_DRAW );
// No support for lights....yet disableVertexAttribPointer( "aNormal3d", programObject3D, "aNormal" );
if ( usingTexture ) { uniformi( "uUsingTexture3d", programObject3D, "uUsingTexture", usingTexture ); vertexAttribPointer( "aTexture3d", programObject3D, "aTexture", 2, shapeTexVBO ); curContext.bufferData( curContext.ARRAY_BUFFER, new Float32Array(tArray), curContext.STREAM_DRAW ); }
curContext.drawArrays( ctxMode, 0, vArray.length/3 ); curContext.disable( curContext.POLYGON_OFFSET_FILL ); };
/** * this series of three operations is used a lot in Drawing2D.prototype.endShape * and has been split off as its own function, to tighten the code and allow for * fewer bugs. */ function fillStrokeClose() { executeContextFill(); executeContextStroke(); curContext.closePath(); }
/** * The endShape() function is the companion to beginShape() and may only be called after beginShape(). * When endshape() is called, all of image data defined since the previous call to beginShape() is written * into the image buffer. * * @param {int} MODE Use CLOSE to close the shape * * @see beginShape */ Drawing2D.prototype.endShape = function(mode) { // Duplicated in Drawing3D; too many variables used if (vertArray.length === 0) { return; }
var closeShape = mode === PConstants.CLOSE;
// if the shape is closed, the first element is also the last element if (closeShape) { vertArray.push(vertArray[0]); }
var lineVertArray = []; var fillVertArray = []; var colorVertArray = []; var strokeVertArray = []; var texVertArray = []; var cachedVertArray;
firstVert = true; var i, j, k; var vertArrayLength = vertArray.length;
for (i = 0; i < vertArrayLength; i++) { cachedVertArray = vertArray[i]; for (j = 0; j < 3; j++) { fillVertArray.push(cachedVertArray[j]); } }
// 5,6,7,8 // R,G,B,A - fill colour for (i = 0; i < vertArrayLength; i++) { cachedVertArray = vertArray[i]; for (j = 5; j < 9; j++) { colorVertArray.push(cachedVertArray[j]); } }
// 9,10,11,12 // R, G, B, A - stroke colour for (i = 0; i < vertArrayLength; i++) { cachedVertArray = vertArray[i]; for (j = 9; j < 13; j++) { strokeVertArray.push(cachedVertArray[j]); } }
// texture u,v for (i = 0; i < vertArrayLength; i++) { cachedVertArray = vertArray[i]; texVertArray.push(cachedVertArray[3]); texVertArray.push(cachedVertArray[4]); }
// curveVertex if ( isCurve && (curShape === PConstants.POLYGON || curShape === undef) ) { if (vertArrayLength > 3) { var b = [], s = 1 - curTightness; curContext.beginPath(); curContext.moveTo(vertArray[1][0], vertArray[1][1]); /* * Matrix to convert from Catmull-Rom to cubic Bezier * where t = curTightness * |0 1 0 0 | * |(t-1)/6 1 (1-t)/6 0 | * |0 (1-t)/6 1 (t-1)/6 | * |0 0 0 0 | */ for (i = 1; (i+2) < vertArrayLength; i++) { cachedVertArray = vertArray[i]; b[0] = [cachedVertArray[0], cachedVertArray[1]]; b[1] = [cachedVertArray[0] + (s * vertArray[i+1][0] - s * vertArray[i-1][0]) / 6, cachedVertArray[1] + (s * vertArray[i+1][1] - s * vertArray[i-1][1]) / 6]; b[2] = [vertArray[i+1][0] + (s * vertArray[i][0] - s * vertArray[i+2][0]) / 6, vertArray[i+1][1] + (s * vertArray[i][1] - s * vertArray[i+2][1]) / 6]; b[3] = [vertArray[i+1][0], vertArray[i+1][1]]; curContext.bezierCurveTo(b[1][0], b[1][1], b[2][0], b[2][1], b[3][0], b[3][1]); } fillStrokeClose(); } }
// bezierVertex else if ( isBezier && (curShape === PConstants.POLYGON || curShape === undef) ) { curContext.beginPath(); for (i = 0; i < vertArrayLength; i++) { cachedVertArray = vertArray[i]; if (vertArray[i].isVert) { //if it is a vertex move to the position if (vertArray[i].moveTo) { curContext.moveTo(cachedVertArray[0], cachedVertArray[1]); } else { curContext.lineTo(cachedVertArray[0], cachedVertArray[1]); } } else { //otherwise continue drawing bezier curContext.bezierCurveTo(vertArray[i][0], vertArray[i][1], vertArray[i][2], vertArray[i][3], vertArray[i][4], vertArray[i][5]); } } fillStrokeClose(); }
// render the vertices provided else { if (curShape === PConstants.POINTS) { for (i = 0; i < vertArrayLength; i++) { cachedVertArray = vertArray[i]; if (doStroke) { p.stroke(cachedVertArray[6]); } p.point(cachedVertArray[0], cachedVertArray[1]); } } else if (curShape === PConstants.LINES) { for (i = 0; (i + 1) < vertArrayLength; i+=2) { cachedVertArray = vertArray[i]; if (doStroke) { p.stroke(vertArray[i+1][6]); } p.line(cachedVertArray[0], cachedVertArray[1], vertArray[i+1][0], vertArray[i+1][1]); } } else if (curShape === PConstants.TRIANGLES) { for (i = 0; (i + 2) < vertArrayLength; i+=3) { cachedVertArray = vertArray[i]; curContext.beginPath(); curContext.moveTo(cachedVertArray[0], cachedVertArray[1]); curContext.lineTo(vertArray[i+1][0], vertArray[i+1][1]); curContext.lineTo(vertArray[i+2][0], vertArray[i+2][1]); curContext.lineTo(cachedVertArray[0], cachedVertArray[1]);
if (doFill) { p.fill(vertArray[i+2][5]); executeContextFill(); } if (doStroke) { p.stroke(vertArray[i+2][6]); executeContextStroke(); }
curContext.closePath(); } } else if (curShape === PConstants.TRIANGLE_STRIP) { for (i = 0; (i+1) < vertArrayLength; i++) { cachedVertArray = vertArray[i]; curContext.beginPath(); curContext.moveTo(vertArray[i+1][0], vertArray[i+1][1]); curContext.lineTo(cachedVertArray[0], cachedVertArray[1]);
if (doStroke) { p.stroke(vertArray[i+1][6]); } if (doFill) { p.fill(vertArray[i+1][5]); }
if (i + 2 < vertArrayLength) { curContext.lineTo(vertArray[i+2][0], vertArray[i+2][1]); if (doStroke) { p.stroke(vertArray[i+2][6]); } if (doFill) { p.fill(vertArray[i+2][5]); } } fillStrokeClose(); } } else if (curShape === PConstants.TRIANGLE_FAN) { if (vertArrayLength > 2) { curContext.beginPath(); curContext.moveTo(vertArray[0][0], vertArray[0][1]); curContext.lineTo(vertArray[1][0], vertArray[1][1]); curContext.lineTo(vertArray[2][0], vertArray[2][1]);
if (doFill) { p.fill(vertArray[2][5]); executeContextFill(); } if (doStroke) { p.stroke(vertArray[2][6]); executeContextStroke(); }
curContext.closePath(); for (i = 3; i < vertArrayLength; i++) { cachedVertArray = vertArray[i]; curContext.beginPath(); curContext.moveTo(vertArray[0][0], vertArray[0][1]); curContext.lineTo(vertArray[i-1][0], vertArray[i-1][1]); curContext.lineTo(cachedVertArray[0], cachedVertArray[1]);
if (doFill) { p.fill(cachedVertArray[5]); executeContextFill(); } if (doStroke) { p.stroke(cachedVertArray[6]); executeContextStroke(); }
curContext.closePath(); } } } else if (curShape === PConstants.QUADS) { for (i = 0; (i + 3) < vertArrayLength; i+=4) { cachedVertArray = vertArray[i]; curContext.beginPath(); curContext.moveTo(cachedVertArray[0], cachedVertArray[1]); for (j = 1; j < 4; j++) { curContext.lineTo(vertArray[i+j][0], vertArray[i+j][1]); } curContext.lineTo(cachedVertArray[0], cachedVertArray[1]);
if (doFill) { p.fill(vertArray[i+3][5]); executeContextFill(); } if (doStroke) { p.stroke(vertArray[i+3][6]); executeContextStroke(); }
curContext.closePath(); } } else if (curShape === PConstants.QUAD_STRIP) { if (vertArrayLength > 3) { for (i = 0; (i+1) < vertArrayLength; i+=2) { cachedVertArray = vertArray[i]; curContext.beginPath(); if (i+3 < vertArrayLength) { curContext.moveTo(vertArray[i+2][0], vertArray[i+2][1]); curContext.lineTo(cachedVertArray[0], cachedVertArray[1]); curContext.lineTo(vertArray[i+1][0], vertArray[i+1][1]); curContext.lineTo(vertArray[i+3][0], vertArray[i+3][1]);
if (doFill) { p.fill(vertArray[i+3][5]); } if (doStroke) { p.stroke(vertArray[i+3][6]); } } else { curContext.moveTo(cachedVertArray[0], cachedVertArray[1]); curContext.lineTo(vertArray[i+1][0], vertArray[i+1][1]); } fillStrokeClose(); } } } else { curContext.beginPath(); curContext.moveTo(vertArray[0][0], vertArray[0][1]); for (i = 1; i < vertArrayLength; i++) { cachedVertArray = vertArray[i]; if (cachedVertArray.isVert) { //if it is a vertex move to the position if (cachedVertArray.moveTo) { curContext.moveTo(cachedVertArray[0], cachedVertArray[1]); } else { curContext.lineTo(cachedVertArray[0], cachedVertArray[1]); } } } fillStrokeClose(); } }
// Reset some settings isCurve = false; isBezier = false; curveVertArray = []; curveVertCount = 0;
// If the shape is closed, the first element was added as last element. // We must remove it again to prevent the list of vertices from growing // over successive calls to endShape(CLOSE) if (closeShape) { vertArray.pop(); } };
Drawing3D.prototype.endShape = function(mode) { // Duplicated in Drawing3D; too many variables used if (vertArray.length === 0) { return; }
var closeShape = mode === PConstants.CLOSE; var lineVertArray = []; var fillVertArray = []; var colorVertArray = []; var strokeVertArray = []; var texVertArray = []; var cachedVertArray;
firstVert = true; var i, j, k; var vertArrayLength = vertArray.length;
for (i = 0; i < vertArrayLength; i++) { cachedVertArray = vertArray[i]; for (j = 0; j < 3; j++) { fillVertArray.push(cachedVertArray[j]); } }
// 5,6,7,8 // R,G,B,A - fill colour for (i = 0; i < vertArrayLength; i++) { cachedVertArray = vertArray[i]; for (j = 5; j < 9; j++) { colorVertArray.push(cachedVertArray[j]); } }
// 9,10,11,12 // R, G, B, A - stroke colour for (i = 0; i < vertArrayLength; i++) { cachedVertArray = vertArray[i]; for (j = 9; j < 13; j++) { strokeVertArray.push(cachedVertArray[j]); } }
// texture u,v for (i = 0; i < vertArrayLength; i++) { cachedVertArray = vertArray[i]; texVertArray.push(cachedVertArray[3]); texVertArray.push(cachedVertArray[4]); }
// if shape is closed, push the first point into the last point (including colours) if (closeShape) { fillVertArray.push(vertArray[0][0]); fillVertArray.push(vertArray[0][1]); fillVertArray.push(vertArray[0][2]);
for (i = 5; i < 9; i++) { colorVertArray.push(vertArray[0][i]); }
for (i = 9; i < 13; i++) { strokeVertArray.push(vertArray[0][i]); }
texVertArray.push(vertArray[0][3]); texVertArray.push(vertArray[0][4]); } // End duplication
// curveVertex if ( isCurve && (curShape === PConstants.POLYGON || curShape === undef) ) { lineVertArray = fillVertArray; if (doStroke) { line3D(lineVertArray, null, strokeVertArray); } if (doFill) { fill3D(fillVertArray, null, colorVertArray); } } // bezierVertex else if ( isBezier && (curShape === PConstants.POLYGON || curShape === undef) ) { lineVertArray = fillVertArray; lineVertArray.splice(lineVertArray.length - 3); strokeVertArray.splice(strokeVertArray.length - 4); if (doStroke) { line3D(lineVertArray, null, strokeVertArray); } if (doFill) { fill3D(fillVertArray, "TRIANGLES", colorVertArray); } }
// render the vertices provided else { if (curShape === PConstants.POINTS) { // if POINTS was the specified parameter in beginShape for (i = 0; i < vertArrayLength; i++) { // loop through and push the point location information to the array cachedVertArray = vertArray[i]; for (j = 0; j < 3; j++) { lineVertArray.push(cachedVertArray[j]); } } point3D(lineVertArray, strokeVertArray); // render function for points } else if (curShape === PConstants.LINES) { // if LINES was the specified parameter in beginShape for (i = 0; i < vertArrayLength; i++) { // loop through and push the point location information to the array cachedVertArray = vertArray[i]; for (j = 0; j < 3; j++) { lineVertArray.push(cachedVertArray[j]); } } for (i = 0; i < vertArrayLength; i++) { // loop through and push the color information to the array cachedVertArray = vertArray[i]; for (j = 5; j < 9; j++) { colorVertArray.push(cachedVertArray[j]); } } line3D(lineVertArray, "LINES", strokeVertArray); // render function for lines } else if (curShape === PConstants.TRIANGLES) { // if TRIANGLES was the specified parameter in beginShape if (vertArrayLength > 2) { for (i = 0; (i+2) < vertArrayLength; i+=3) { // loop through the array per triangle fillVertArray = []; texVertArray = []; lineVertArray = []; colorVertArray = []; strokeVertArray = []; for (j = 0; j < 3; j++) { for (k = 0; k < 3; k++) { // loop through and push lineVertArray.push(vertArray[i+j][k]); // the line point location information fillVertArray.push(vertArray[i+j][k]); // and fill point location information } } for (j = 0; j < 3; j++) { // loop through and push the texture information for (k = 3; k < 5; k++) { texVertArray.push(vertArray[i+j][k]); } } for (j = 0; j < 3; j++) { for (k = 5; k < 9; k++) { // loop through and push colorVertArray.push(vertArray[i+j][k]); // the colour information strokeVertArray.push(vertArray[i+j][k+4]);// and the stroke information } } if (doStroke) { line3D(lineVertArray, "LINE_LOOP", strokeVertArray ); // line render function } if (doFill || usingTexture) { fill3D(fillVertArray, "TRIANGLES", colorVertArray, texVertArray); // fill shape render function } } } } else if (curShape === PConstants.TRIANGLE_STRIP) { // if TRIANGLE_STRIP was the specified parameter in beginShape if (vertArrayLength > 2) { for (i = 0; (i+2) < vertArrayLength; i++) { lineVertArray = []; fillVertArray = []; strokeVertArray = []; colorVertArray = []; texVertArray = []; for (j = 0; j < 3; j++) { for (k = 0; k < 3; k++) { lineVertArray.push(vertArray[i+j][k]); fillVertArray.push(vertArray[i+j][k]); } } for (j = 0; j < 3; j++) { for (k = 3; k < 5; k++) { texVertArray.push(vertArray[i+j][k]); } } for (j = 0; j < 3; j++) { for (k = 5; k < 9; k++) { strokeVertArray.push(vertArray[i+j][k+4]); colorVertArray.push(vertArray[i+j][k]); } }
if (doFill || usingTexture) { fill3D(fillVertArray, "TRIANGLE_STRIP", colorVertArray, texVertArray); } if (doStroke) { line3D(lineVertArray, "LINE_LOOP", strokeVertArray); } } } } else if (curShape === PConstants.TRIANGLE_FAN) { if (vertArrayLength > 2) { for (i = 0; i < 3; i++) { cachedVertArray = vertArray[i]; for (j = 0; j < 3; j++) { lineVertArray.push(cachedVertArray[j]); } } for (i = 0; i < 3; i++) { cachedVertArray = vertArray[i]; for (j = 9; j < 13; j++) { strokeVertArray.push(cachedVertArray[j]); } } if (doStroke) { line3D(lineVertArray, "LINE_LOOP", strokeVertArray); }
for (i = 2; (i+1) < vertArrayLength; i++) { lineVertArray = []; strokeVertArray = []; lineVertArray.push(vertArray[0][0]); lineVertArray.push(vertArray[0][1]); lineVertArray.push(vertArray[0][2]);
strokeVertArray.push(vertArray[0][9]); strokeVertArray.push(vertArray[0][10]); strokeVertArray.push(vertArray[0][11]); strokeVertArray.push(vertArray[0][12]);
for (j = 0; j < 2; j++) { for (k = 0; k < 3; k++) { lineVertArray.push(vertArray[i+j][k]); } } for (j = 0; j < 2; j++) { for (k = 9; k < 13; k++) { strokeVertArray.push(vertArray[i+j][k]); } } if (doStroke) { line3D(lineVertArray, "LINE_STRIP",strokeVertArray); } } if (doFill || usingTexture) { fill3D(fillVertArray, "TRIANGLE_FAN", colorVertArray, texVertArray); } } } else if (curShape === PConstants.QUADS) { for (i = 0; (i + 3) < vertArrayLength; i+=4) { lineVertArray = []; for (j = 0; j < 4; j++) { cachedVertArray = vertArray[i+j]; for (k = 0; k < 3; k++) { lineVertArray.push(cachedVertArray[k]); } } if (doStroke) { line3D(lineVertArray, "LINE_LOOP",strokeVertArray); }
if (doFill) { fillVertArray = []; colorVertArray = []; texVertArray = []; for (j = 0; j < 3; j++) { fillVertArray.push(vertArray[i][j]); } for (j = 5; j < 9; j++) { colorVertArray.push(vertArray[i][j]); }
for (j = 0; j < 3; j++) { fillVertArray.push(vertArray[i+1][j]); } for (j = 5; j < 9; j++) { colorVertArray.push(vertArray[i+1][j]); }
for (j = 0; j < 3; j++) { fillVertArray.push(vertArray[i+3][j]); } for (j = 5; j < 9; j++) { colorVertArray.push(vertArray[i+3][j]); }
for (j = 0; j < 3; j++) { fillVertArray.push(vertArray[i+2][j]); } for (j = 5; j < 9; j++) { colorVertArray.push(vertArray[i+2][j]); }
if (usingTexture) { texVertArray.push(vertArray[i+0][3]); texVertArray.push(vertArray[i+0][4]); texVertArray.push(vertArray[i+1][3]); texVertArray.push(vertArray[i+1][4]); texVertArray.push(vertArray[i+3][3]); texVertArray.push(vertArray[i+3][4]); texVertArray.push(vertArray[i+2][3]); texVertArray.push(vertArray[i+2][4]); }
fill3D(fillVertArray, "TRIANGLE_STRIP", colorVertArray, texVertArray); } } } else if (curShape === PConstants.QUAD_STRIP) { var tempArray = []; if (vertArrayLength > 3) { for (i = 0; i < 2; i++) { cachedVertArray = vertArray[i]; for (j = 0; j < 3; j++) { lineVertArray.push(cachedVertArray[j]); } }
for (i = 0; i < 2; i++) { cachedVertArray = vertArray[i]; for (j = 9; j < 13; j++) { strokeVertArray.push(cachedVertArray[j]); } }
line3D(lineVertArray, "LINE_STRIP", strokeVertArray); if (vertArrayLength > 4 && vertArrayLength % 2 > 0) { tempArray = fillVertArray.splice(fillVertArray.length - 3); vertArray.pop(); } for (i = 0; (i+3) < vertArrayLength; i+=2) { lineVertArray = []; strokeVertArray = []; for (j = 0; j < 3; j++) { lineVertArray.push(vertArray[i+1][j]); } for (j = 0; j < 3; j++) { lineVertArray.push(vertArray[i+3][j]); } for (j = 0; j < 3; j++) { lineVertArray.push(vertArray[i+2][j]); } for (j = 0; j < 3; j++) { lineVertArray.push(vertArray[i+0][j]); } for (j = 9; j < 13; j++) { strokeVertArray.push(vertArray[i+1][j]); } for (j = 9; j < 13; j++) { strokeVertArray.push(vertArray[i+3][j]); } for (j = 9; j < 13; j++) { strokeVertArray.push(vertArray[i+2][j]); } for (j = 9; j < 13; j++) { strokeVertArray.push(vertArray[i+0][j]); } if (doStroke) { line3D(lineVertArray, "LINE_STRIP", strokeVertArray); } }
if (doFill || usingTexture) { fill3D(fillVertArray, "TRIANGLE_LIST", colorVertArray, texVertArray); } } } // If the user didn't specify a type (LINES, TRIANGLES, etc) else { // If only one vertex was specified, it must be a point if (vertArrayLength === 1) { for (j = 0; j < 3; j++) { lineVertArray.push(vertArray[0][j]); } for (j = 9; j < 13; j++) { strokeVertArray.push(vertArray[0][j]); } point3D(lineVertArray,strokeVertArray); } else { for (i = 0; i < vertArrayLength; i++) { cachedVertArray = vertArray[i]; for (j = 0; j < 3; j++) { lineVertArray.push(cachedVertArray[j]); } for (j = 5; j < 9; j++) { strokeVertArray.push(cachedVertArray[j]); } } if (doStroke && closeShape) { line3D(lineVertArray, "LINE_LOOP", strokeVertArray); } else if (doStroke && !closeShape) { line3D(lineVertArray, "LINE_STRIP", strokeVertArray); }
// fill is ignored if textures are used if (doFill || usingTexture) { fill3D(fillVertArray, "TRIANGLE_FAN", colorVertArray, texVertArray); } } } // everytime beginShape is followed by a call to // texture(), texturing it turned back on. We do this to // figure out if the shape should be textured or filled // with a color. usingTexture = false; curContext.useProgram(programObject3D); uniformi("usingTexture3d", programObject3D, "uUsingTexture", usingTexture); }
// Reset some settings isCurve = false; isBezier = false; curveVertArray = []; curveVertCount = 0; };
/** * The function splineForward() setup forward-differencing matrix to be used for speedy * curve rendering. It's based on using a specific number * of curve segments and just doing incremental adds for each * vertex of the segment, rather than running the mathematically * expensive cubic equation. This function is used by both curveDetail and bezierDetail. * * @param {int} segments number of curve segments to use when drawing * @param {PMatrix3D} matrix target object for the new matrix */ var splineForward = function(segments, matrix) { var f = 1.0 / segments; var ff = f * f; var fff = ff * f;
matrix.set(0, 0, 0, 1, fff, ff, f, 0, 6 * fff, 2 * ff, 0, 0, 6 * fff, 0, 0, 0); };
/** * The curveInit() function set the number of segments to use when drawing a Catmull-Rom * curve, and setting the s parameter, which defines how tightly * the curve fits to each vertex. Catmull-Rom curves are actually * a subset of this curve type where the s is set to zero. * This in an internal function used by curveDetail() and curveTightness(). */ var curveInit = function() { // allocate only if/when used to save startup time if (!curveDrawMatrix) { curveBasisMatrix = new PMatrix3D(); curveDrawMatrix = new PMatrix3D(); curveInited = true; }
var s = curTightness; curveBasisMatrix.set((s - 1) / 2, (s + 3) / 2, (-3 - s) / 2, (1 - s) / 2, (1 - s), (-5 - s) / 2, (s + 2), (s - 1) / 2, (s - 1) / 2, 0, (1 - s) / 2, 0, 0, 1, 0, 0);
splineForward(curveDet, curveDrawMatrix);
if (!bezierBasisInverse) { //bezierBasisInverse = bezierBasisMatrix.get(); //bezierBasisInverse.invert(); curveToBezierMatrix = new PMatrix3D(); }
// TODO only needed for PGraphicsJava2D? if so, move it there // actually, it's generally useful for other renderers, so keep it // or hide the implementation elsewhere. curveToBezierMatrix.set(curveBasisMatrix); curveToBezierMatrix.preApply(bezierBasisInverse);
// multiply the basis and forward diff matrices together // saves much time since this needn't be done for each curve curveDrawMatrix.apply(curveBasisMatrix); };
/** * Specifies vertex coordinates for Bezier curves. Each call to bezierVertex() defines the position of two control * points and one anchor point of a Bezier curve, adding a new segment to a line or shape. The first time * bezierVertex() is used within a beginShape() call, it must be prefaced with a call to vertex() * to set the first anchor point. This function must be used between beginShape() and endShape() and only * when there is no MODE parameter specified to beginShape(). Using the 3D version of requires rendering with P3D * or OPENGL (see the Environment reference for more information).
NOTE: Fill does not work properly yet. * * @param {float | int} cx1 The x-coordinate of 1st control point * @param {float | int} cy1 The y-coordinate of 1st control point * @param {float | int} cz1 The z-coordinate of 1st control point * @param {float | int} cx2 The x-coordinate of 2nd control point * @param {float | int} cy2 The y-coordinate of 2nd control point * @param {float | int} cz2 The z-coordinate of 2nd control point * @param {float | int} x The x-coordinate of the anchor point * @param {float | int} y The y-coordinate of the anchor point * @param {float | int} z The z-coordinate of the anchor point * * @see curveVertex * @see vertex * @see bezier */ Drawing2D.prototype.bezierVertex = function() { isBezier = true; var vert = []; if (firstVert) { throw ("vertex() must be used at least once before calling bezierVertex()"); }
for (var i = 0; i < arguments.length; i++) { vert[i] = arguments[i]; } vertArray.push(vert); vertArray[vertArray.length -1].isVert = false; };
Drawing3D.prototype.bezierVertex = function() { isBezier = true; var vert = []; if (firstVert) { throw ("vertex() must be used at least once before calling bezierVertex()"); }
if (arguments.length === 9) { if (bezierDrawMatrix === undef) { bezierDrawMatrix = new PMatrix3D(); } // setup matrix for forward differencing to speed up drawing var lastPoint = vertArray.length - 1; splineForward( bezDetail, bezierDrawMatrix ); bezierDrawMatrix.apply( bezierBasisMatrix ); var draw = bezierDrawMatrix.array(); var x1 = vertArray[lastPoint][0], y1 = vertArray[lastPoint][1], z1 = vertArray[lastPoint][2]; var xplot1 = draw[4] * x1 + draw[5] * arguments[0] + draw[6] * arguments[3] + draw[7] * arguments[6]; var xplot2 = draw[8] * x1 + draw[9] * arguments[0] + draw[10]* arguments[3] + draw[11]* arguments[6]; var xplot3 = draw[12]* x1 + draw[13]* arguments[0] + draw[14]* arguments[3] + draw[15]* arguments[6];
var yplot1 = draw[4] * y1 + draw[5] * arguments[1] + draw[6] * arguments[4] + draw[7] * arguments[7]; var yplot2 = draw[8] * y1 + draw[9] * arguments[1] + draw[10]* arguments[4] + draw[11]* arguments[7]; var yplot3 = draw[12]* y1 + draw[13]* arguments[1] + draw[14]* arguments[4] + draw[15]* arguments[7];
var zplot1 = draw[4] * z1 + draw[5] * arguments[2] + draw[6] * arguments[5] + draw[7] * arguments[8]; var zplot2 = draw[8] * z1 + draw[9] * arguments[2] + draw[10]* arguments[5] + draw[11]* arguments[8]; var zplot3 = draw[12]* z1 + draw[13]* arguments[2] + draw[14]* arguments[5] + draw[15]* arguments[8]; for (var j = 0; j < bezDetail; j++) { x1 += xplot1; xplot1 += xplot2; xplot2 += xplot3; y1 += yplot1; yplot1 += yplot2; yplot2 += yplot3; z1 += zplot1; zplot1 += zplot2; zplot2 += zplot3; p.vertex(x1, y1, z1); } p.vertex(arguments[6], arguments[7], arguments[8]); } };
/** * Sets a texture to be applied to vertex points. The texture() function * must be called between beginShape() and endShape() and before * any calls to vertex(). * * When textures are in use, the fill color is ignored. Instead, use tint() to * specify the color of the texture as it is applied to the shape. * * @param {PImage} pimage the texture to apply * * @returns none * * @see textureMode * @see beginShape * @see endShape * @see vertex */ p.texture = function(pimage) { var curContext = drawing.$ensureContext();
if (pimage.__texture) { curContext.bindTexture(curContext.TEXTURE_2D, pimage.__texture); } else if (pimage.localName === "canvas") { curContext.bindTexture(curContext.TEXTURE_2D, canTex); curContext.texImage2D(curContext.TEXTURE_2D, 0, curContext.RGBA, curContext.RGBA, curContext.UNSIGNED_BYTE, pimage); curContext.texParameteri(curContext.TEXTURE_2D, curContext.TEXTURE_MAG_FILTER, curContext.LINEAR); curContext.texParameteri(curContext.TEXTURE_2D, curContext.TEXTURE_MIN_FILTER, curContext.LINEAR); curContext.generateMipmap(curContext.TEXTURE_2D); curTexture.width = pimage.width; curTexture.height = pimage.height; } else { var texture = curContext.createTexture(), cvs = document.createElement('canvas'), cvsTextureCtx = cvs.getContext('2d'), pot;
// WebGL requires power of two textures if (pimage.width & (pimage.width-1) === 0) { cvs.width = pimage.width; } else { pot = 1; while (pot < pimage.width) { pot *= 2; } cvs.width = pot; }
if (pimage.height & (pimage.height-1) === 0) { cvs.height = pimage.height; } else { pot = 1; while (pot < pimage.height) { pot *= 2; } cvs.height = pot; }
cvsTextureCtx.drawImage(pimage.sourceImg, 0, 0, pimage.width, pimage.height, 0, 0, cvs.width, cvs.height);
curContext.bindTexture(curContext.TEXTURE_2D, texture); curContext.texParameteri(curContext.TEXTURE_2D, curContext.TEXTURE_MIN_FILTER, curContext.LINEAR_MIPMAP_LINEAR); curContext.texParameteri(curContext.TEXTURE_2D, curContext.TEXTURE_MAG_FILTER, curContext.LINEAR); curContext.texParameteri(curContext.TEXTURE_2D, curContext.TEXTURE_WRAP_T, curContext.CLAMP_TO_EDGE); curContext.texParameteri(curContext.TEXTURE_2D, curContext.TEXTURE_WRAP_S, curContext.CLAMP_TO_EDGE); curContext.texImage2D(curContext.TEXTURE_2D, 0, curContext.RGBA, curContext.RGBA, curContext.UNSIGNED_BYTE, cvs); curContext.generateMipmap(curContext.TEXTURE_2D);
pimage.__texture = texture; curTexture.width = pimage.width; curTexture.height = pimage.height; }
usingTexture = true; curContext.useProgram(programObject3D); uniformi("usingTexture3d", programObject3D, "uUsingTexture", usingTexture); };
/** * Sets the coordinate space for texture mapping. There are two options, IMAGE, * which refers to the actual coordinates of the image, and NORMALIZED, which * refers to a normalized space of values ranging from 0 to 1. The default mode * is IMAGE. In IMAGE, if an image is 100 x 200 pixels, mapping the image onto * the entire size of a quad would require the points (0,0) (0,100) (100,200) (0,200). * The same mapping in NORMAL_SPACE is (0,0) (0,1) (1,1) (0,1). * * @param MODE either IMAGE or NORMALIZED * * @returns none * * @see texture */ p.textureMode = function(mode){ curTextureMode = mode; }; /** * The curveVertexSegment() function handle emitting a specific segment of Catmull-Rom curve. Internal helper function used by curveVertex(). */ var curveVertexSegment = function(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4) { var x0 = x2; var y0 = y2; var z0 = z2;
var draw = curveDrawMatrix.array();
var xplot1 = draw[4] * x1 + draw[5] * x2 + draw[6] * x3 + draw[7] * x4; var xplot2 = draw[8] * x1 + draw[9] * x2 + draw[10] * x3 + draw[11] * x4; var xplot3 = draw[12] * x1 + draw[13] * x2 + draw[14] * x3 + draw[15] * x4;
var yplot1 = draw[4] * y1 + draw[5] * y2 + draw[6] * y3 + draw[7] * y4; var yplot2 = draw[8] * y1 + draw[9] * y2 + draw[10] * y3 + draw[11] * y4; var yplot3 = draw[12] * y1 + draw[13] * y2 + draw[14] * y3 + draw[15] * y4;
var zplot1 = draw[4] * z1 + draw[5] * z2 + draw[6] * z3 + draw[7] * z4; var zplot2 = draw[8] * z1 + draw[9] * z2 + draw[10] * z3 + draw[11] * z4; var zplot3 = draw[12] * z1 + draw[13] * z2 + draw[14] * z3 + draw[15] * z4;
p.vertex(x0, y0, z0); for (var j = 0; j < curveDet; j++) { x0 += xplot1; xplot1 += xplot2; xplot2 += xplot3; y0 += yplot1; yplot1 += yplot2; yplot2 += yplot3; z0 += zplot1; zplot1 += zplot2; zplot2 += zplot3; p.vertex(x0, y0, z0); } };
/** * Specifies vertex coordinates for curves. This function may only be used between beginShape() and * endShape() and only when there is no MODE parameter specified to beginShape(). The first and last points * in a series of curveVertex() lines will be used to guide the beginning and end of a the curve. A minimum of four * points is required to draw a tiny curve between the second and third points. Adding a fifth point with * curveVertex() will draw the curve between the second, third, and fourth points. The curveVertex() function * is an implementation of Catmull-Rom splines. Using the 3D version of requires rendering with P3D or OPENGL (see the * Environment reference for more information).
NOTE: Fill does not work properly yet. * * @param {float | int} x The x-coordinate of the vertex * @param {float | int} y The y-coordinate of the vertex * @param {float | int} z The z-coordinate of the vertex * * @see curve * @see beginShape * @see endShape * @see vertex * @see bezierVertex */ Drawing2D.prototype.curveVertex = function(x, y) { isCurve = true;
p.vertex(x, y); };
Drawing3D.prototype.curveVertex = function(x, y, z) { isCurve = true;
if (!curveInited) { curveInit(); } var vert = []; vert[0] = x; vert[1] = y; vert[2] = z; curveVertArray.push(vert); curveVertCount++;
if (curveVertCount > 3) { curveVertexSegment( curveVertArray[curveVertCount-4][0], curveVertArray[curveVertCount-4][1], curveVertArray[curveVertCount-4][2], curveVertArray[curveVertCount-3][0], curveVertArray[curveVertCount-3][1], curveVertArray[curveVertCount-3][2], curveVertArray[curveVertCount-2][0], curveVertArray[curveVertCount-2][1], curveVertArray[curveVertCount-2][2], curveVertArray[curveVertCount-1][0], curveVertArray[curveVertCount-1][1], curveVertArray[curveVertCount-1][2] ); } };
/** * The curve() function draws a curved line on the screen. The first and second parameters * specify the beginning control point and the last two parameters specify * the ending control point. The middle parameters specify the start and * stop of the curve. Longer curves can be created by putting a series of * curve() functions together or using curveVertex(). * An additional function called curveTightness() provides control * for the visual quality of the curve. The curve() function is an * implementation of Catmull-Rom splines. Using the 3D version of requires * rendering with P3D or OPENGL (see the Environment reference for more * information). * * @param {int|float} x1 coordinates for the beginning control point * @param {int|float} y1 coordinates for the beginning control point * @param {int|float} z1 coordinates for the beginning control point * @param {int|float} x2 coordinates for the first point * @param {int|float} y2 coordinates for the first point * @param {int|float} z2 coordinates for the first point * @param {int|float} x3 coordinates for the second point * @param {int|float} y3 coordinates for the second point * @param {int|float} z3 coordinates for the second point * @param {int|float} x4 coordinates for the ending control point * @param {int|float} y4 coordinates for the ending control point * @param {int|float} z4 coordinates for the ending control point * * @see #curveVertex() * @see #curveTightness() * @see #bezier() */ Drawing2D.prototype.curve = function(x1, y1, x2, y2, x3, y3, x4, y4) { p.beginShape(); p.curveVertex(x1, y1); p.curveVertex(x2, y2); p.curveVertex(x3, y3); p.curveVertex(x4, y4); p.endShape(); };
Drawing3D.prototype.curve = function(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4) { if (z4 !== undef) { p.beginShape(); p.curveVertex(x1, y1, z1); p.curveVertex(x2, y2, z2); p.curveVertex(x3, y3, z3); p.curveVertex(x4, y4, z4); p.endShape(); return; } p.beginShape(); p.curveVertex(x1, y1); p.curveVertex(z1, x2); p.curveVertex(y2, z2); p.curveVertex(x3, y3); p.endShape(); };
/** * The curveTightness() function modifies the quality of forms created with curve() and * curveVertex(). The parameter squishy determines how the * curve fits to the vertex points. The value 0.0 is the default value for * squishy (this value defines the curves to be Catmull-Rom splines) * and the value 1.0 connects all the points with straight lines. * Values within the range -5.0 and 5.0 will deform the curves but * will leave them recognizable and as values increase in magnitude, * they will continue to deform. * * @param {float} tightness amount of deformation from the original vertices * * @see #curve() * @see #curveVertex() * */ p.curveTightness = function(tightness) { curTightness = tightness; };
/** * The curveDetail() function sets the resolution at which curves display. The default value is 20. * This function is only useful when using the P3D or OPENGL renderer. * * @param {int} detail resolution of the curves * * @see curve() * @see curveVertex() * @see curveTightness() */ p.curveDetail = function(detail) { curveDet = detail; curveInit(); };
/** * Modifies the location from which rectangles draw. The default mode is rectMode(CORNER), which * specifies the location to be the upper left corner of the shape and uses the third and fourth * parameters of rect() to specify the width and height. The syntax rectMode(CORNERS) uses the * first and second parameters of rect() to set the location of one corner and uses the third and * fourth parameters to set the opposite corner. The syntax rectMode(CENTER) draws the image from * its center point and uses the third and forth parameters of rect() to specify the image's width * and height. The syntax rectMode(RADIUS) draws the image from its center point and uses the third * and forth parameters of rect() to specify half of the image's width and height. The parameter must * be written in ALL CAPS because Processing is a case sensitive language. Note: In version 125, the * mode named CENTER_RADIUS was shortened to RADIUS. * * @param {MODE} MODE Either CORNER, CORNERS, CENTER, or RADIUS * * @see rect */ p.rectMode = function(aRectMode) { curRectMode = aRectMode; };
/** * Modifies the location from which images draw. The default mode is imageMode(CORNER), which specifies * the location to be the upper left corner and uses the fourth and fifth parameters of image() to set * the image's width and height. The syntax imageMode(CORNERS) uses the second and third parameters of * image() to set the location of one corner of the image and uses the fourth and fifth parameters to * set the opposite corner. Use imageMode(CENTER) to draw images centered at the given x and y position. * The parameter to imageMode() must be written in ALL CAPS because Processing is a case sensitive language. * * @param {MODE} MODE Either CORNER, CORNERS, or CENTER * * @see loadImage * @see PImage * @see image * @see background */ p.imageMode = function(mode) { switch (mode) { case PConstants.CORNER: imageModeConvert = imageModeCorner; break; case PConstants.CORNERS: imageModeConvert = imageModeCorners; break; case PConstants.CENTER: imageModeConvert = imageModeCenter; break; default: throw "Invalid imageMode"; } };
/** * The origin of the ellipse is modified by the ellipseMode() function. The default configuration is * ellipseMode(CENTER), which specifies the location of the ellipse as the center of the shape. The RADIUS * mode is the same, but the width and height parameters to ellipse() specify the radius of the ellipse, * rather than the diameter. The CORNER mode draws the shape from the upper-left corner of its bounding box. * The CORNERS mode uses the four parameters to ellipse() to set two opposing corners of the ellipse's bounding * box. The parameter must be written in "ALL CAPS" because Processing is a case sensitive language. * * @param {MODE} MODE Either CENTER, RADIUS, CORNER, or CORNERS. * * @see ellipse */ p.ellipseMode = function(aEllipseMode) { curEllipseMode = aEllipseMode; };
/** * The arc() function draws an arc in the display window. * Arcs are drawn along the outer edge of an ellipse defined by the * x, y, width and height parameters. * The origin or the arc's ellipse may be changed with the * ellipseMode() function. * The start and stop parameters specify the angles * at which to draw the arc. * * @param {float} a x-coordinate of the arc's ellipse * @param {float} b y-coordinate of the arc's ellipse * @param {float} c width of the arc's ellipse * @param {float} d height of the arc's ellipse * @param {float} start angle to start the arc, specified in radians * @param {float} stop angle to stop the arc, specified in radians * * @see #ellipseMode() * @see #ellipse() */ p.arc = function(x, y, width, height, start, stop) { if (width <= 0 || stop < start) { return; }
if (curEllipseMode === PConstants.CORNERS) { width = width - x; height = height - y; } else if (curEllipseMode === PConstants.RADIUS) { x = x - width; y = y - height; width = width * 2; height = height * 2; } else if (curEllipseMode === PConstants.CENTER) { x = x - width/2; y = y - height/2; } // make sure that we're starting at a useful point while (start < 0) { start += PConstants.TWO_PI; stop += PConstants.TWO_PI; } if (stop - start > PConstants.TWO_PI) { start = 0; stop = PConstants.TWO_PI; } var hr = width / 2, vr = height / 2, centerX = x + hr, centerY = y + vr, startLUT = 0 | (0.5 + start * p.RAD_TO_DEG * 2), stopLUT = 0 | (0.5 + stop * p.RAD_TO_DEG * 2), i, j; if (doFill) { // shut off stroke for a minute var savedStroke = doStroke; doStroke = false; p.beginShape(); p.vertex(centerX, centerY); for (i = startLUT; i <= stopLUT; i++) { j = i % PConstants.SINCOS_LENGTH; p.vertex(centerX + cosLUT[j] * hr, centerY + sinLUT[j] * vr); } p.endShape(PConstants.CLOSE); doStroke = savedStroke; }
if (doStroke) { // and doesn't include the first (center) vertex. var savedFill = doFill; doFill = false; p.beginShape(); for (i = startLUT; i <= stopLUT; i++) { j = i % PConstants.SINCOS_LENGTH; p.vertex(centerX + cosLUT[j] * hr, centerY + sinLUT[j] * vr); } p.endShape(); doFill = savedFill; } };
/** * Draws a line (a direct path between two points) to the screen. The version of line() with four parameters * draws the line in 2D. To color a line, use the stroke() function. A line cannot be filled, therefore the * fill() method will not affect the color of a line. 2D lines are drawn with a width of one pixel by default, * but this can be changed with the strokeWeight() function. The version with six parameters allows the line * to be placed anywhere within XYZ space. Drawing this shape in 3D using the z parameter requires the P3D or * OPENGL parameter in combination with size. * * @param {int|float} x1 x-coordinate of the first point * @param {int|float} y1 y-coordinate of the first point * @param {int|float} z1 z-coordinate of the first point * @param {int|float} x2 x-coordinate of the second point * @param {int|float} y2 y-coordinate of the second point * @param {int|float} z2 z-coordinate of the second point * * @see strokeWeight * @see strokeJoin * @see strokeCap * @see beginShape */ Drawing2D.prototype.line = function(x1, y1, x2, y2) { if (!doStroke) { return; } x1 = Math.round(x1); x2 = Math.round(x2); y1 = Math.round(y1); y2 = Math.round(y2); // A line is only defined if it has different start and end coordinates. // If they are the same, we call point instead. if (x1 === x2 && y1 === y2) { p.point(x1, y1); return; }
var swap = undef, lineCap = undef, drawCrisp = true, currentModelView = modelView.array(), identityMatrix = [1, 0, 0, 0, 1, 0]; // Test if any transformations have been applied to the sketch for (var i = 0; i < 6 && drawCrisp; i++) { drawCrisp = currentModelView[i] === identityMatrix[i]; } /* Draw crisp lines if the line is vertical or horizontal with the following method * If any transformations have been applied to the sketch, don't make the line crisp * If the line is directed up or to the left, reverse it by swapping x1/x2 or y1/y2 * Make the line 1 pixel longer to work around cross-platform canvas implementations * If the lineWidth is odd, translate the line by 0.5 in the perpendicular direction * Even lineWidths do not need to be translated because the canvas will draw them on pixel boundaries * Change the cap to butt-end to work around cross-platform canvas implementations * Reverse the translate and lineCap canvas state changes after drawing the line */ if (drawCrisp) { if (x1 === x2) { if (y1 > y2) { swap = y1; y1 = y2; y2 = swap; } y2++; if (lineWidth % 2 === 1) { curContext.translate(0.5, 0.0); } } else if (y1 === y2) { if (x1 > x2) { swap = x1; x1 = x2; x2 = swap; } x2++; if (lineWidth % 2 === 1) { curContext.translate(0.0, 0.5); } } if (lineWidth === 1) { lineCap = curContext.lineCap; curContext.lineCap = 'butt'; } } curContext.beginPath(); curContext.moveTo(x1 || 0, y1 || 0); curContext.lineTo(x2 || 0, y2 || 0); executeContextStroke(); if (drawCrisp) { if (x1 === x2 && lineWidth % 2 === 1) { curContext.translate(-0.5, 0.0); } else if (y1 === y2 && lineWidth % 2 === 1) { curContext.translate(0.0, -0.5); } if (lineWidth === 1) { curContext.lineCap = lineCap; } } };
Drawing3D.prototype.line = function(x1, y1, z1, x2, y2, z2) { if (y2 === undef || z2 === undef) { // 2D line called in 3D context z2 = 0; y2 = x2; x2 = z1; z1 = 0; }
// a line is only defined if it has different start and end coordinates. // If they are the same, we call point instead. if (x1===x2 && y1===y2 && z1===z2) { p.point(x1,y1,z1); return; }
var lineVerts = [x1, y1, z1, x2, y2, z2];
var view = new PMatrix3D(); view.scale(1, -1, 1); view.apply(modelView.array()); view.transpose();
if (lineWidth > 0 && doStroke) { curContext.useProgram(programObject2D);
uniformMatrix("uModel2d", programObject2D, "uModel", false, [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]); uniformMatrix("uView2d", programObject2D, "uView", false, view.array());
uniformf("uColor2d", programObject2D, "uColor", strokeStyle); uniformi("uIsDrawingText", programObject2D, "uIsDrawingText", false);
vertexAttribPointer("aVertex2d", programObject2D, "aVertex", 3, lineBuffer); disableVertexAttribPointer("aTextureCoord2d", programObject2D, "aTextureCoord");
curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array(lineVerts), curContext.STREAM_DRAW); curContext.drawArrays(curContext.LINES, 0, 2); } };
/** * Draws a Bezier curve on the screen. These curves are defined by a series of anchor and control points. The first * two parameters specify the first anchor point and the last two parameters specify the other anchor point. The * middle parameters specify the control points which define the shape of the curve. Bezier curves were developed * by French engineer Pierre Bezier. Using the 3D version of requires rendering with P3D or OPENGL (see the * Environment reference for more information). * * @param {int | float} x1,y1,z1 coordinates for the first anchor point * @param {int | float} cx1,cy1,cz1 coordinates for the first control point * @param {int | float} cx2,cy2,cz2 coordinates for the second control point * @param {int | float} x2,y2,z2 coordinates for the second anchor point * * @see bezierVertex * @see curve */ Drawing2D.prototype.bezier = function() { if (arguments.length !== 8) { throw("You must use 8 parameters for bezier() in 2D mode"); }
p.beginShape(); p.vertex( arguments[0], arguments[1] ); p.bezierVertex( arguments[2], arguments[3], arguments[4], arguments[5], arguments[6], arguments[7] ); p.endShape(); };
Drawing3D.prototype.bezier = function() { if (arguments.length !== 12) { throw("You must use 12 parameters for bezier() in 3D mode"); }
p.beginShape(); p.vertex( arguments[0], arguments[1], arguments[2] ); p.bezierVertex( arguments[3], arguments[4], arguments[5], arguments[6], arguments[7], arguments[8], arguments[9], arguments[10], arguments[11] ); p.endShape(); };
/** * Sets the resolution at which Beziers display. The default value is 20. This function is only useful when using the P3D * or OPENGL renderer as the default (JAVA2D) renderer does not use this information. * * @param {int} detail resolution of the curves * * @see curve * @see curveVertex * @see curveTightness */ p.bezierDetail = function( detail ){ bezDetail = detail; };
/** * The bezierPoint() function evalutes quadratic bezier at point t for points a, b, c, d. * The parameter t varies between 0 and 1. The a and d parameters are the * on-curve points, b and c are the control points. To make a two-dimensional * curve, call this function once with the x coordinates and a second time * with the y coordinates to get the location of a bezier curve at t. * * @param {float} a coordinate of first point on the curve * @param {float} b coordinate of first control point * @param {float} c coordinate of second control point * @param {float} d coordinate of second point on the curve * @param {float} t value between 0 and 1 * * @see #bezier() * @see #bezierVertex() * @see #curvePoint() */ p.bezierPoint = function(a, b, c, d, t) { return (1 - t) * (1 - t) * (1 - t) * a + 3 * (1 - t) * (1 - t) * t * b + 3 * (1 - t) * t * t * c + t * t * t * d; };
/** * The bezierTangent() function calculates the tangent of a point on a Bezier curve. There is a good * definition of "tangent" at Wikipedia: <a href="http://en.wikipedia.org/wiki/Tangent" target="new">http://en.wikipedia.org/wiki/Tangent</a> * * @param {float} a coordinate of first point on the curve * @param {float} b coordinate of first control point * @param {float} c coordinate of second control point * @param {float} d coordinate of second point on the curve * @param {float} t value between 0 and 1 * * @see #bezier() * @see #bezierVertex() * @see #curvePoint() */ p.bezierTangent = function(a, b, c, d, t) { return (3 * t * t * (-a + 3 * b - 3 * c + d) + 6 * t * (a - 2 * b + c) + 3 * (-a + b)); };
/** * The curvePoint() function evalutes the Catmull-Rom curve at point t for points a, b, c, d. The * parameter t varies between 0 and 1, a and d are points on the curve, * and b and c are the control points. This can be done once with the x * coordinates and a second time with the y coordinates to get the * location of a curve at t. * * @param {int|float} a coordinate of first point on the curve * @param {int|float} b coordinate of second point on the curve * @param {int|float} c coordinate of third point on the curve * @param {int|float} d coordinate of fourth point on the curve * @param {float} t value between 0 and 1 * * @see #curve() * @see #curveVertex() * @see #bezierPoint() */ p.curvePoint = function(a, b, c, d, t) { return 0.5 * ((2 * b) + (-a + c) * t + (2 * a - 5 * b + 4 * c - d) * t * t + (-a + 3 * b - 3 * c + d) * t * t * t); };
/** * The curveTangent() function calculates the tangent of a point on a Catmull-Rom curve. * There is a good definition of "tangent" at Wikipedia: <a href="http://en.wikipedia.org/wiki/Tangent" target="new">http://en.wikipedia.org/wiki/Tangent</a>. * * @param {int|float} a coordinate of first point on the curve * @param {int|float} b coordinate of first control point * @param {int|float} c coordinate of second control point * @param {int|float} d coordinate of second point on the curve * @param {float} t value between 0 and 1 * * @see #curve() * @see #curveVertex() * @see #curvePoint() * @see #bezierTangent() */ p.curveTangent = function(a, b, c, d, t) { return 0.5 * ((-a + c) + 2 * (2 * a - 5 * b + 4 * c - d) * t + 3 * (-a + 3 * b - 3 * c + d) * t * t); };
/** * A triangle is a plane created by connecting three points. The first two arguments specify the first point, * the middle two arguments specify the second point, and the last two arguments specify the third point. * * @param {int | float} x1 x-coordinate of the first point * @param {int | float} y1 y-coordinate of the first point * @param {int | float} x2 x-coordinate of the second point * @param {int | float} y2 y-coordinate of the second point * @param {int | float} x3 x-coordinate of the third point * @param {int | float} y3 y-coordinate of the third point */ p.triangle = function(x1, y1, x2, y2, x3, y3) { p.beginShape(PConstants.TRIANGLES); p.vertex(x1, y1, 0); p.vertex(x2, y2, 0); p.vertex(x3, y3, 0); p.endShape(); };
/** * A quad is a quadrilateral, a four sided polygon. It is similar to a rectangle, but the angles between its * edges are not constrained to ninety degrees. The first pair of parameters (x1,y1) sets the first vertex * and the subsequent pairs should proceed clockwise or counter-clockwise around the defined shape. * * @param {float | int} x1 x-coordinate of the first corner * @param {float | int} y1 y-coordinate of the first corner * @param {float | int} x2 x-coordinate of the second corner * @param {float | int} y2 y-coordinate of the second corner * @param {float | int} x3 x-coordinate of the third corner * @param {float | int} y3 y-coordinate of the third corner * @param {float | int} x4 x-coordinate of the fourth corner * @param {float | int} y4 y-coordinate of the fourth corner */ p.quad = function(x1, y1, x2, y2, x3, y3, x4, y4) { p.beginShape(PConstants.QUADS); p.vertex(x1, y1, 0); p.vertex(x2, y2, 0); p.vertex(x3, y3, 0); p.vertex(x4, y4, 0); p.endShape(); };
var roundedRect$2d = function(x, y, width, height, tl, tr, br, bl) { if (bl === undef) { tr = tl; br = tl; bl = tl; } var halfWidth = width / 2, halfHeight = height / 2; if (tl > halfWidth || tl > halfHeight) { tl = Math.min(halfWidth, halfHeight); } if (tr > halfWidth || tr > halfHeight) { tr = Math.min(halfWidth, halfHeight); } if (br > halfWidth || br > halfHeight) { br = Math.min(halfWidth, halfHeight); } if (bl > halfWidth || bl > halfHeight) { bl = Math.min(halfWidth, halfHeight); } // Translate the stroke by (0.5, 0.5) to draw a crisp border if (!doFill || doStroke) { curContext.translate(0.5, 0.5); } curContext.beginPath(); curContext.moveTo(x + tl, y); curContext.lineTo(x + width - tr, y); curContext.quadraticCurveTo(x + width, y, x + width, y + tr); curContext.lineTo(x + width, y + height - br); curContext.quadraticCurveTo(x + width, y + height, x + width - br, y + height); curContext.lineTo(x + bl, y + height); curContext.quadraticCurveTo(x, y + height, x, y + height - bl); curContext.lineTo(x, y + tl); curContext.quadraticCurveTo(x, y, x + tl, y); if (!doFill || doStroke) { curContext.translate(-0.5, -0.5); } executeContextFill(); executeContextStroke(); };
/** * Draws a rectangle to the screen. A rectangle is a four-sided shape with every angle at ninety * degrees. The first two parameters set the location, the third sets the width, and the fourth * sets the height. The origin is changed with the rectMode() function. * * @param {int|float} x x-coordinate of the rectangle * @param {int|float} y y-coordinate of the rectangle * @param {int|float} width width of the rectangle * @param {int|float} height height of the rectangle * * @see rectMode * @see quad */ Drawing2D.prototype.rect = function(x, y, width, height, tl, tr, br, bl) { if (!width && !height) { return; }
if (curRectMode === PConstants.CORNERS) { width -= x; height -= y; } else if (curRectMode === PConstants.RADIUS) { width *= 2; height *= 2; x -= width / 2; y -= height / 2; } else if (curRectMode === PConstants.CENTER) { x -= width / 2; y -= height / 2; }
x = Math.round(x); y = Math.round(y); width = Math.round(width); height = Math.round(height); if (tl !== undef) { roundedRect$2d(x, y, width, height, tl, tr, br, bl); return; }
// Translate the line by (0.5, 0.5) to draw a crisp rectangle border if (doStroke && lineWidth % 2 === 1) { curContext.translate(0.5, 0.5); } curContext.beginPath(); curContext.rect(x, y, width, height); executeContextFill(); executeContextStroke(); if (doStroke && lineWidth % 2 === 1) { curContext.translate(-0.5, -0.5); } };
Drawing3D.prototype.rect = function(x, y, width, height, tl, tr, br, bl) { if (tl !== undef) { throw "rect() with rounded corners is not supported in 3D mode"; }
if (curRectMode === PConstants.CORNERS) { width -= x; height -= y; } else if (curRectMode === PConstants.RADIUS) { width *= 2; height *= 2; x -= width / 2; y -= height / 2; } else if (curRectMode === PConstants.CENTER) { x -= width / 2; y -= height / 2; }
// Modeling transformation var model = new PMatrix3D(); model.translate(x, y, 0); model.scale(width, height, 1); model.transpose();
// viewing transformation needs to have Y flipped // becuase that's what Processing does. var view = new PMatrix3D(); view.scale(1, -1, 1); view.apply(modelView.array()); view.transpose();
if (lineWidth > 0 && doStroke) { curContext.useProgram(programObject2D); uniformMatrix("uModel2d", programObject2D, "uModel", false, model.array()); uniformMatrix("uView2d", programObject2D, "uView", false, view.array()); uniformf("uColor2d", programObject2D, "uColor", strokeStyle); uniformi("uIsDrawingText2d", programObject2D, "uIsDrawingText", false); vertexAttribPointer("aVertex2d", programObject2D, "aVertex", 3, rectBuffer); disableVertexAttribPointer("aTextureCoord2d", programObject2D, "aTextureCoord"); curContext.drawArrays(curContext.LINE_LOOP, 0, rectVerts.length / 3); }
if (doFill) { curContext.useProgram(programObject3D); uniformMatrix("uModel3d", programObject3D, "uModel", false, model.array()); uniformMatrix("uView3d", programObject3D, "uView", false, view.array());
// fix stitching problems. (lines get occluded by triangles // since they share the same depth values). This is not entirely // working, but it's a start for drawing the outline. So // developers can start playing around with styles. curContext.enable(curContext.POLYGON_OFFSET_FILL); curContext.polygonOffset(1, 1);
uniformf("color3d", programObject3D, "uColor", fillStyle);
if(lightCount > 0){ var v = new PMatrix3D(); v.set(view);
var m = new PMatrix3D(); m.set(model);
v.mult(m);
var normalMatrix = new PMatrix3D(); normalMatrix.set(v); normalMatrix.invert(); normalMatrix.transpose();
uniformMatrix("uNormalTransform3d", programObject3D, "uNormalTransform", false, normalMatrix.array()); vertexAttribPointer("aNormal3d", programObject3D, "aNormal", 3, rectNormBuffer); } else{ disableVertexAttribPointer("normal3d", programObject3D, "aNormal"); }
vertexAttribPointer("vertex3d", programObject3D, "aVertex", 3, rectBuffer);
curContext.drawArrays(curContext.TRIANGLE_FAN, 0, rectVerts.length / 3); curContext.disable(curContext.POLYGON_OFFSET_FILL); } };
/** * Draws an ellipse (oval) in the display window. An ellipse with an equal width and height is a circle. * The first two parameters set the location, the third sets the width, and the fourth sets the height. The origin may be * changed with the ellipseMode() function. * * @param {float|int} x x-coordinate of the ellipse * @param {float|int} y y-coordinate of the ellipse * @param {float|int} width width of the ellipse * @param {float|int} height height of the ellipse * * @see ellipseMode */ Drawing2D.prototype.ellipse = function(x, y, width, height) { x = x || 0; y = y || 0;
if (width <= 0 && height <= 0) { return; }
if (curEllipseMode === PConstants.RADIUS) { width *= 2; height *= 2; } else if (curEllipseMode === PConstants.CORNERS) { width = width - x; height = height - y; x += width / 2; y += height / 2; } else if (curEllipseMode === PConstants.CORNER) { x += width / 2; y += height / 2; }
// Shortcut for drawing a 2D circle if (width === height) { curContext.beginPath(); curContext.arc(x, y, width / 2, 0, PConstants.TWO_PI, false); executeContextFill(); executeContextStroke(); } else { var w = width / 2, h = height / 2, C = 0.5522847498307933, c_x = C * w, c_y = C * h;
p.beginShape(); p.vertex(x + w, y); p.bezierVertex(x + w, y - c_y, x + c_x, y - h, x, y - h); p.bezierVertex(x - c_x, y - h, x - w, y - c_y, x - w, y); p.bezierVertex(x - w, y + c_y, x - c_x, y + h, x, y + h); p.bezierVertex(x + c_x, y + h, x + w, y + c_y, x + w, y); p.endShape(); } };
Drawing3D.prototype.ellipse = function(x, y, width, height) { x = x || 0; y = y || 0;
if (width <= 0 && height <= 0) { return; }
if (curEllipseMode === PConstants.RADIUS) { width *= 2; height *= 2; } else if (curEllipseMode === PConstants.CORNERS) { width = width - x; height = height - y; x += width / 2; y += height / 2; } else if (curEllipseMode === PConstants.CORNER) { x += width / 2; y += height / 2; }
var w = width / 2, h = height / 2, C = 0.5522847498307933, c_x = C * w, c_y = C * h;
p.beginShape(); p.vertex(x + w, y); p.bezierVertex(x + w, y - c_y, 0, x + c_x, y - h, 0, x, y - h, 0); p.bezierVertex(x - c_x, y - h, 0, x - w, y - c_y, 0, x - w, y, 0); p.bezierVertex(x - w, y + c_y, 0, x - c_x, y + h, 0, x, y + h, 0); p.bezierVertex(x + c_x, y + h, 0, x + w, y + c_y, 0, x + w, y, 0); p.endShape();
if (doFill) { //temporary workaround to not working fills for bezier -- will fix later var xAv = 0, yAv = 0, i, j; for (i = 0; i < vertArray.length; i++) { xAv += vertArray[i][0]; yAv += vertArray[i][1]; } xAv /= vertArray.length; yAv /= vertArray.length; var vert = [], fillVertArray = [], colorVertArray = []; vert[0] = xAv; vert[1] = yAv; vert[2] = 0; vert[3] = 0; vert[4] = 0; vert[5] = fillStyle[0]; vert[6] = fillStyle[1]; vert[7] = fillStyle[2]; vert[8] = fillStyle[3]; vert[9] = strokeStyle[0]; vert[10] = strokeStyle[1]; vert[11] = strokeStyle[2]; vert[12] = strokeStyle[3]; vert[13] = normalX; vert[14] = normalY; vert[15] = normalZ; vertArray.unshift(vert); for (i = 0; i < vertArray.length; i++) { for (j = 0; j < 3; j++) { fillVertArray.push(vertArray[i][j]); } for (j = 5; j < 9; j++) { colorVertArray.push(vertArray[i][j]); } } fill3D(fillVertArray, "TRIANGLE_FAN", colorVertArray); } };
/** * Sets the current normal vector. This is for drawing three dimensional shapes and surfaces and * specifies a vector perpendicular to the surface of the shape which determines how lighting affects * it. Processing attempts to automatically assign normals to shapes, but since that's imperfect, * this is a better option when you want more control. This function is identical to glNormal3f() in OpenGL. * * @param {float} nx x direction * @param {float} ny y direction * @param {float} nz z direction * * @see beginShape * @see endShape * @see lights */ p.normal = function(nx, ny, nz) { if (arguments.length !== 3 || !(typeof nx === "number" && typeof ny === "number" && typeof nz === "number")) { throw "normal() requires three numeric arguments."; }
normalX = nx; normalY = ny; normalZ = nz;
if (curShape !== 0) { if (normalMode === PConstants.NORMAL_MODE_AUTO) { normalMode = PConstants.NORMAL_MODE_SHAPE; } else if (normalMode === PConstants.NORMAL_MODE_SHAPE) { normalMode = PConstants.NORMAL_MODE_VERTEX; } } };
//////////////////////////////////////////////////////////////////////////// // Raster drawing functions ////////////////////////////////////////////////////////////////////////////
/** * Saves an image from the display window. Images are saved in TIFF, TARGA, JPEG, and PNG format * depending on the extension within the filename parameter. For example, "image.tif" will have * a TIFF image and "image.png" will save a PNG image. If no extension is included in the filename, * the image will save in TIFF format and .tif will be added to the name. These files are saved to * the sketch's folder, which may be opened by selecting "Show sketch folder" from the "Sketch" menu. * It is not possible to use save() while running the program in a web browser. All images saved * from the main drawing window will be opaque. To save images without a background, use createGraphics(). * * @param {String} filename any sequence of letters and numbers * * @see saveFrame * @see createGraphics */ p.save = function(file, img) { // file is unused at the moment // may implement this differently in later release if (img !== undef) { return window.open(img.toDataURL(),"_blank"); } return window.open(p.externals.canvas.toDataURL(),"_blank"); };
var saveNumber = 0;
p.saveFrame = function(file) { if(file === undef) { // use default name template if parameter is not specified file = "screen-####.png"; } // Increment changeable part: screen-0000.png, screen-0001.png, ... var frameFilename = file.replace(/#+/, function(all) { var s = "" + (saveNumber++); while(s.length < all.length) { s = "0" + s; } return s; }); p.save(frameFilename); };
var utilityContext2d = document.createElement("canvas").getContext("2d");
var canvasDataCache = [undef, undef, undef]; // we need three for now
function getCanvasData(obj, w, h) { var canvasData = canvasDataCache.shift();
if (canvasData === undef) { canvasData = {}; canvasData.canvas = document.createElement("canvas"); canvasData.context = canvasData.canvas.getContext('2d'); }
canvasDataCache.push(canvasData);
var canvas = canvasData.canvas, context = canvasData.context, width = w || obj.width, height = h || obj.height;
canvas.width = width; canvas.height = height;
if (!obj) { context.clearRect(0, 0, width, height); } else if ("data" in obj) { // ImageData context.putImageData(obj, 0, 0); } else { context.clearRect(0, 0, width, height); context.drawImage(obj, 0, 0, width, height); } return canvasData; }
/** * Handle the sketch code for pixels[] and pixels.length * parser code converts pixels[] to getPixels() * or setPixels(), .length becomes getLength() */ function buildPixelsObject(pImage) { return {
getLength: (function(aImg) { return function() { if (aImg.isRemote) { throw "Image is loaded remotely. Cannot get length."; } else { return aImg.imageData.data.length ? aImg.imageData.data.length/4 : 0; } }; }(pImage)),
getPixel: (function(aImg) { return function(i) { var offset = i*4, data = aImg.imageData.data;
if (aImg.isRemote) { throw "Image is loaded remotely. Cannot get pixels."; }
return (data[offset+3] << 24) & PConstants.ALPHA_MASK | (data[offset] << 16) & PConstants.RED_MASK | (data[offset+1] << 8) & PConstants.GREEN_MASK | data[offset+2] & PConstants.BLUE_MASK; }; }(pImage)),
setPixel: (function(aImg) { return function(i, c) { var offset = i*4, data = aImg.imageData.data;
if (aImg.isRemote) { throw "Image is loaded remotely. Cannot set pixel."; }
data[offset+0] = (c & PConstants.RED_MASK) >>> 16; data[offset+1] = (c & PConstants.GREEN_MASK) >>> 8; data[offset+2] = (c & PConstants.BLUE_MASK); data[offset+3] = (c & PConstants.ALPHA_MASK) >>> 24; aImg.__isDirty = true; }; }(pImage)),
toArray: (function(aImg) { return function() { var arr = [], data = aImg.imageData.data, length = aImg.width * aImg.height;
if (aImg.isRemote) { throw "Image is loaded remotely. Cannot get pixels."; }
for (var i = 0, offset = 0; i < length; i++, offset += 4) { arr.push( (data[offset+3] << 24) & PConstants.ALPHA_MASK | (data[offset] << 16) & PConstants.RED_MASK | (data[offset+1] << 8) & PConstants.GREEN_MASK | data[offset+2] & PConstants.BLUE_MASK ); } return arr; }; }(pImage)),
set: (function(aImg) { return function(arr) { var offset, data, c; if (this.isRemote) { throw "Image is loaded remotely. Cannot set pixels."; }
data = aImg.imageData.data; for (var i = 0, aL = arr.length; i < aL; i++) { c = arr[i]; offset = i*4;
data[offset+0] = (c & PConstants.RED_MASK) >>> 16; data[offset+1] = (c & PConstants.GREEN_MASK) >>> 8; data[offset+2] = (c & PConstants.BLUE_MASK); data[offset+3] = (c & PConstants.ALPHA_MASK) >>> 24; } aImg.__isDirty = true; }; }(pImage))
}; }
/** * Datatype for storing images. Processing can display .gif, .jpg, .tga, and .png images. Images may be * displayed in 2D and 3D space. Before an image is used, it must be loaded with the loadImage() function. * The PImage object contains fields for the width and height of the image, as well as an array called * pixels[] which contains the values for every pixel in the image. A group of methods, described below, * allow easy access to the image's pixels and alpha channel and simplify the process of compositing. * Before using the pixels[] array, be sure to use the loadPixels() method on the image to make sure that the * pixel data is properly loaded. To create a new image, use the createImage() function (do not use new PImage()). * * @param {int} width image width * @param {int} height image height * @param {MODE} format Either RGB, ARGB, ALPHA (grayscale alpha channel) * * @returns {PImage} * * @see loadImage * @see imageMode * @see createImage */ var PImage = function(aWidth, aHeight, aFormat) {
// Keep track of whether or not the cached imageData has been touched. this.__isDirty = false;
if (aWidth instanceof HTMLImageElement) { // convert an <img> to a PImage this.fromHTMLImageData(aWidth); } else if (aHeight || aFormat) { this.width = aWidth || 1; this.height = aHeight || 1;
// Stuff a canvas into sourceImg so image() calls can use drawImage like an <img> var canvas = this.sourceImg = document.createElement("canvas"); canvas.width = this.width; canvas.height = this.height;
var imageData = this.imageData = canvas.getContext('2d').createImageData(this.width, this.height); this.format = (aFormat === PConstants.ARGB || aFormat === PConstants.ALPHA) ? aFormat : PConstants.RGB; if (this.format === PConstants.RGB) { // Set the alpha channel of an RGB image to opaque. for (var i = 3, data = this.imageData.data, len = data.length; i < len; i += 4) { data[i] = 255; } }
this.__isDirty = true; this.updatePixels(); } else { this.width = 0; this.height = 0; this.imageData = utilityContext2d.createImageData(1, 1); this.format = PConstants.ARGB; }
this.pixels = buildPixelsObject(this); }; PImage.prototype = {
/** * Temporary hack to deal with cross-Processing-instance created PImage. See * tickets #1623 and #1644. */ __isPImage: true,
/** * @member PImage * Updates the image with the data in its pixels[] array. Use in conjunction with loadPixels(). If * you're only reading pixels from the array, there's no need to call updatePixels(). * Certain renderers may or may not seem to require loadPixels() or updatePixels(). However, the rule * is that any time you want to manipulate the pixels[] array, you must first call loadPixels(), and * after changes have been made, call updatePixels(). Even if the renderer may not seem to use this * function in the current Processing release, this will always be subject to change. * Currently, none of the renderers use the additional parameters to updatePixels(). */ updatePixels: function() { var canvas = this.sourceImg; if (canvas && canvas instanceof HTMLCanvasElement && this.__isDirty) { canvas.getContext('2d').putImageData(this.imageData, 0, 0); } this.__isDirty = false; },
fromHTMLImageData: function(htmlImg) { // convert an <img> to a PImage var canvasData = getCanvasData(htmlImg); try { var imageData = canvasData.context.getImageData(0, 0, htmlImg.width, htmlImg.height); this.fromImageData(imageData); } catch(e) { if (htmlImg.width && htmlImg.height) { this.isRemote = true; this.width = htmlImg.width; this.height = htmlImg.height; } } this.sourceImg = htmlImg; },
'get': function(x, y, w, h) { if (!arguments.length) { return p.get(this); } if (arguments.length === 2) { return p.get(x, y, this); } if (arguments.length === 4) { return p.get(x, y, w, h, this); } },
/** * @member PImage * Changes the color of any pixel or writes an image directly into the image. The x and y parameter * specify the pixel or the upper-left corner of the image. The color parameter specifies the color value. * Setting the color of a single pixel with set(x, y) is easy, but not as fast as putting the data * directly into pixels[]. The equivalent statement to "set(x, y, #000000)" using pixels[] is * "pixels[y*width+x] = #000000". Processing requires calling loadPixels() to load the display window * data into the pixels[] array before getting the values and calling updatePixels() to update the window. * * @param {int} x x-coordinate of the pixel or upper-left corner of the image * @param {int} y y-coordinate of the pixel or upper-left corner of the image * @param {color} color any value of the color datatype * * @see get * @see pixels[] * @see copy */ 'set': function(x, y, c) { p.set(x, y, c, this); this.__isDirty = true; },
/** * @member PImage * Blends a region of pixels into the image specified by the img parameter. These copies utilize full * alpha channel support and a choice of the following modes to blend the colors of source pixels (A) * with the ones of pixels in the destination image (B): * BLEND - linear interpolation of colours: C = A*factor + B * ADD - additive blending with white clip: C = min(A*factor + B, 255) * SUBTRACT - subtractive blending with black clip: C = max(B - A*factor, 0) * DARKEST - only the darkest colour succeeds: C = min(A*factor, B) * LIGHTEST - only the lightest colour succeeds: C = max(A*factor, B) * DIFFERENCE - subtract colors from underlying image. * EXCLUSION - similar to DIFFERENCE, but less extreme. * MULTIPLY - Multiply the colors, result will always be darker. * SCREEN - Opposite multiply, uses inverse values of the colors. * OVERLAY - A mix of MULTIPLY and SCREEN. Multiplies dark values, and screens light values. * HARD_LIGHT - SCREEN when greater than 50% gray, MULTIPLY when lower. * SOFT_LIGHT - Mix of DARKEST and LIGHTEST. Works like OVERLAY, but not as harsh. * DODGE - Lightens light tones and increases contrast, ignores darks. Called "Color Dodge" in Illustrator and Photoshop. * BURN - Darker areas are applied, increasing contrast, ignores lights. Called "Color Burn" in Illustrator and Photoshop. * All modes use the alpha information (highest byte) of source image pixels as the blending factor. * If the source and destination regions are different sizes, the image will be automatically resized to * match the destination size. If the srcImg parameter is not used, the display window is used as the source image. * This function ignores imageMode(). * * @param {int} x X coordinate of the source's upper left corner * @param {int} y Y coordinate of the source's upper left corner * @param {int} width source image width * @param {int} height source image height * @param {int} dx X coordinate of the destinations's upper left corner * @param {int} dy Y coordinate of the destinations's upper left corner * @param {int} dwidth destination image width * @param {int} dheight destination image height * @param {PImage} srcImg an image variable referring to the source image * @param {MODE} MODE Either BLEND, ADD, SUBTRACT, LIGHTEST, DARKEST, DIFFERENCE, EXCLUSION, * MULTIPLY, SCREEN, OVERLAY, HARD_LIGHT, SOFT_LIGHT, DODGE, BURN * * @see alpha * @see copy */ blend: function(srcImg, x, y, width, height, dx, dy, dwidth, dheight, MODE) { if (arguments.length === 9) { p.blend(this, srcImg, x, y, width, height, dx, dy, dwidth, dheight, this); } else if (arguments.length === 10) { p.blend(srcImg, x, y, width, height, dx, dy, dwidth, dheight, MODE, this); } delete this.sourceImg; },
/** * @member PImage * Copies a region of pixels from one image into another. If the source and destination regions * aren't the same size, it will automatically resize source pixels to fit the specified target region. * No alpha information is used in the process, however if the source image has an alpha channel set, * it will be copied as well. This function ignores imageMode(). * * @param {int} sx X coordinate of the source's upper left corner * @param {int} sy Y coordinate of the source's upper left corner * @param {int} swidth source image width * @param {int} sheight source image height * @param {int} dx X coordinate of the destinations's upper left corner * @param {int} dy Y coordinate of the destinations's upper left corner * @param {int} dwidth destination image width * @param {int} dheight destination image height * @param {PImage} srcImg an image variable referring to the source image * * @see alpha * @see blend */ copy: function(srcImg, sx, sy, swidth, sheight, dx, dy, dwidth, dheight) { if (arguments.length === 8) { p.blend(this, srcImg, sx, sy, swidth, sheight, dx, dy, dwidth, PConstants.REPLACE, this); } else if (arguments.length === 9) { p.blend(srcImg, sx, sy, swidth, sheight, dx, dy, dwidth, dheight, PConstants.REPLACE, this); } delete this.sourceImg; },
/** * @member PImage * Filters an image as defined by one of the following modes: * THRESHOLD - converts the image to black and white pixels depending if they are above or below * the threshold defined by the level parameter. The level must be between 0.0 (black) and 1.0(white). * If no level is specified, 0.5 is used. * GRAY - converts any colors in the image to grayscale equivalents * INVERT - sets each pixel to its inverse value * POSTERIZE - limits each channel of the image to the number of colors specified as the level parameter * BLUR - executes a Guassian blur with the level parameter specifying the extent of the blurring. * If no level parameter is used, the blur is equivalent to Guassian blur of radius 1. * OPAQUE - sets the alpha channel to entirely opaque. * ERODE - reduces the light areas with the amount defined by the level parameter. * DILATE - increases the light areas with the amount defined by the level parameter * * @param {MODE} MODE Either THRESHOLD, GRAY, INVERT, POSTERIZE, BLUR, OPAQUE, ERODE, or DILATE * @param {int|float} param in the range from 0 to 1 */ filter: function(mode, param) { if (arguments.length === 2) { p.filter(mode, param, this); } else if (arguments.length === 1) { // no param specified, send null to show its invalid p.filter(mode, null, this); } delete this.sourceImg; },
/** * @member PImage * Saves the image into a file. Images are saved in TIFF, TARGA, JPEG, and PNG format depending on * the extension within the filename parameter. For example, "image.tif" will have a TIFF image and * "image.png" will save a PNG image. If no extension is included in the filename, the image will save * in TIFF format and .tif will be added to the name. These files are saved to the sketch's folder, * which may be opened by selecting "Show sketch folder" from the "Sketch" menu. It is not possible to * use save() while running the program in a web browser. * To save an image created within the code, rather than through loading, it's necessary to make the * image with the createImage() function so it is aware of the location of the program and can therefore * save the file to the right place. See the createImage() reference for more information. * * @param {String} filename a sequence of letters and numbers */ save: function(file){ p.save(file,this); },
/** * @member PImage * Resize the image to a new width and height. To make the image scale proportionally, use 0 as the * value for the wide or high parameter. * * @param {int} wide the resized image width * @param {int} high the resized image height * * @see get */ resize: function(w, h) { if (this.isRemote) { // Remote images cannot access imageData throw "Image is loaded remotely. Cannot resize."; } if (this.width !== 0 || this.height !== 0) { // make aspect ratio if w or h is 0 if (w === 0 && h !== 0) { w = Math.floor(this.width / this.height * h); } else if (h === 0 && w !== 0) { h = Math.floor(this.height / this.width * w); } // put 'this.imageData' into a new canvas var canvas = getCanvasData(this.imageData).canvas; // pull imageData object out of canvas into ImageData object var imageData = getCanvasData(canvas, w, h).context.getImageData(0, 0, w, h); // set this as new pimage this.fromImageData(imageData); } },
/** * @member PImage * Masks part of an image from displaying by loading another image and using it as an alpha channel. * This mask image should only contain grayscale data, but only the blue color channel is used. The * mask image needs to be the same size as the image to which it is applied. * In addition to using a mask image, an integer array containing the alpha channel data can be * specified directly. This method is useful for creating dynamically generated alpha masks. This * array must be of the same length as the target image's pixels array and should contain only grayscale * data of values between 0-255. * * @param {PImage} maskImg any PImage object used as the alpha channel for "img", needs to be same * size as "img" * @param {int[]} maskArray any array of Integer numbers used as the alpha channel, needs to be same * length as the image's pixel array */ mask: function(mask) { var obj = this.toImageData(), i, size;
if (mask instanceof PImage || mask.__isPImage) { if (mask.width === this.width && mask.height === this.height) { mask = mask.toImageData();
for (i = 2, size = this.width * this.height * 4; i < size; i += 4) { // using it as an alpha channel obj.data[i + 1] = mask.data[i]; // but only the blue color channel } } else { throw "mask must have the same dimensions as PImage."; } } else if (mask instanceof Array) { if (this.width * this.height === mask.length) { for (i = 0, size = mask.length; i < size; ++i) { obj.data[i * 4 + 3] = mask[i]; } } else { throw "mask array must be the same length as PImage pixels array."; } }
this.fromImageData(obj); },
// These are intentionally left blank for PImages, we work live with pixels and draw as necessary /** * @member PImage * Loads the pixel data for the image into its pixels[] array. This function must always be called * before reading from or writing to pixels[]. * Certain renderers may or may not seem to require loadPixels() or updatePixels(). However, the * rule is that any time you want to manipulate the pixels[] array, you must first call loadPixels(), * and after changes have been made, call updatePixels(). Even if the renderer may not seem to use * this function in the current Processing release, this will always be subject to change. */ loadPixels: noop,
toImageData: function() { if (this.isRemote) { return this.sourceImg; }
if (!this.__isDirty) { return this.imageData; }
var canvasData = getCanvasData(this.sourceImg); return canvasData.context.getImageData(0, 0, this.width, this.height); },
toDataURL: function() { if (this.isRemote) { // Remote images cannot access imageData throw "Image is loaded remotely. Cannot create dataURI."; } var canvasData = getCanvasData(this.imageData); return canvasData.canvas.toDataURL(); },
fromImageData: function(canvasImg) { var w = canvasImg.width, h = canvasImg.height, canvas = document.createElement('canvas'), ctx = canvas.getContext('2d');
this.width = canvas.width = w; this.height = canvas.height = h;
ctx.putImageData(canvasImg, 0, 0);
// changed for 0.9 this.format = PConstants.ARGB;
this.imageData = canvasImg; this.sourceImg = canvas; } };
p.PImage = PImage;
/** * Creates a new PImage (the datatype for storing images). This provides a fresh buffer of pixels to play * with. Set the size of the buffer with the width and height parameters. The format parameter defines how * the pixels are stored. See the PImage reference for more information. * Be sure to include all three parameters, specifying only the width and height (but no format) will * produce a strange error. * Advanced users please note that createImage() should be used instead of the syntax new PImage(). * * @param {int} width image width * @param {int} height image height * @param {MODE} format Either RGB, ARGB, ALPHA (grayscale alpha channel) * * @returns {PImage} * * @see PImage * @see PGraphics */ p.createImage = function(w, h, mode) { return new PImage(w,h,mode); };
// Loads an image for display. Type is an extension. Callback is fired on load. /** * Loads an image into a variable of type PImage. Four types of images ( .gif, .jpg, .tga, .png) images may * be loaded. To load correctly, images must be located in the data directory of the current sketch. In most * cases, load all images in setup() to preload them at the start of the program. Loading images inside draw() * will reduce the speed of a program. * The filename parameter can also be a URL to a file found online. For security reasons, a Processing sketch * found online can only download files from the same server from which it came. Getting around this restriction * requires a signed applet. * The extension parameter is used to determine the image type in cases where the image filename does not end * with a proper extension. Specify the extension as the second parameter to loadImage(), as shown in the * third example on this page. * If an image is not loaded successfully, the null value is returned and an error message will be printed to * the console. The error message does not halt the program, however the null value may cause a NullPointerException * if your code does not check whether the value returned from loadImage() is null. * Depending on the type of error, a PImage object may still be returned, but the width and height of the image * will be set to -1. This happens if bad image data is returned or cannot be decoded properly. Sometimes this happens * with image URLs that produce a 403 error or that redirect to a password prompt, because loadImage() will attempt * to interpret the HTML as image data. * * @param {String} filename name of file to load, can be .gif, .jpg, .tga, or a handful of other image * types depending on your platform. * @param {String} extension the type of image to load, for example "png", "gif", "jpg" * * @returns {PImage} * * @see PImage * @see image * @see imageMode * @see background */ p.loadImage = function(file, type, callback) { // if type is specified, we just ignore it
var pimg; // if image is in the preloader cache return a new PImage if (curSketch.imageCache.images[file]) { pimg = new PImage(curSketch.imageCache.images[file]); pimg.loaded = true; return pimg; } // else async load it pimg = new PImage(); var img = document.createElement('img');
pimg.sourceImg = img;
img.onload = (function(aImage, aPImage, aCallback) { var image = aImage; var pimg = aPImage; var callback = aCallback; return function() { // change the <img> object into a PImage now that its loaded pimg.fromHTMLImageData(image); pimg.loaded = true; if (callback) { callback(); } }; }(img, pimg, callback));
img.src = file; // needs to be called after the img.onload function is declared or it wont work in opera return pimg; };
// async loading of large images, same functionality as loadImage above /** * This function load images on a separate thread so that your sketch does not freeze while images load during * setup(). While the image is loading, its width and height will be 0. If an error occurs while loading the image, * its width and height will be set to -1. You'll know when the image has loaded properly because its width and * height will be greater than 0. Asynchronous image loading (particularly when downloading from a server) can * dramatically improve performance. * The extension parameter is used to determine the image type in cases where the image filename does not end * with a proper extension. Specify the extension as the second parameter to requestImage(). * * @param {String} filename name of file to load, can be .gif, .jpg, .tga, or a handful of other image * types depending on your platform. * @param {String} extension the type of image to load, for example "png", "gif", "jpg" * * @returns {PImage} * * @see PImage * @see loadImage */ p.requestImage = p.loadImage;
function get$2(x,y) { var data; // return the color at x,y (int) of curContext if (x >= p.width || x < 0 || y < 0 || y >= p.height) { // x,y is outside image return transparent black return 0; }
// loadPixels() has been called if (isContextReplaced) { var offset = ((0|x) + p.width * (0|y)) * 4; data = p.imageData.data; return (data[offset + 3] << 24) & PConstants.ALPHA_MASK | (data[offset] << 16) & PConstants.RED_MASK | (data[offset + 1] << 8) & PConstants.GREEN_MASK | data[offset + 2] & PConstants.BLUE_MASK; }
// x,y is inside canvas space data = p.toImageData(0|x, 0|y, 1, 1).data; return (data[3] << 24) & PConstants.ALPHA_MASK | (data[0] << 16) & PConstants.RED_MASK | (data[1] << 8) & PConstants.GREEN_MASK | data[2] & PConstants.BLUE_MASK; } function get$3(x,y,img) { if (img.isRemote) { // Remote images cannot access imageData throw "Image is loaded remotely. Cannot get x,y."; } // PImage.get(x,y) was called, return the color (int) at x,y of img var offset = y * img.width * 4 + (x * 4), data = img.imageData.data; return (data[offset + 3] << 24) & PConstants.ALPHA_MASK | (data[offset] << 16) & PConstants.RED_MASK | (data[offset + 1] << 8) & PConstants.GREEN_MASK | data[offset + 2] & PConstants.BLUE_MASK; } function get$4(x, y, w, h) { // return a PImage of w and h from cood x,y of curContext var c = new PImage(w, h, PConstants.ARGB); c.fromImageData(p.toImageData(x, y, w, h)); return c; } function get$5(x, y, w, h, img) { if (img.isRemote) { // Remote images cannot access imageData throw "Image is loaded remotely. Cannot get x,y,w,h."; } // PImage.get(x,y,w,h) was called, return x,y,w,h PImage of img // offset start point needs to be *4 var c = new PImage(w, h, PConstants.ARGB), cData = c.imageData.data, imgWidth = img.width, imgHeight = img.height, imgData = img.imageData.data; // Don't need to copy pixels from the image outside ranges. var startRow = Math.max(0, -y), startColumn = Math.max(0, -x), stopRow = Math.min(h, imgHeight - y), stopColumn = Math.min(w, imgWidth - x); for (var i = startRow; i < stopRow; ++i) { var sourceOffset = ((y + i) * imgWidth + (x + startColumn)) * 4; var targetOffset = (i * w + startColumn) * 4; for (var j = startColumn; j < stopColumn; ++j) { cData[targetOffset++] = imgData[sourceOffset++]; cData[targetOffset++] = imgData[sourceOffset++]; cData[targetOffset++] = imgData[sourceOffset++]; cData[targetOffset++] = imgData[sourceOffset++]; } } c.__isDirty = true; return c; }
// Gets a single pixel or block of pixels from the current Canvas Context or a PImage /** * Reads the color of any pixel or grabs a section of an image. If no parameters are specified, the entire * image is returned. Get the value of one pixel by specifying an x,y coordinate. Get a section of the display * window by specifying an additional width and height parameter. If the pixel requested is outside of the image * window, black is returned. The numbers returned are scaled according to the current color ranges, but only RGB * values are returned by this function. For example, even though you may have drawn a shape with colorMode(HSB), * the numbers returned will be in RGB. * Getting the color of a single pixel with get(x, y) is easy, but not as fast as grabbing the data directly * from pixels[]. The equivalent statement to "get(x, y)" using pixels[] is "pixels[y*width+x]". Processing * requires calling loadPixels() to load the display window data into the pixels[] array before getting the values. * This function ignores imageMode(). * * @param {int} x x-coordinate of the pixel * @param {int} y y-coordinate of the pixel * @param {int} width width of pixel rectangle to get * @param {int} height height of pixel rectangle to get * * @returns {Color|PImage} * * @see set * @see pixels[] * @see imageMode */ p.get = function(x, y, w, h, img) { // for 0 2 and 4 arguments use curContext, otherwise PImage.get was called if (img !== undefined) { return get$5(x, y, w, h, img); } if (h !== undefined) { return get$4(x, y, w, h); } if (w !== undefined) { return get$3(x, y, w); } if (y !== undefined) { return get$2(x, y); } if (x !== undefined) { // PImage.get() was called, return a new PImage return get$5(0, 0, x.width, x.height, x); }
return get$4(0, 0, p.width, p.height); };
/** * Creates and returns a new PGraphics object of the types P2D, P3D, and JAVA2D. Use this class if you need to draw * into an off-screen graphics buffer. It's not possible to use createGraphics() with OPENGL, because it doesn't * allow offscreen use. The DXF and PDF renderers require the filename parameter.
It's important to call * any drawing commands between beginDraw() and endDraw() statements. This is also true for any commands that affect * drawing, such as smooth() or colorMode().
Unlike the main drawing surface which is completely opaque, * surfaces created with createGraphics() can have transparency. This makes it possible to draw into a graphics and * maintain the alpha channel. * * @param {int} width width in pixels * @param {int} height height in pixels * @param {int} renderer Either P2D, P3D, JAVA2D, PDF, DXF * @param {String} filename the name of the file (not supported yet) */ p.createGraphics = function(w, h, render) { var pg = new Processing(); pg.size(w, h, render); pg.background(0,0); return pg; };
// pixels caching function resetContext() { if(isContextReplaced) { curContext = originalContext; isContextReplaced = false;
p.updatePixels(); } } function SetPixelContextWrapper() { function wrapFunction(newContext, name) { function wrapper() { resetContext(); curContext[name].apply(curContext, arguments); } newContext[name] = wrapper; } function wrapProperty(newContext, name) { function getter() { resetContext(); return curContext[name]; } function setter(value) { resetContext(); curContext[name] = value; } p.defineProperty(newContext, name, { get: getter, set: setter }); } for(var n in curContext) { if(typeof curContext[n] === 'function') { wrapFunction(this, n); } else { wrapProperty(this, n); } } } function replaceContext() { if(isContextReplaced) { return; } p.loadPixels(); if(proxyContext === null) { originalContext = curContext; proxyContext = new SetPixelContextWrapper(); } isContextReplaced = true; curContext = proxyContext; setPixelsCached = 0; }
function set$3(x, y, c) { if (x < p.width && x >= 0 && y >= 0 && y < p.height) { replaceContext(); p.pixels.setPixel((0|x)+p.width*(0|y), c); if(++setPixelsCached > maxPixelsCached) { resetContext(); } } } function set$4(x, y, obj, img) { if (img.isRemote) { // Remote images cannot access imageData throw "Image is loaded remotely. Cannot set x,y."; } var c = p.color.toArray(obj); var offset = y * img.width * 4 + (x*4); var data = img.imageData.data; data[offset] = c[0]; data[offset+1] = c[1]; data[offset+2] = c[2]; data[offset+3] = c[3]; }
// Paints a pixel array into the canvas /** * Changes the color of any pixel or writes an image directly into the display window. The x and y parameters * specify the pixel to change and the color parameter specifies the color value. The color parameter is affected * by the current color mode (the default is RGB values from 0 to 255). When setting an image, the x and y * parameters define the coordinates for the upper-left corner of the image. * Setting the color of a single pixel with set(x, y) is easy, but not as fast as putting the data directly * into pixels[]. The equivalent statement to "set(x, y, #000000)" using pixels[] is "pixels[y*width+x] = #000000". * You must call loadPixels() to load the display window data into the pixels[] array before setting the values * and calling updatePixels() to update the window with any changes. This function ignores imageMode(). * * @param {int} x x-coordinate of the pixel * @param {int} y y-coordinate of the pixel * @param {Color} obj any value of the color datatype * @param {PImage} img any valid variable of type PImage * * @see get * @see pixels[] * @see imageMode */ p.set = function(x, y, obj, img) { var color, oldFill; if (arguments.length === 3) { // called p.set(), was it with a color or a img ? if (typeof obj === "number") { set$3(x, y, obj); } else if (obj instanceof PImage || obj.__isPImage) { p.image(obj, x, y); } } else if (arguments.length === 4) { // PImage.set(x,y,c) was called, set coordinate x,y color to c of img set$4(x, y, obj, img); } }; p.imageData = {};
// handle the sketch code for pixels[] // parser code converts pixels[] to getPixels() or setPixels(), // .length becomes getLength() /** * Array containing the values for all the pixels in the display window. These values are of the color datatype. * This array is the size of the display window. For example, if the image is 100x100 pixels, there will be 10000 * values and if the window is 200x300 pixels, there will be 60000 values. The index value defines the position * of a value within the array. For example, the statment color b = pixels[230] will set the variable b to be * equal to the value at that location in the array. * Before accessing this array, the data must loaded with the loadPixels() function. After the array data has * been modified, the updatePixels() function must be run to update the changes. * * @param {int} index must not exceed the size of the array * * @see loadPixels * @see updatePixels * @see get * @see set * @see PImage */ p.pixels = { getLength: function() { return p.imageData.data.length ? p.imageData.data.length/4 : 0; }, getPixel: function(i) { var offset = i*4, data = p.imageData.data; return (data[offset+3] << 24) & 0xff000000 | (data[offset+0] << 16) & 0x00ff0000 | (data[offset+1] << 8) & 0x0000ff00 | data[offset+2] & 0x000000ff; }, setPixel: function(i,c) { var offset = i*4, data = p.imageData.data; data[offset+0] = (c & 0x00ff0000) >>> 16; // RED_MASK data[offset+1] = (c & 0x0000ff00) >>> 8; // GREEN_MASK data[offset+2] = (c & 0x000000ff); // BLUE_MASK data[offset+3] = (c & 0xff000000) >>> 24; // ALPHA_MASK }, toArray: function() { var arr = [], length = p.imageData.width * p.imageData.height, data = p.imageData.data; for (var i = 0, offset = 0; i < length; i++, offset += 4) { arr.push((data[offset+3] << 24) & 0xff000000 | (data[offset+0] << 16) & 0x00ff0000 | (data[offset+1] << 8) & 0x0000ff00 | data[offset+2] & 0x000000ff); } return arr; }, set: function(arr) { for (var i = 0, aL = arr.length; i < aL; i++) { this.setPixel(i, arr[i]); } } };
// Gets a 1-Dimensional pixel array from Canvas /** * Loads the pixel data for the display window into the pixels[] array. This function must always be called * before reading from or writing to pixels[]. * Certain renderers may or may not seem to require loadPixels() or updatePixels(). However, the rule is that * any time you want to manipulate the pixels[] array, you must first call loadPixels(), and after changes * have been made, call updatePixels(). Even if the renderer may not seem to use this function in the current * Processing release, this will always be subject to change. * * @see pixels[] * @see updatePixels */ p.loadPixels = function() { p.imageData = drawing.$ensureContext().getImageData(0, 0, p.width, p.height); };
// Draws a 1-Dimensional pixel array to Canvas /** * Updates the display window with the data in the pixels[] array. Use in conjunction with loadPixels(). If * you're only reading pixels from the array, there's no need to call updatePixels() unless there are changes. * Certain renderers may or may not seem to require loadPixels() or updatePixels(). However, the rule is that * any time you want to manipulate the pixels[] array, you must first call loadPixels(), and after changes * have been made, call updatePixels(). Even if the renderer may not seem to use this function in the current * Processing release, this will always be subject to change. * Currently, none of the renderers use the additional parameters to updatePixels(), however this may be * implemented in the future. * * @see loadPixels * @see pixels[] */ p.updatePixels = function() { if (p.imageData) { drawing.$ensureContext().putImageData(p.imageData, 0, 0); } };
/** * Set various hints and hacks for the renderer. This is used to handle obscure rendering features that cannot be * implemented in a consistent manner across renderers. Many options will often graduate to standard features * instead of hints over time. * hint(ENABLE_OPENGL_4X_SMOOTH) - Enable 4x anti-aliasing for OpenGL. This can help force anti-aliasing if * it has not been enabled by the user. On some graphics cards, this can also be set by the graphics driver's * control panel, however not all cards make this available. This hint must be called immediately after the * size() command because it resets the renderer, obliterating any settings and anything drawn (and like size(), * re-running the code that came before it again). * hint(DISABLE_OPENGL_2X_SMOOTH) - In Processing 1.0, Processing always enables 2x smoothing when the OpenGL * renderer is used. This hint disables the default 2x smoothing and returns the smoothing behavior found in * earlier releases, where smooth() and noSmooth() could be used to enable and disable smoothing, though the * quality was inferior. * hint(ENABLE_NATIVE_FONTS) - Use the native version fonts when they are installed, rather than the bitmapped * version from a .vlw file. This is useful with the JAVA2D renderer setting, as it will improve font rendering * speed. This is not enabled by default, because it can be misleading while testing because the type will look * great on your machine (because you have the font installed) but lousy on others' machines if the identical * font is unavailable. This option can only be set per-sketch, and must be called before any use of textFont(). * hint(DISABLE_DEPTH_TEST) - Disable the zbuffer, allowing you to draw on top of everything at will. When depth * testing is disabled, items will be drawn to the screen sequentially, like a painting. This hint is most often * used to draw in 3D, then draw in 2D on top of it (for instance, to draw GUI controls in 2D on top of a 3D * interface). Starting in release 0149, this will also clear the depth buffer. Restore the default with * hint(ENABLE_DEPTH_TEST), but note that with the depth buffer cleared, any 3D drawing that happens later in * draw() will ignore existing shapes on the screen. * hint(ENABLE_DEPTH_SORT) - Enable primitive z-sorting of triangles and lines in P3D and OPENGL. This can slow * performance considerably, and the algorithm is not yet perfect. Restore the default with hint(DISABLE_DEPTH_SORT). * hint(DISABLE_OPENGL_ERROR_REPORT) - Speeds up the OPENGL renderer setting by not checking for errors while * running. Undo with hint(ENABLE_OPENGL_ERROR_REPORT). * As of release 0149, unhint() has been removed in favor of adding additional ENABLE/DISABLE constants to reset * the default behavior. This prevents the double negatives, and also reinforces which hints can be enabled or disabled. * * @param {MODE} item constant: name of the hint to be enabled or disabled * * @see PGraphics * @see createGraphics * @see size */ p.hint = function(which) { var curContext = drawing.$ensureContext(); if (which === PConstants.DISABLE_DEPTH_TEST) { curContext.disable(curContext.DEPTH_TEST); curContext.depthMask(false); curContext.clear(curContext.DEPTH_BUFFER_BIT); } else if (which === PConstants.ENABLE_DEPTH_TEST) { curContext.enable(curContext.DEPTH_TEST); curContext.depthMask(true); } else if (which === PConstants.ENABLE_OPENGL_2X_SMOOTH || which === PConstants.ENABLE_OPENGL_4X_SMOOTH){ renderSmooth = true; } else if (which === PConstants.DISABLE_OPENGL_2X_SMOOTH){ renderSmooth = false; } };
/** * The background() function sets the color used for the background of the Processing window. * The default background is light gray. In the draw() function, the background color is used to clear the display window at the beginning of each frame. * An image can also be used as the background for a sketch, however its width and height must be the same size as the sketch window. * To resize an image 'b' to the size of the sketch window, use b.resize(width, height). * Images used as background will ignore the current tint() setting. * For the main drawing surface, the alpha value will be ignored. However, * alpha can be used on PGraphics objects from createGraphics(). This is * the only way to set all the pixels partially transparent, for instance. * If the 'gray' parameter is passed in the function sets the background to a grayscale value, based on the * current colorMode.*
* Note that background() should be called before any transformations occur,
* because some implementations may require the current transformation matrix
* to be identity before drawing.
*
* @param {int|float} gray specifies a value between white and black
* @param {int|float} value1 red or hue value (depending on the current color mode)
* @param {int|float} value2 green or saturation value (depending on the current color mode)
* @param {int|float} value3 blue or brightness value (depending on the current color mode)
* @param {int|float} alpha opacity of the background
* @param {Color} color any value of the color datatype
* @param {int} hex color value in hexadecimal notation (i.e. #FFCC00 or 0xFFFFCC00)
* @param {PImage} image an instance of a PImage to use as a background
*
* @see #stroke()
* @see #fill()
* @see #tint()
* @see #colorMode()
*/
var backgroundHelper = function(arg1, arg2, arg3, arg4) {
var obj;
if (arg1 instanceof PImage || arg1.__isPImage) {
obj = arg1;
if (!obj.loaded) {
throw "Error using image in background(): PImage not loaded.";
}
if(obj.width !== p.width || obj.height !== p.height){
throw "Background image must be the same dimensions as the canvas.";
}
} else {
obj = p.color(arg1, arg2, arg3, arg4);
}
backgroundObj = obj;
};
Drawing2D.prototype.background = function(arg1, arg2, arg3, arg4) {
if (arg1 !== undef) {
backgroundHelper(arg1, arg2, arg3, arg4);
}
if (backgroundObj instanceof PImage || backgroundObj.__isPImage) {
saveContext();
curContext.setTransform(1, 0, 0, 1, 0, 0);
p.image(backgroundObj, 0, 0);
restoreContext();
} else {
saveContext();
curContext.setTransform(1, 0, 0, 1, 0, 0);
// If the background is transparent
if (p.alpha(backgroundObj) !== colorModeA) {
curContext.clearRect(0,0, p.width, p.height);
}
curContext.fillStyle = p.color.toString(backgroundObj);
curContext.fillRect(0, 0, p.width, p.height);
isFillDirty = true;
restoreContext();
}
};
Drawing3D.prototype.background = function(arg1, arg2, arg3, arg4) {
if (arguments.length > 0) {
backgroundHelper(arg1, arg2, arg3, arg4);
}
var c = p.color.toGLArray(backgroundObj);
curContext.clearColor(c[0], c[1], c[2], c[3]);
curContext.clear(curContext.COLOR_BUFFER_BIT | curContext.DEPTH_BUFFER_BIT);
// An image as a background in 3D is not implemented yet
};
// Draws an image to the Canvas
/**
* Displays images to the screen. The images must be in the sketch's "data" directory to load correctly. Select "Add
* file..." from the "Sketch" menu to add the image. Processing currently works with GIF, JPEG, and Targa images. The
* color of an image may be modified with the tint() function and if a GIF has transparency, it will maintain its
* transparency. The img parameter specifies the image to display and the x and y parameters define the location of
* the image from its upper-left corner. The image is displayed at its original size unless the width and height
* parameters specify a different size. The imageMode() function changes the way the parameters work. A call to
* imageMode(CORNERS) will change the width and height parameters to define the x and y values of the opposite
* corner of the image.
*
* @param {PImage} img the image to display
* @param {int|float} x x-coordinate of the image
* @param {int|float} y y-coordinate of the image
* @param {int|float} width width to display the image
* @param {int|float} height height to display the image
*
* @see loadImage
* @see PImage
* @see imageMode
* @see tint
* @see background
* @see alpha
*/
Drawing2D.prototype.image = function(img, x, y, w, h) {
// Fix fractional positions
x = Math.round(x);
y = Math.round(y);
if (img.width > 0) {
var wid = w || img.width;
var hgt = h || img.height;
var bounds = imageModeConvert(x || 0, y || 0, w || img.width, h || img.height, arguments.length < 4);
var fastImage = !!img.sourceImg && curTint === null;
if (fastImage) {
var htmlElement = img.sourceImg;
if (img.__isDirty) {
img.updatePixels();
}
// Using HTML element's width and height in case if the image was resized.
curContext.drawImage(htmlElement, 0, 0,
htmlElement.width, htmlElement.height, bounds.x, bounds.y, bounds.w, bounds.h);
} else {
var obj = img.toImageData();
// Tint the image
if (curTint !== null) {
curTint(obj);
img.__isDirty = true;
}
curContext.drawImage(getCanvasData(obj).canvas, 0, 0,
img.width, img.height, bounds.x, bounds.y, bounds.w, bounds.h);
}
}
};
Drawing3D.prototype.image = function(img, x, y, w, h) {
if (img.width > 0) {
// Fix fractional positions
x = Math.round(x);
y = Math.round(y);
w = w || img.width;
h = h || img.height;
p.beginShape(p.QUADS);
p.texture(img);
p.vertex(x, y, 0, 0, 0);
p.vertex(x, y+h, 0, 0, h);
p.vertex(x+w, y+h, 0, w, h);
p.vertex(x+w, y, 0, w, 0);
p.endShape();
}
};
/**
* The tint() function sets the fill value for displaying images. Images can be tinted to
* specified colors or made transparent by setting the alpha.
*
To make an image transparent, but not change it's color,
* use white as the tint color and specify an alpha value. For instance,
* tint(255, 128) will make an image 50% transparent (unless
* colorMode() has been used).
*
*
When using hexadecimal notation to specify a color, use "#" or
* "0x" before the values (e.g. #CCFFAA, 0xFFCCFFAA). The # syntax uses six
* digits to specify a color (the way colors are specified in HTML and CSS).
* When using the hexadecimal notation starting with "0x", the hexadecimal
* value must be specified with eight characters; the first two characters
* define the alpha component and the remainder the red, green, and blue
* components.
*
The value for the parameter "gray" must be less than or equal
* to the current maximum value as specified by colorMode().
* The default maximum value is 255.
*
The tint() method is also used to control the coloring of
* textures in 3D.
*
* @param {int|float} gray any valid number
* @param {int|float} alpha opacity of the image
* @param {int|float} value1 red or hue value
* @param {int|float} value2 green or saturation value
* @param {int|float} value3 blue or brightness value
* @param {int|float} color any value of the color datatype
* @param {int} hex color value in hexadecimal notation (i.e. #FFCC00 or 0xFFFFCC00)
*
* @see #noTint()
* @see #image()
*/
p.tint = function(a1, a2, a3, a4) {
var tintColor = p.color(a1, a2, a3, a4);
var r = p.red(tintColor) / colorModeX;
var g = p.green(tintColor) / colorModeY;
var b = p.blue(tintColor) / colorModeZ;
var a = p.alpha(tintColor) / colorModeA;
curTint = function(obj) {
var data = obj.data,
length = 4 * obj.width * obj.height;
for (var i = 0; i < length;) {
data[i++] *= r;
data[i++] *= g;
data[i++] *= b;
data[i++] *= a;
}
};
// for overriding the color buffer when 3d rendering
curTint3d = function(data){
for (var i = 0; i < data.length;) {
data[i++] = r;
data[i++] = g;
data[i++] = b;
data[i++] = a;
}
};
};
/**
* The noTint() function removes the current fill value for displaying images and reverts to displaying images with their original hues.
*
* @see #tint()
* @see #image()
*/
p.noTint = function() {
curTint = null;
curTint3d = null;
};
/**
* Copies a region of pixels from the display window to another area of the display window and copies a region of pixels from an
* image used as the srcImg parameter into the display window. If the source and destination regions aren't the same size, it will
* automatically resize the source pixels to fit the specified target region. No alpha information is used in the process, however
* if the source image has an alpha channel set, it will be copied as well. This function ignores imageMode().
*
* @param {int} x X coordinate of the source's upper left corner
* @param {int} y Y coordinate of the source's upper left corner
* @param {int} width source image width
* @param {int} height source image height
* @param {int} dx X coordinate of the destination's upper left corner
* @param {int} dy Y coordinate of the destination's upper left corner
* @param {int} dwidth destination image width
* @param {int} dheight destination image height
* @param {PImage} srcImg image variable referring to the source image
*
* @see blend
* @see get
*/
p.copy = function(src, sx, sy, sw, sh, dx, dy, dw, dh) {
if (dh === undef) {
// shift everything, and introduce p
dh = dw;
dw = dy;
dy = dx;
dx = sh;
sh = sw;
sw = sy;
sy = sx;
sx = src;
src = p;
}
p.blend(src, sx, sy, sw, sh, dx, dy, dw, dh, PConstants.REPLACE);
};
/**
* Blends a region of pixels from one image into another (or in itself again) with full alpha channel support. There
* is a choice of the following modes to blend the source pixels (A) with the ones of pixels in the destination image (B):
* BLEND - linear interpolation of colours: C = A*factor + B
* ADD - additive blending with white clip: C = min(A*factor + B, 255)
* SUBTRACT - subtractive blending with black clip: C = max(B - A*factor, 0)
* DARKEST - only the darkest colour succeeds: C = min(A*factor, B)
* LIGHTEST - only the lightest colour succeeds: C = max(A*factor, B)
* DIFFERENCE - subtract colors from underlying image.
* EXCLUSION - similar to DIFFERENCE, but less extreme.
* MULTIPLY - Multiply the colors, result will always be darker.
* SCREEN - Opposite multiply, uses inverse values of the colors.
* OVERLAY - A mix of MULTIPLY and SCREEN. Multiplies dark values, and screens light values.
* HARD_LIGHT - SCREEN when greater than 50% gray, MULTIPLY when lower.
* SOFT_LIGHT - Mix of DARKEST and LIGHTEST. Works like OVERLAY, but not as harsh.
* DODGE - Lightens light tones and increases contrast, ignores darks. Called "Color Dodge" in Illustrator and Photoshop.
* BURN - Darker areas are applied, increasing contrast, ignores lights. Called "Color Burn" in Illustrator and Photoshop.
* All modes use the alpha information (highest byte) of source image pixels as the blending factor. If the source and
* destination regions are different sizes, the image will be automatically resized to match the destination size. If the
* srcImg parameter is not used, the display window is used as the source image. This function ignores imageMode().
*
* @param {int} x X coordinate of the source's upper left corner
* @param {int} y Y coordinate of the source's upper left corner
* @param {int} width source image width
* @param {int} height source image height
* @param {int} dx X coordinate of the destination's upper left corner
* @param {int} dy Y coordinate of the destination's upper left corner
* @param {int} dwidth destination image width
* @param {int} dheight destination image height
* @param {PImage} srcImg image variable referring to the source image
* @param {PImage} MODE Either BLEND, ADD, SUBTRACT, LIGHTEST, DARKEST, DIFFERENCE, EXCLUSION, MULTIPLY, SCREEN,
* OVERLAY, HARD_LIGHT, SOFT_LIGHT, DODGE, BURN
* @see filter
*/
p.blend = function(src, sx, sy, sw, sh, dx, dy, dw, dh, mode, pimgdest) {
if (src.isRemote) {
throw "Image is loaded remotely. Cannot blend image.";
}
if (mode === undef) {
// shift everything, and introduce p
mode = dh;
dh = dw;
dw = dy;
dy = dx;
dx = sh;
sh = sw;
sw = sy;
sy = sx;
sx = src;
src = p;
}
var sx2 = sx + sw,
sy2 = sy + sh,
dx2 = dx + dw,
dy2 = dy + dh,
dest = pimgdest || p;
// check if pimgdest is there and pixels, if so this was a call from pimg.blend
if (pimgdest === undef || mode === undef) {
p.loadPixels();
}
src.loadPixels();
if (src === p && p.intersect(sx, sy, sx2, sy2, dx, dy, dx2, dy2)) {
p.blit_resize(p.get(sx, sy, sx2 - sx, sy2 - sy), 0, 0, sx2 - sx - 1, sy2 - sy - 1,
dest.imageData.data, dest.width, dest.height, dx, dy, dx2, dy2, mode);
} else {
p.blit_resize(src, sx, sy, sx2, sy2, dest.imageData.data, dest.width, dest.height, dx, dy, dx2, dy2, mode);
}
if (pimgdest === undef) {
p.updatePixels();
}
};
// helper function for filter()
var buildBlurKernel = function(r) {
var radius = p.floor(r * 3.5), i, radiusi;
radius = (radius < 1) ? 1 : ((radius < 248) ? radius : 248);
if (p.shared.blurRadius !== radius) {
p.shared.blurRadius = radius;
p.shared.blurKernelSize = 1 + (p.shared.blurRadius<<1);
p.shared.blurKernel = new Float32Array(p.shared.blurKernelSize);
var sharedBlurKernal = p.shared.blurKernel;
var sharedBlurKernelSize = p.shared.blurKernelSize;
var sharedBlurRadius = p.shared.blurRadius;
// init blurKernel
for (i = 0; i < sharedBlurKernelSize; i++) {
sharedBlurKernal[i] = 0;
}
var radiusiSquared = (radius - 1) * (radius - 1);
for (i = 1; i < radius; i++) {
sharedBlurKernal[radius + i] = sharedBlurKernal[radiusi] = radiusiSquared;
}
sharedBlurKernal[radius] = radius * radius;
}
};
var blurARGB = function(r, aImg) {
var sum, cr, cg, cb, ca, c, m;
var read, ri, ym, ymi, bk0;
var wh = aImg.pixels.getLength();
var r2 = new Float32Array(wh);
var g2 = new Float32Array(wh);
var b2 = new Float32Array(wh);
var a2 = new Float32Array(wh);
var yi = 0;
var x, y, i, offset;
buildBlurKernel(r);
var aImgHeight = aImg.height;
var aImgWidth = aImg.width;
var sharedBlurKernelSize = p.shared.blurKernelSize;
var sharedBlurRadius = p.shared.blurRadius;
var sharedBlurKernal = p.shared.blurKernel;
var pix = aImg.imageData.data;
for (y = 0; y < aImgHeight; y++) {
for (x = 0; x < aImgWidth; x++) {
cb = cg = cr = ca = sum = 0;
read = x - sharedBlurRadius;
if (read<0) {
bk0 = -read;
read = 0;
} else {
if (read >= aImgWidth) {
break;
}
bk0=0;
}
for (i = bk0; i < sharedBlurKernelSize; i++) {
if (read >= aImgWidth) {
break;
}
offset = (read + yi) *4;
m = sharedBlurKernal[i];
ca += m * pix[offset + 3];
cr += m * pix[offset];
cg += m * pix[offset + 1];
cb += m * pix[offset + 2];
sum += m;
read++;
}
ri = yi + x;
a2[ri] = ca / sum;
r2[ri] = cr / sum;
g2[ri] = cg / sum;
b2[ri] = cb / sum;
}
yi += aImgWidth;
}
yi = 0;
ym = -sharedBlurRadius;
ymi = ym*aImgWidth;
for (y = 0; y < aImgHeight; y++) {
for (x = 0; x < aImgWidth; x++) {
cb = cg = cr = ca = sum = 0;
if (ym<0) {
bk0 = ri = -ym;
read = x;
} else {
if (ym >= aImgHeight) {
break;
}
bk0 = 0;
ri = ym;
read = x + ymi;
}
for (i = bk0; i < sharedBlurKernelSize; i++) {
if (ri >= aImgHeight) {
break;
}
m = sharedBlurKernal[i];
ca += m * a2[read];
cr += m * r2[read];
cg += m * g2[read];
cb += m * b2[read];
sum += m;
ri++;
read += aImgWidth;
}
offset = (x + yi) *4;
pix[offset] = cr / sum;
pix[offset + 1] = cg / sum;
pix[offset + 2] = cb / sum;
pix[offset + 3] = ca / sum;
}
yi += aImgWidth;
ymi += aImgWidth;
ym++;
}
};
// helper funtion for ERODE and DILATE modes of filter()
var dilate = function(isInverted, aImg) {
var currIdx = 0;
var maxIdx = aImg.pixels.getLength();
var out = new Int32Array(maxIdx);
var currRowIdx, maxRowIdx, colOrig, colOut, currLum;
var idxRight, idxLeft, idxUp, idxDown,
colRight, colLeft, colUp, colDown,
lumRight, lumLeft, lumUp, lumDown;
if (!isInverted) {
// erosion (grow light areas)
while (currIdx<maxIdx) {
currRowIdx = currIdx;
maxRowIdx = currIdx + aImg.width;
while (currIdx < maxRowIdx) {
colOrig = colOut = aImg.pixels.getPixel(currIdx);
idxLeft = currIdx - 1;
idxRight = currIdx + 1;
idxUp = currIdx - aImg.width;
idxDown = currIdx + aImg.width;
if (idxLeft < currRowIdx) {
idxLeft = currIdx;
}
if (idxRight >= maxRowIdx) {
idxRight = currIdx;
}
if (idxUp < 0) {
idxUp = 0;
}
if (idxDown >= maxIdx) {
idxDown = currIdx;
}
colUp = aImg.pixels.getPixel(idxUp);
colLeft = aImg.pixels.getPixel(idxLeft);
colDown = aImg.pixels.getPixel(idxDown);
colRight = aImg.pixels.getPixel(idxRight);
// compute luminance
currLum = 77*(colOrig>>16&0xff) + 151*(colOrig>>8&0xff) + 28*(colOrig&0xff);
lumLeft = 77*(colLeft>>16&0xff) + 151*(colLeft>>8&0xff) + 28*(colLeft&0xff);
lumRight = 77*(colRight>>16&0xff) + 151*(colRight>>8&0xff) + 28*(colRight&0xff);
lumUp = 77*(colUp>>16&0xff) + 151*(colUp>>8&0xff) + 28*(colUp&0xff);
lumDown = 77*(colDown>>16&0xff) + 151*(colDown>>8&0xff) + 28*(colDown&0xff);
if (lumLeft > currLum) {
colOut = colLeft;
currLum = lumLeft;
}
if (lumRight > currLum) {
colOut = colRight;
currLum = lumRight;
}
if (lumUp > currLum) {
colOut = colUp;
currLum = lumUp;
}
if (lumDown > currLum) {
colOut = colDown;
currLum = lumDown;
}
out[currIdx++] = colOut;
}
}
} else {
// dilate (grow dark areas)
while (currIdx < maxIdx) {
currRowIdx = currIdx;
maxRowIdx = currIdx + aImg.width;
while (currIdx < maxRowIdx) {
colOrig = colOut = aImg.pixels.getPixel(currIdx);
idxLeft = currIdx - 1;
idxRight = currIdx + 1;
idxUp = currIdx - aImg.width;
idxDown = currIdx + aImg.width;
if (idxLeft < currRowIdx) {
idxLeft = currIdx;
}
if (idxRight >= maxRowIdx) {
idxRight = currIdx;
}
if (idxUp < 0) {
idxUp = 0;
}
if (idxDown >= maxIdx) {
idxDown = currIdx;
}
colUp = aImg.pixels.getPixel(idxUp);
colLeft = aImg.pixels.getPixel(idxLeft);
colDown = aImg.pixels.getPixel(idxDown);
colRight = aImg.pixels.getPixel(idxRight);
// compute luminance
currLum = 77*(colOrig>>16&0xff) + 151*(colOrig>>8&0xff) + 28*(colOrig&0xff);
lumLeft = 77*(colLeft>>16&0xff) + 151*(colLeft>>8&0xff) + 28*(colLeft&0xff);
lumRight = 77*(colRight>>16&0xff) + 151*(colRight>>8&0xff) + 28*(colRight&0xff);
lumUp = 77*(colUp>>16&0xff) + 151*(colUp>>8&0xff) + 28*(colUp&0xff);
lumDown = 77*(colDown>>16&0xff) + 151*(colDown>>8&0xff) + 28*(colDown&0xff);
if (lumLeft < currLum) {
colOut = colLeft;
currLum = lumLeft;
}
if (lumRight < currLum) {
colOut = colRight;
currLum = lumRight;
}
if (lumUp < currLum) {
colOut = colUp;
currLum = lumUp;
}
if (lumDown < currLum) {
colOut = colDown;
currLum = lumDown;
}
out[currIdx++]=colOut;
}
}
}
aImg.pixels.set(out);
//p.arraycopy(out,0,pixels,0,maxIdx);
};
/**
* Filters the display window as defined by one of the following modes:
* THRESHOLD - converts the image to black and white pixels depending if they are above or below the threshold
* defined by the level parameter. The level must be between 0.0 (black) and 1.0(white). If no level is specified, 0.5 is used.
* GRAY - converts any colors in the image to grayscale equivalents
* INVERT - sets each pixel to its inverse value
* POSTERIZE - limits each channel of the image to the number of colors specified as the level parameter
* BLUR - executes a Guassian blur with the level parameter specifying the extent of the blurring. If no level parameter is
* used, the blur is equivalent to Guassian blur of radius 1.
* OPAQUE - sets the alpha channel to entirely opaque.
* ERODE - reduces the light areas with the amount defined by the level parameter.
* DILATE - increases the light areas with the amount defined by the level parameter.
*
* @param {MODE} MODE Either THRESHOLD, GRAY, INVERT, POSTERIZE, BLUR, OPAQUE, ERODE, or DILATE
* @param {int|float} level defines the quality of the filter
*
* @see blend
*/
p.filter = function(kind, param, aImg){
var img, col, lum, i;
if (arguments.length === 3) {
aImg.loadPixels();
img = aImg;
} else {
p.loadPixels();
img = p;
}
if (param === undef) {
param = null;
}
if (img.isRemote) { // Remote images cannot access imageData
throw "Image is loaded remotely. Cannot filter image.";
}
// begin filter process
var imglen = img.pixels.getLength();
switch (kind) {
case PConstants.BLUR:
var radius = param || 1; // if no param specified, use 1 (default for p5)
blurARGB(radius, img);
break;
case PConstants.GRAY:
if (img.format === PConstants.ALPHA) { //trouble
// for an alpha image, convert it to an opaque grayscale
for (i = 0; i < imglen; i++) {
col = 255 - img.pixels.getPixel(i);
img.pixels.setPixel(i,(0xff000000 | (col << 16) | (col << 8) | col));
}
img.format = PConstants.RGB; //trouble
} else {
for (i = 0; i < imglen; i++) {
col = img.pixels.getPixel(i);
lum = (77*(col>>16&0xff) + 151*(col>>8&0xff) + 28*(col&0xff))>>8;
img.pixels.setPixel(i,((col & PConstants.ALPHA_MASK) | lum<<16 | lum<<8 | lum));
}
}
break;
case PConstants.INVERT:
for (i = 0; i < imglen; i++) {
img.pixels.setPixel(i, (img.pixels.getPixel(i) ^ 0xffffff));
}
break;
case PConstants.POSTERIZE:
if (param === null) {
throw "Use filter(POSTERIZE, int levels) instead of filter(POSTERIZE)";
}
var levels = p.floor(param);
if ((levels < 2) || (levels > 255)) {
throw "Levels must be between 2 and 255 for filter(POSTERIZE, levels)";
}
var levels1 = levels - 1;
for (i = 0; i < imglen; i++) {
var rlevel = (img.pixels.getPixel(i) >> 16) & 0xff;
var glevel = (img.pixels.getPixel(i) >> 8) & 0xff;
var blevel = img.pixels.getPixel(i) & 0xff;
rlevel = (((rlevel * levels) >> 8) * 255) / levels1;
glevel = (((glevel * levels) >> 8) * 255) / levels1;
blevel = (((blevel * levels) >> 8) * 255) / levels1;
img.pixels.setPixel(i, ((0xff000000 & img.pixels.getPixel(i)) | (rlevel << 16) | (glevel << 8) | blevel));
}
break;
case PConstants.OPAQUE:
for (i = 0; i < imglen; i++) {
img.pixels.setPixel(i, (img.pixels.getPixel(i) | 0xff000000));
}
img.format = PConstants.RGB; //trouble
break;
case PConstants.THRESHOLD:
if (param === null) {
param = 0.5;
}
if ((param < 0) || (param > 1)) {
throw "Level must be between 0 and 1 for filter(THRESHOLD, level)";
}
var thresh = p.floor(param * 255);
for (i = 0; i < imglen; i++) {
var max = p.max((img.pixels.getPixel(i) & PConstants.RED_MASK) >> 16, p.max((img.pixels.getPixel(i) & PConstants.GREEN_MASK) >> 8, (img.pixels.getPixel(i) & PConstants.BLUE_MASK)));
img.pixels.setPixel(i, ((img.pixels.getPixel(i) & PConstants.ALPHA_MASK) | ((max < thresh) ? 0x000000 : 0xffffff)));
}
break;
case PConstants.ERODE:
dilate(true, img);
break;
case PConstants.DILATE:
dilate(false, img);
break;
}
img.updatePixels();
};
// shared variables for blit_resize(), filter_new_scanline(), filter_bilinear(), filter()
// change this in the future to not be exposed to p
p.shared = {
fracU: 0,
ifU: 0,
fracV: 0,
ifV: 0,
u1: 0,
u2: 0,
v1: 0,
v2: 0,
sX: 0,
sY: 0,
iw: 0,
iw1: 0,
ih1: 0,
ul: 0,
ll: 0,
ur: 0,
lr: 0,
cUL: 0,
cLL: 0,
cUR: 0,
cLR: 0,
srcXOffset: 0,
srcYOffset: 0,
r: 0,
g: 0,
b: 0,
a: 0,
srcBuffer: null,
blurRadius: 0,
blurKernelSize: 0,
blurKernel: null
};
p.intersect = function(sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2) {
var sw = sx2 - sx1 + 1;
var sh = sy2 - sy1 + 1;
var dw = dx2 - dx1 + 1;
var dh = dy2 - dy1 + 1;
if (dx1 < sx1) {
dw += dx1 - sx1;
if (dw > sw) {
dw = sw;
}
} else {
var w = sw + sx1 - dx1;
if (dw > w) {
dw = w;
}
}
if (dy1 < sy1) {
dh += dy1 - sy1;
if (dh > sh) {
dh = sh;
}
} else {
var h = sh + sy1 - dy1;
if (dh > h) {
dh = h;
}
}
return ! (dw <= 0 || dh <= 0);
};
var blendFuncs = {};
blendFuncs[PConstants.BLEND] = p.modes.blend;
blendFuncs[PConstants.ADD] = p.modes.add;
blendFuncs[PConstants.SUBTRACT] = p.modes.subtract;
blendFuncs[PConstants.LIGHTEST] = p.modes.lightest;
blendFuncs[PConstants.DARKEST] = p.modes.darkest;
blendFuncs[PConstants.REPLACE] = p.modes.replace;
blendFuncs[PConstants.DIFFERENCE] = p.modes.difference;
blendFuncs[PConstants.EXCLUSION] = p.modes.exclusion;
blendFuncs[PConstants.MULTIPLY] = p.modes.multiply;
blendFuncs[PConstants.SCREEN] = p.modes.screen;
blendFuncs[PConstants.OVERLAY] = p.modes.overlay;
blendFuncs[PConstants.HARD_LIGHT] = p.modes.hard_light;
blendFuncs[PConstants.SOFT_LIGHT] = p.modes.soft_light;
blendFuncs[PConstants.DODGE] = p.modes.dodge;
blendFuncs[PConstants.BURN] = p.modes.burn;
p.blit_resize = function(img, srcX1, srcY1, srcX2, srcY2, destPixels,
screenW, screenH, destX1, destY1, destX2, destY2, mode) {
var x, y;
if (srcX1 < 0) {
srcX1 = 0;
}
if (srcY1 < 0) {
srcY1 = 0;
}
if (srcX2 >= img.width) {
srcX2 = img.width - 1;
}
if (srcY2 >= img.height) {
srcY2 = img.height - 1;
}
var srcW = srcX2 - srcX1;
var srcH = srcY2 - srcY1;
var destW = destX2 - destX1;
var destH = destY2 - destY1;
if (destW <= 0 || destH <= 0 || srcW <= 0 || srcH <= 0 || destX1 >= screenW ||
destY1 >= screenH || srcX1 >= img.width || srcY1 >= img.height) {
return;
}
var dx = Math.floor(srcW / destW * PConstants.PRECISIONF);
var dy = Math.floor(srcH / destH * PConstants.PRECISIONF);
var pshared = p.shared;
pshared.srcXOffset = Math.floor(destX1 < 0 ? -destX1 * dx : srcX1 * PConstants.PRECISIONF);
pshared.srcYOffset = Math.floor(destY1 < 0 ? -destY1 * dy : srcY1 * PConstants.PRECISIONF);
if (destX1 < 0) {
destW += destX1;
destX1 = 0;
}
if (destY1 < 0) {
destH += destY1;
destY1 = 0;
}
destW = Math.min(destW, screenW - destX1);
destH = Math.min(destH, screenH - destY1);
var destOffset = destY1 * screenW + destX1;
var destColor;
pshared.srcBuffer = img.imageData.data;
pshared.iw = img.width;
pshared.iw1 = img.width - 1;
pshared.ih1 = img.height - 1;
// cache for speed
var filterBilinear = p.filter_bilinear,
filterNewScanline = p.filter_new_scanline,
blendFunc = blendFuncs[mode],
blendedColor,
idx,
cULoffset,
cURoffset,
cLLoffset,
cLRoffset,
ALPHA_MASK = PConstants.ALPHA_MASK,
RED_MASK = PConstants.RED_MASK,
GREEN_MASK = PConstants.GREEN_MASK,
BLUE_MASK = PConstants.BLUE_MASK,
PREC_MAXVAL = PConstants.PREC_MAXVAL,
PRECISIONB = PConstants.PRECISIONB,
PREC_RED_SHIFT = PConstants.PREC_RED_SHIFT,
PREC_ALPHA_SHIFT = PConstants.PREC_ALPHA_SHIFT,
srcBuffer = pshared.srcBuffer,
min = Math.min;
for (y = 0; y < destH; y++) {
pshared.sX = pshared.srcXOffset;
pshared.fracV = pshared.srcYOffset & PREC_MAXVAL;
pshared.ifV = PREC_MAXVAL - pshared.fracV;
pshared.v1 = (pshared.srcYOffset >> PRECISIONB) * pshared.iw;
pshared.v2 = min((pshared.srcYOffset >> PRECISIONB) + 1, pshared.ih1) * pshared.iw;
for (x = 0; x < destW; x++) {
idx = (destOffset + x) * 4;
destColor = (destPixels[idx + 3] << 24) &
ALPHA_MASK | (destPixels[idx] << 16) &
RED_MASK | (destPixels[idx + 1] << 8) &
GREEN_MASK | destPixels[idx + 2] & BLUE_MASK;
pshared.fracU = pshared.sX & PREC_MAXVAL;
pshared.ifU = PREC_MAXVAL - pshared.fracU;
pshared.ul = (pshared.ifU * pshared.ifV) >> PRECISIONB;
pshared.ll = (pshared.ifU * pshared.fracV) >> PRECISIONB;
pshared.ur = (pshared.fracU * pshared.ifV) >> PRECISIONB;
pshared.lr = (pshared.fracU * pshared.fracV) >> PRECISIONB;
pshared.u1 = (pshared.sX >> PRECISIONB);
pshared.u2 = min(pshared.u1 + 1, pshared.iw1);
cULoffset = (pshared.v1 + pshared.u1) * 4;
cURoffset = (pshared.v1 + pshared.u2) * 4;
cLLoffset = (pshared.v2 + pshared.u1) * 4;
cLRoffset = (pshared.v2 + pshared.u2) * 4;
pshared.cUL = (srcBuffer[cULoffset + 3] << 24) &
ALPHA_MASK | (srcBuffer[cULoffset] << 16) &
RED_MASK | (srcBuffer[cULoffset + 1] << 8) &
GREEN_MASK | srcBuffer[cULoffset + 2] & BLUE_MASK;
pshared.cUR = (srcBuffer[cURoffset + 3] << 24) &
ALPHA_MASK | (srcBuffer[cURoffset] << 16) &
RED_MASK | (srcBuffer[cURoffset + 1] << 8) &
GREEN_MASK | srcBuffer[cURoffset + 2] & BLUE_MASK;
pshared.cLL = (srcBuffer[cLLoffset + 3] << 24) &
ALPHA_MASK | (srcBuffer[cLLoffset] << 16) &
RED_MASK | (srcBuffer[cLLoffset + 1] << 8) &
GREEN_MASK | srcBuffer[cLLoffset + 2] & BLUE_MASK;
pshared.cLR = (srcBuffer[cLRoffset + 3] << 24) &
ALPHA_MASK | (srcBuffer[cLRoffset] << 16) &
RED_MASK | (srcBuffer[cLRoffset + 1] << 8) &
GREEN_MASK | srcBuffer[cLRoffset + 2] & BLUE_MASK;
pshared.r = ((pshared.ul * ((pshared.cUL & RED_MASK) >> 16) +
pshared.ll * ((pshared.cLL & RED_MASK) >> 16) +
pshared.ur * ((pshared.cUR & RED_MASK) >> 16) +
pshared.lr * ((pshared.cLR & RED_MASK) >> 16)) << PREC_RED_SHIFT) & RED_MASK;
pshared.g = ((pshared.ul * (pshared.cUL & GREEN_MASK) +
pshared.ll * (pshared.cLL & GREEN_MASK) +
pshared.ur * (pshared.cUR & GREEN_MASK) +
pshared.lr * (pshared.cLR & GREEN_MASK)) >>> PRECISIONB) & GREEN_MASK;
pshared.b = (pshared.ul * (pshared.cUL & BLUE_MASK) +
pshared.ll * (pshared.cLL & BLUE_MASK) +
pshared.ur * (pshared.cUR & BLUE_MASK) +
pshared.lr * (pshared.cLR & BLUE_MASK)) >>> PRECISIONB;
pshared.a = ((pshared.ul * ((pshared.cUL & ALPHA_MASK) >>> 24) +
pshared.ll * ((pshared.cLL & ALPHA_MASK) >>> 24) +
pshared.ur * ((pshared.cUR & ALPHA_MASK) >>> 24) +
pshared.lr * ((pshared.cLR & ALPHA_MASK) >>> 24)) << PREC_ALPHA_SHIFT) & ALPHA_MASK;
blendedColor = blendFunc(destColor, (pshared.a | pshared.r | pshared.g | pshared.b));
destPixels[idx] = (blendedColor & RED_MASK) >>> 16;
destPixels[idx + 1] = (blendedColor & GREEN_MASK) >>> 8;
destPixels[idx + 2] = (blendedColor & BLUE_MASK);
destPixels[idx + 3] = (blendedColor & ALPHA_MASK) >>> 24;
pshared.sX += dx;
}
destOffset += screenW;
pshared.srcYOffset += dy;
}
};
////////////////////////////////////////////////////////////////////////////
// Font handling
////////////////////////////////////////////////////////////////////////////
/**
* loadFont() Loads a font into a variable of type PFont.
*
* @param {String} name filename of the font to load
* @param {int|float} size option font size (used internally)
*
* @returns {PFont} new PFont object
*
* @see #PFont
* @see #textFont
* @see #text
* @see #createFont
*/
p.loadFont = function(name, size) {
if (name === undef) {
throw("font name required in loadFont.");
}
if (name.indexOf(".svg") === -1) {
if (size === undef) {
size = curTextFont.size;
}
return PFont.get(name, size);
}
// If the font is a glyph, calculate by SVG table
var font = p.loadGlyphs(name);
return {
name: name,
css: '12px sans-serif',
glyph: true,
units_per_em: font.units_per_em,
horiz_adv_x: 1 / font.units_per_em * font.horiz_adv_x,
ascent: font.ascent,
descent: font.descent,
width: function(str) {
var width = 0;
var len = str.length;
for (var i = 0; i < len; i++) {
try {
width += parseFloat(p.glyphLook(p.glyphTable[name], str[i]).horiz_adv_x);
}
catch(e) {
Processing.debug(e);
}
}
return width / p.glyphTable[name].units_per_em;
}
};
};
/**
* createFont() Loads a font into a variable of type PFont.
* Smooth and charset are ignored in Processing.js.
*
* @param {String} name filename of the font to load
* @param {int|float} size font size in pixels
* @param {boolean} smooth not used in Processing.js
* @param {char[]} charset not used in Processing.js
*
* @returns {PFont} new PFont object
*
* @see #PFont
* @see #textFont
* @see #text
* @see #loadFont
*/
p.createFont = function(name, size) {
// because Processing.js only deals with real fonts,
// createFont is simply a wrapper for loadFont/2
return p.loadFont(name, size);
};
/**
* textFont() Sets the current font.
*
* @param {PFont} pfont the PFont to load as current text font
* @param {int|float} size optional font size in pixels
*
* @see #createFont
* @see #loadFont
* @see #PFont
* @see #text
*/
p.textFont = function(pfont, size) {
if (size !== undef) {
// If we're using an SVG glyph font, don't load from cache
if (!pfont.glyph) {
pfont = PFont.get(pfont.name, size);
}
curTextSize = size;
}
curTextFont = pfont;
curFontName = curTextFont.name;
curTextAscent = curTextFont.ascent;
curTextDescent = curTextFont.descent;
curTextLeading = curTextFont.leading;
var curContext = drawing.$ensureContext();
curContext.font = curTextFont.css;
};
/**
* textSize() Sets the current font size in pixels.
*
* @param {int|float} size font size in pixels
*
* @see #textFont
* @see #loadFont
* @see #PFont
* @see #text
*/
p.textSize = function(size) {
curTextFont = PFont.get(curFontName, size);
curTextSize = size;
// recache metrics
curTextAscent = curTextFont.ascent;
curTextDescent = curTextFont.descent;
curTextLeading = curTextFont.leading;
var curContext = drawing.$ensureContext();
curContext.font = curTextFont.css;
};
/**
* textAscent() returns the maximum height a character extends above the baseline of the
* current font at its current size, in pixels.
*
* @returns {float} height of the current font above the baseline, at its current size, in pixels
*
* @see #textDescent
*/
p.textAscent = function() {
return curTextAscent;
};
/**
* textDescent() returns the maximum depth a character will protrude below the baseline of
* the current font at its current size, in pixels.
*
* @returns {float} depth of the current font below the baseline, at its current size, in pixels
*
* @see #textAscent
*/
p.textDescent = function() {
return curTextDescent;
};
/**
* textLeading() Sets the current font's leading, which is the distance
* from baseline to baseline over consecutive lines, with additional vertical
* spacing taking into account. Usually this value is 1.2 or 1.25 times the
* textsize, but this value can be changed to effect vertically compressed
* or stretched text.
*
* @param {int|float} the desired baseline-to-baseline size in pixels
*/
p.textLeading = function(leading) {
curTextLeading = leading;
};
/**
* textAlign() Sets the current alignment for drawing text.
*
* @param {int} ALIGN Horizontal alignment, either LEFT, CENTER, or RIGHT
* @param {int} YALIGN optional vertical alignment, either TOP, BOTTOM, CENTER, or BASELINE
*
* @see #loadFont
* @see #PFont
* @see #text
*/
p.textAlign = function(xalign, yalign) {
horizontalTextAlignment = xalign;
verticalTextAlignment = yalign || PConstants.BASELINE;
};
/**
* toP5String converts things with arbitrary data type into
* string values, for text rendering.
*
* @param {any} any object that can be converted into a string
*
* @return {String} the string representation of the input
*/
function toP5String(obj) {
if(obj instanceof String) {
return obj;
}
if(typeof obj === 'number') {
// check if an int
if(obj === (0 | obj)) {
return obj.toString();
}
return p.nf(obj, 0, 3);
}
if(obj === null || obj === undef) {
return "";
}
return obj.toString();
}
/**
* textWidth() Calculates and returns the width of any character or text string in pixels.
*
* @param {char|String} str char or String to be measured
*
* @return {float} width of char or String in pixels
*
* @see #loadFont
* @see #PFont
* @see #text
* @see #textFont
*/
Drawing2D.prototype.textWidth = function(str) {
var lines = toP5String(str).split(/\r?\n/g), width = 0;
var i, linesCount = lines.length;
curContext.font = curTextFont.css;
for (i = 0; i < linesCount; ++i) {
width = Math.max(width, curTextFont.measureTextWidth(lines[i]));
}
return width | 0;
};
Drawing3D.prototype.textWidth = function(str) {
var lines = toP5String(str).split(/\r?\n/g), width = 0;
var i, linesCount = lines.length;
if (textcanvas === undef) {
textcanvas = document.createElement("canvas");
}
var textContext = textcanvas.getContext("2d");
textContext.font = curTextFont.css;
for (i = 0; i < linesCount; ++i) {
width = Math.max(width, textContext.measureText(lines[i]).width);
}
return width | 0;
};
// A lookup table for characters that can not be referenced by Object
p.glyphLook = function(font, chr) {
try {
switch (chr) {
case "1":
return font.one;
case "2":
return font.two;
case "3":
return font.three;
case "4":
return font.four;
case "5":
return font.five;
case "6":
return font.six;
case "7":
return font.seven;
case "8":
return font.eight;
case "9":
return font.nine;
case "0":
return font.zero;
case " ":
return font.space;
case "$":
return font.dollar;
case "!":
return font.exclam;
case '"':
return font.quotedbl;
case "#":
return font.numbersign;
case "%":
return font.percent;
case "&":
return font.ampersand;
case "'":
return font.quotesingle;
case "(":
return font.parenleft;
case ")":
return font.parenright;
case "*":
return font.asterisk;
case "+":
return font.plus;
case ",":
return font.comma;
case "-":
return font.hyphen;
case ".":
return font.period;
case "/":
return font.slash;
case "_":
return font.underscore;
case ":":
return font.colon;
case ";":
return font.semicolon;
case "<":
return font.less;
case "=":
return font.equal;
case ">":
return font.greater;
case "?":
return font.question;
case "@":
return font.at;
case "[":
return font.bracketleft;
case "\\":
return font.backslash;
case "]":
return font.bracketright;
case "^":
return font.asciicircum;
case "`":
return font.grave;
case "{":
return font.braceleft;
case "|":
return font.bar;
case "}":
return font.braceright;
case "~":
return font.asciitilde;
// If the character is not 'special', access it by object reference
default:
return font[chr];
}
} catch(e) {
Processing.debug(e);
}
};
// Print some text to the Canvas
Drawing2D.prototype.text$line = function(str, x, y, z, align) {
var textWidth = 0, xOffset = 0;
// If the font is a standard Canvas font...
if (!curTextFont.glyph) {
if (str && ("fillText" in curContext)) {
if (isFillDirty) {
curContext.fillStyle = p.color.toString(currentFillColor);
isFillDirty = false;
}
// horizontal offset/alignment
if(align === PConstants.RIGHT || align === PConstants.CENTER) {
textWidth = curTextFont.measureTextWidth(str);
if(align === PConstants.RIGHT) {
xOffset = -textWidth;
} else { // if(align === PConstants.CENTER)
xOffset = -textWidth/2;
}
}
curContext.fillText(str, x+xOffset, y);
}
} else {
// If the font is a Batik SVG font...
var font = p.glyphTable[curFontName];
saveContext();
curContext.translate(x, y + curTextSize);
// horizontal offset/alignment
if(align === PConstants.RIGHT || align === PConstants.CENTER) {
textWidth = font.width(str);
if(align === PConstants.RIGHT) {
xOffset = -textWidth;
} else { // if(align === PConstants.CENTER)
xOffset = -textWidth/2;
}
}
var upem = font.units_per_em,
newScale = 1 / upem * curTextSize;
curContext.scale(newScale, newScale);
for (var i=0, len=str.length; i < len; i++) {
// Test character against glyph table
try {
p.glyphLook(font, str[i]).draw();
} catch(e) {
Processing.debug(e);
}
}
restoreContext();
}
};
Drawing3D.prototype.text$line = function(str, x, y, z, align) {
// handle case for 3d text
if (textcanvas === undef) {
textcanvas = document.createElement("canvas");
}
var oldContext = curContext;
curContext = textcanvas.getContext("2d");
curContext.font = curTextFont.css;
var textWidth = curTextFont.measureTextWidth(str);
textcanvas.width = textWidth;
textcanvas.height = curTextSize;
curContext = textcanvas.getContext("2d"); // refreshes curContext
curContext.font = curTextFont.css;
curContext.textBaseline="top";
// paint on 2D canvas
Drawing2D.prototype.text$line(str,0,0,0,PConstants.LEFT);
// use it as a texture
var aspect = textcanvas.width/textcanvas.height;
curContext = oldContext;
curContext.bindTexture(curContext.TEXTURE_2D, textTex);
curContext.texImage2D(curContext.TEXTURE_2D, 0, curContext.RGBA, curContext.RGBA, curContext.UNSIGNED_BYTE, textcanvas);
curContext.texParameteri(curContext.TEXTURE_2D, curContext.TEXTURE_MAG_FILTER, curContext.LINEAR);
curContext.texParameteri(curContext.TEXTURE_2D, curContext.TEXTURE_MIN_FILTER, curContext.LINEAR);
curContext.texParameteri(curContext.TEXTURE_2D, curContext.TEXTURE_WRAP_T, curContext.CLAMP_TO_EDGE);
curContext.texParameteri(curContext.TEXTURE_2D, curContext.TEXTURE_WRAP_S, curContext.CLAMP_TO_EDGE);
// If we don't have a power of two texture, we can't mipmap it.
// curContext.generateMipmap(curContext.TEXTURE_2D);
// horizontal offset/alignment
var xOffset = 0;
if (align === PConstants.RIGHT) {
xOffset = -textWidth;
} else if(align === PConstants.CENTER) {
xOffset = -textWidth/2;
}
var model = new PMatrix3D();
var scalefactor = curTextSize * 0.5;
model.translate(x+xOffset-scalefactor/2, y-scalefactor, z);
model.scale(-aspect*scalefactor, -scalefactor, scalefactor);
model.translate(-1, -1, -1);
model.transpose();
var view = new PMatrix3D();
view.scale(1, -1, 1);
view.apply(modelView.array());
view.transpose();
curContext.useProgram(programObject2D);
vertexAttribPointer("aVertex2d", programObject2D, "aVertex", 3, textBuffer);
vertexAttribPointer("aTextureCoord2d", programObject2D, "aTextureCoord", 2, textureBuffer);
uniformi("uSampler2d", programObject2D, "uSampler", [0]);
uniformi("uIsDrawingText2d", programObject2D, "uIsDrawingText", true);
uniformMatrix("uModel2d", programObject2D, "uModel", false, model.array());
uniformMatrix("uView2d", programObject2D, "uView", false, view.array());
uniformf("uColor2d", programObject2D, "uColor", fillStyle);
curContext.bindBuffer(curContext.ELEMENT_ARRAY_BUFFER, indexBuffer);
curContext.drawElements(curContext.TRIANGLES, 6, curContext.UNSIGNED_SHORT, 0);
};
/**
* unbounded text function (z is an optional argument)
*/
function text$4(str, x, y, z) {
var lines, linesCount;
if(str.indexOf('\n') < 0) {
lines = [str];
linesCount = 1;
} else {
lines = str.split(/\r?\n/g);
linesCount = lines.length;
}
// handle text line-by-line
var yOffset = 0;
if(verticalTextAlignment === PConstants.TOP) {
yOffset = curTextAscent + curTextDescent;
} else if(verticalTextAlignment === PConstants.CENTER) {
yOffset = curTextAscent/2 - (linesCount-1)*curTextLeading/2;
} else if(verticalTextAlignment === PConstants.BOTTOM) {
yOffset = -(curTextDescent + (linesCount-1)*curTextLeading);
}
for(var i=0;i<linesCount;++i) {
var line = lines[i];
drawing.text$line(line, x, y + yOffset, z, horizontalTextAlignment);
yOffset += curTextLeading;
}
}
/**
* box-bounded text function (z is an optional argument)
*/
function text$6(str, x, y, width, height, z) {
// 'fail' on 0-valued dimensions
if (str.length === 0 || width === 0 || height === 0) {
return;
}
// also 'fail' if the text height is larger than the bounding height
if(curTextSize > height) {
return;
}
var spaceMark = -1;
var start = 0;
var lineWidth = 0;
var drawCommands = [];
// run through text, character-by-character
for (var charPos=0, len=str.length; charPos < len; charPos++)
{
var currentChar = str[charPos];
var spaceChar = (currentChar === " ");
var letterWidth = curTextFont.measureTextWidth(currentChar);
// if we aren't looking at a newline, and the text still fits, keep processing
if (currentChar !== "\n" && (lineWidth + letterWidth <= width)) {
if (spaceChar) { spaceMark = charPos; }
lineWidth += letterWidth;
}
// if we're looking at a newline, or the text no longer fits, push the section that fit into the drawcommand list
else
{
if (spaceMark + 1 === start) {
if(charPos>0) {
// Whole line without spaces so far.
spaceMark = charPos;
} else {
// 'fail', because the line can't even fit the first character
return;
}
}
if (currentChar === "\n") {
drawCommands.push({text:str.substring(start, charPos), width: lineWidth});
start = charPos + 1;
} else {
// current is not a newline, which means the line doesn't fit in box. push text.
// In Processing 1.5.1, the space is also pushed, so we push up to spaceMark+1,
// rather than up to spaceMark, as was the case for Processing 1.5 and earlier.
drawCommands.push({text:str.substring(start, spaceMark+1), width: lineWidth});
start = spaceMark + 1;
}
// newline + return
lineWidth = 0;
charPos = start - 1;
}
}
// push the remaining text
if (start < len) {
drawCommands.push({text:str.substring(start), width: lineWidth});
}
// resolve horizontal alignment
var xOffset = 1,
yOffset = curTextAscent;
if (horizontalTextAlignment === PConstants.CENTER) {
xOffset = width/2;
} else if (horizontalTextAlignment === PConstants.RIGHT) {
xOffset = width;
}
// resolve vertical alignment
var linesCount = drawCommands.length,
visibleLines = Math.min(linesCount, Math.floor(height/curTextLeading));
if(verticalTextAlignment === PConstants.TOP) {
yOffset = curTextAscent + curTextDescent;
} else if(verticalTextAlignment === PConstants.CENTER) {
yOffset = (height/2) - curTextLeading * (visibleLines/2 - 1);
} else if(verticalTextAlignment === PConstants.BOTTOM) {
yOffset = curTextDescent + curTextLeading;
}
var command,
drawCommand,
leading;
for (command = 0; command < linesCount; command++) {
leading = command * curTextLeading;
// stop if not enough space for one more line draw
if (yOffset + leading > height - curTextDescent) {
break;
}
drawCommand = drawCommands[command];
drawing.text$line(drawCommand.text, x + xOffset, y + yOffset + leading, z, horizontalTextAlignment);
}
}
/**
* text() Draws text to the screen.
*
* @param {String|char|int|float} data the alphanumeric symbols to be displayed
* @param {int|float} x x-coordinate of text
* @param {int|float} y y-coordinate of text
* @param {int|float} z optional z-coordinate of text
* @param {String} stringdata optional letters to be displayed
* @param {int|float} width optional width of text box
* @param {int|float} height optional height of text box
*
* @see #textAlign
* @see #textMode
* @see #loadFont
* @see #PFont
* @see #textFont
*/
p.text = function() {
if (textMode === PConstants.SHAPE) {
// TODO: requires beginRaw function
return;
}
if (arguments.length === 3) { // for text( str, x, y)
text$4(toP5String(arguments[0]), arguments[1], arguments[2], 0);
} else if (arguments.length === 4) { // for text( str, x, y, z)
text$4(toP5String(arguments[0]), arguments[1], arguments[2], arguments[3]);
} else if (arguments.length === 5) { // for text( str, x, y , width, height)
text$6(toP5String(arguments[0]), arguments[1], arguments[2], arguments[3], arguments[4], 0);
} else if (arguments.length === 6) { // for text( stringdata, x, y , width, height, z)
text$6(toP5String(arguments[0]), arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]);
}
};
/**
* Sets the way text draws to the screen. In the default configuration (the MODEL mode), it's possible to rotate,
* scale, and place letters in two and three dimensional space.
Changing to SCREEN mode draws letters
* directly to the front of the window and greatly increases rendering quality and speed when used with the P2D and
* P3D renderers. textMode(SCREEN) with OPENGL and JAVA2D (the default) renderers will generally be slower, though
* pixel accurate with P2D and P3D. With textMode(SCREEN), the letters draw at the actual size of the font (in pixels)
* and therefore calls to textSize() will not affect the size of the letters. To create a font at the size you
* desire, use the "Create font..." option in the Tools menu, or use the createFont() function. When using textMode(SCREEN),
* any z-coordinate passed to a text() command will be ignored, because your computer screen is...flat!
*
* @param {int} MODE Either MODEL, SCREEN or SHAPE (not yet supported)
*
* @see loadFont
* @see PFont
* @see text
* @see textFont
* @see createFont
*/
p.textMode = function(mode){
textMode = mode;
};
// Load Batik SVG Fonts and parse to pre-def objects for quick rendering
p.loadGlyphs = function(url) {
var x, y, cx, cy, nx, ny, d, a, lastCom, lenC, horiz_adv_x, getXY = '[0-9\\-]+', path;
// Return arrays of SVG commands and coords
// get this to use p.matchAll() - will need to work around the lack of null return
var regex = function(needle, hay) {
var i = 0,
results = [],
latest, regexp = new RegExp(needle, "g");
latest = results[i] = regexp.exec(hay);
while (latest) {
i++;
latest = results[i] = regexp.exec(hay);
}
return results;
};
var buildPath = function(d) {
var c = regex("[A-Za-z][0-9\\- ]+|Z", d);
var beforePathDraw = function() {
saveContext();
return drawing.$ensureContext();
};
var afterPathDraw = function() {
executeContextFill();
executeContextStroke();
restoreContext();
};
// Begin storing path object
path = "return {draw:function(){var curContext=beforePathDraw();curContext.beginPath();";
x = 0;
y = 0;
cx = 0;
cy = 0;
nx = 0;
ny = 0;
d = 0;
a = 0;
lastCom = "";
lenC = c.length - 1;
// Loop through SVG commands translating to canvas eqivs functions in path object
for (var j = 0; j < lenC; j++) {
var com = c[j][0], xy = regex(getXY, com);
switch (com[0]) {
case "M":
//curContext.moveTo(x,-y);
x = parseFloat(xy[0][0]);
y = parseFloat(xy[1][0]);
path += "curContext.moveTo(" + x + "," + (-y) + ");";
break;
case "L":
//curContext.lineTo(x,-y);
x = parseFloat(xy[0][0]);
y = parseFloat(xy[1][0]);
path += "curContext.lineTo(" + x + "," + (-y) + ");";
break;
case "H":
//curContext.lineTo(x,-y)
x = parseFloat(xy[0][0]);
path += "curContext.lineTo(" + x + "," + (-y) + ");";
break;
case "V":
//curContext.lineTo(x,-y);
y = parseFloat(xy[0][0]);
path += "curContext.lineTo(" + x + "," + (-y) + ");";
break;
case "T":
//curContext.quadraticCurveTo(cx,-cy,nx,-ny);
nx = parseFloat(xy[0][0]);
ny = parseFloat(xy[1][0]);
if (lastCom === "Q" || lastCom === "T") {
d = Math.sqrt(Math.pow(x - cx, 2) + Math.pow(cy - y, 2));
a = Math.PI + Math.atan2(cx - x, cy - y);
cx = x + (Math.sin(a) * (d));
cy = y + (Math.cos(a) * (d));
} else {
cx = x;
cy = y;
}
path += "curContext.quadraticCurveTo(" + cx + "," + (-cy) + "," + nx + "," + (-ny) + ");";
x = nx;
y = ny;
break;
case "Q":
//curContext.quadraticCurveTo(cx,-cy,nx,-ny);
cx = parseFloat(xy[0][0]);
cy = parseFloat(xy[1][0]);
nx = parseFloat(xy[2][0]);
ny = parseFloat(xy[3][0]);
path += "curContext.quadraticCurveTo(" + cx + "," + (-cy) + "," + nx + "," + (-ny) + ");";
x = nx;
y = ny;
break;
case "Z":
//curContext.closePath();
path += "curContext.closePath();";
break;
}
lastCom = com[0];
}
path += "afterPathDraw();";
path += "curContext.translate(" + horiz_adv_x + ",0);";
path += "}}";
return ((new Function("beforePathDraw", "afterPathDraw", path))(beforePathDraw, afterPathDraw));
};
// Parse SVG font-file into block of Canvas commands
var parseSVGFont = function(svg) {
// Store font attributes
var font = svg.getElementsByTagName("font");
p.glyphTable[url].horiz_adv_x = font[0].getAttribute("horiz-adv-x");
var font_face = svg.getElementsByTagName("font-face")[0];
p.glyphTable[url].units_per_em = parseFloat(font_face.getAttribute("units-per-em"));
p.glyphTable[url].ascent = parseFloat(font_face.getAttribute("ascent"));
p.glyphTable[url].descent = parseFloat(font_face.getAttribute("descent"));
var glyph = svg.getElementsByTagName("glyph"),
len = glyph.length;
// Loop through each glyph in the SVG
for (var i = 0; i < len; i++) {
// Store attributes for this glyph
var unicode = glyph[i].getAttribute("unicode");
var name = glyph[i].getAttribute("glyph-name");
horiz_adv_x = glyph[i].getAttribute("horiz-adv-x");
if (horiz_adv_x === null) {
horiz_adv_x = p.glyphTable[url].horiz_adv_x;
}
d = glyph[i].getAttribute("d");
// Split path commands in glpyh
if (d !== undef) {
path = buildPath(d);
// Store glyph data to table object
p.glyphTable[url][name] = {
name: name,
unicode: unicode,
horiz_adv_x: horiz_adv_x,
draw: path.draw
};
}
} // finished adding glyphs to table
};
// Load and parse Batik SVG font as XML into a Processing Glyph object
var loadXML = function() {
var xmlDoc;
try {
xmlDoc = document.implementation.createDocument("", "", null);
}
catch(e_fx_op) {
Processing.debug(e_fx_op.message);
return;
}
try {
xmlDoc.async = false;
xmlDoc.load(url);
parseSVGFont(xmlDoc.getElementsByTagName("svg")[0]);
}
catch(e_sf_ch) {
// Google Chrome, Safari etc.
Processing.debug(e_sf_ch);
try {
var xmlhttp = new window.XMLHttpRequest();
xmlhttp.open("GET", url, false);
xmlhttp.send(null);
parseSVGFont(xmlhttp.responseXML.documentElement);
}
catch(e) {
Processing.debug(e_sf_ch);
}
}
};
// Create a new object in glyphTable to store this font
p.glyphTable[url] = {};
// Begin loading the Batik SVG font...
loadXML(url);
// Return the loaded font for attribute grabbing
return p.glyphTable[url];
};
/**
* Gets the sketch parameter value. The parameter can be defined as the canvas attribute with
* the "data-processing-" prefix or provided in the pjs directive (e.g. param-test="52").
* The function tries the canvas attributes, then the pjs directive content.
*
* @param {String} name The name of the param to read.
*
* @returns {String} The parameter value, or null if parameter is not defined.
*/
p.param = function(name) {
// trying attribute that was specified in CANVAS
var attributeName = "data-processing-" + name;
if (curElement.hasAttribute(attributeName)) {
return curElement.getAttribute(attributeName);
}
// trying child PARAM elements of the CANVAS
for (var i = 0, len = curElement.childNodes.length; i < len; ++i) {
var item = curElement.childNodes.item(i);
if (item.nodeType !== 1 || item.tagName.toLowerCase() !== "param") {
continue;
}
if (item.getAttribute("name") === name) {
return item.getAttribute("value");
}
}
// fallback to default params
if (curSketch.params.hasOwnProperty(name)) {
return curSketch.params[name];
}
return null;
};
////////////////////////////////////////////////////////////////////////////
// 2D/3D methods wiring utils
////////////////////////////////////////////////////////////////////////////
function wireDimensionalFunctions(mode) {
// Drawing2D/Drawing3D
if (mode === '3D') {
drawing = new Drawing3D();
} else if (mode === '2D') {
drawing = new Drawing2D();
} else {
drawing = new DrawingPre();
}
// Wire up functions (Use DrawingPre properties names)
for (var i in DrawingPre.prototype) {
if (DrawingPre.prototype.hasOwnProperty(i) && i.indexOf("$") < 0) {
p[i] = drawing[i];
}
}
// Run initialization
drawing.$init();
}
function createDrawingPreFunction(name) {
return function() {
wireDimensionalFunctions("2D");
return drawing[name].apply(this, arguments);
};
}
DrawingPre.prototype.translate = createDrawingPreFunction("translate");
DrawingPre.prototype.transform = createDrawingPreFunction("transform");
DrawingPre.prototype.scale = createDrawingPreFunction("scale");
DrawingPre.prototype.pushMatrix = createDrawingPreFunction("pushMatrix");
DrawingPre.prototype.popMatrix = createDrawingPreFunction("popMatrix");
DrawingPre.prototype.resetMatrix = createDrawingPreFunction("resetMatrix");
DrawingPre.prototype.applyMatrix = createDrawingPreFunction("applyMatrix");
DrawingPre.prototype.rotate = createDrawingPreFunction("rotate");
DrawingPre.prototype.rotateZ = createDrawingPreFunction("rotateZ");
DrawingPre.prototype.shearX = createDrawingPreFunction("shearX");
DrawingPre.prototype.shearY = createDrawingPreFunction("shearY");
DrawingPre.prototype.redraw = createDrawingPreFunction("redraw");
DrawingPre.prototype.toImageData = createDrawingPreFunction("toImageData");
DrawingPre.prototype.ambientLight = createDrawingPreFunction("ambientLight");
DrawingPre.prototype.directionalLight = createDrawingPreFunction("directionalLight");
DrawingPre.prototype.lightFalloff = createDrawingPreFunction("lightFalloff");
DrawingPre.prototype.lightSpecular = createDrawingPreFunction("lightSpecular");
DrawingPre.prototype.pointLight = createDrawingPreFunction("pointLight");
DrawingPre.prototype.noLights = createDrawingPreFunction("noLights");
DrawingPre.prototype.spotLight = createDrawingPreFunction("spotLight");
DrawingPre.prototype.beginCamera = createDrawingPreFunction("beginCamera");
DrawingPre.prototype.endCamera = createDrawingPreFunction("endCamera");
DrawingPre.prototype.frustum = createDrawingPreFunction("frustum");
DrawingPre.prototype.box = createDrawingPreFunction("box");
DrawingPre.prototype.sphere = createDrawingPreFunction("sphere");
DrawingPre.prototype.ambient = createDrawingPreFunction("ambient");
DrawingPre.prototype.emissive = createDrawingPreFunction("emissive");
DrawingPre.prototype.shininess = createDrawingPreFunction("shininess");
DrawingPre.prototype.specular = createDrawingPreFunction("specular");
DrawingPre.prototype.fill = createDrawingPreFunction("fill");
DrawingPre.prototype.stroke = createDrawingPreFunction("stroke");
DrawingPre.prototype.strokeWeight = createDrawingPreFunction("strokeWeight");
DrawingPre.prototype.smooth = createDrawingPreFunction("smooth");
DrawingPre.prototype.noSmooth = createDrawingPreFunction("noSmooth");
DrawingPre.prototype.point = createDrawingPreFunction("point");
DrawingPre.prototype.vertex = createDrawingPreFunction("vertex");
DrawingPre.prototype.endShape = createDrawingPreFunction("endShape");
DrawingPre.prototype.bezierVertex = createDrawingPreFunction("bezierVertex");
DrawingPre.prototype.curveVertex = createDrawingPreFunction("curveVertex");
DrawingPre.prototype.curve = createDrawingPreFunction("curve");
DrawingPre.prototype.line = createDrawingPreFunction("line");
DrawingPre.prototype.bezier = createDrawingPreFunction("bezier");
DrawingPre.prototype.rect = createDrawingPreFunction("rect");
DrawingPre.prototype.ellipse = createDrawingPreFunction("ellipse");
DrawingPre.prototype.background = createDrawingPreFunction("background");
DrawingPre.prototype.image = createDrawingPreFunction("image");
DrawingPre.prototype.textWidth = createDrawingPreFunction("textWidth");
DrawingPre.prototype.text$line = createDrawingPreFunction("text$line");
DrawingPre.prototype.$ensureContext = createDrawingPreFunction("$ensureContext");
DrawingPre.prototype.$newPMatrix = createDrawingPreFunction("$newPMatrix");
DrawingPre.prototype.size = function(aWidth, aHeight, aMode) {
wireDimensionalFunctions(aMode === PConstants.WEBGL ? "3D" : "2D");
p.size(aWidth, aHeight, aMode);
};
DrawingPre.prototype.$init = noop;
Drawing2D.prototype.$init = function() {
// Setup default 2d canvas context.
// Moving this here removes the number of times we need to check the 3D variable
p.size(p.width, p.height);
curContext.lineCap = 'round';
// Set default stroke and fill color
p.noSmooth();
p.disableContextMenu();
};
Drawing3D.prototype.$init = function() {
// For ref/perf test compatibility until those are fixed
p.use3DContext = true;
p.disableContextMenu();
};
DrawingShared.prototype.$ensureContext = function() {
return curContext;
};
//////////////////////////////////////////////////////////////////////////
// Keyboard Events
//////////////////////////////////////////////////////////////////////////
// In order to catch key events in a canvas, it needs to be "specially focusable",
// by assigning it a tabindex. If no tabindex is specified on-page, set this to 0.
if (!curElement.getAttribute("tabindex")) {
curElement.setAttribute("tabindex", 0);
}
function getKeyCode(e) {
var code = e.which || e.keyCode;
switch (code) {
case 13: // ENTER
return 10;
case 91: // META L (Saf/Mac)
case 93: // META R (Saf/Mac)
case 224: // META (FF/Mac)
return 157;
case 57392: // CONTROL (Op/Mac)
return 17;
case 46: // DELETE
return 127;
case 45: // INSERT
return 155;
}
return code;
}
function getKeyChar(e) {
var c = e.which || e.keyCode;
var anyShiftPressed = e.shiftKey || e.ctrlKey || e.altKey || e.metaKey;
switch (c) {
case 13:
c = anyShiftPressed ? 13 : 10; // RETURN vs ENTER (Mac)
break;
case 8:
c = anyShiftPressed ? 127 : 8; // DELETE vs BACKSPACE (Mac)
break;
}
return new Char(c);
}
function suppressKeyEvent(e) {
if (typeof e.preventDefault === "function") {
e.preventDefault();
} else if (typeof e.stopPropagation === "function") {
e.stopPropagation();
}
return false;
}
function updateKeyPressed() {
var ch;
for (ch in pressedKeysMap) {
if (pressedKeysMap.hasOwnProperty(ch)) {
p.__keyPressed = true;
return;
}
}
p.__keyPressed = false;
}
function resetKeyPressed() {
p.__keyPressed = false;
pressedKeysMap = [];
lastPressedKeyCode = null;
}
function simulateKeyTyped(code, c) {
pressedKeysMap[code] = c;
lastPressedKeyCode = null;
p.key = c;
p.keyCode = code;
p.keyPressed();
p.keyCode = 0;
p.keyTyped();
updateKeyPressed();
}
function handleKeydown(e) {
var code = getKeyCode(e);
if (code === PConstants.DELETE) {
simulateKeyTyped(code, new Char(127));
return;
}
if (codedKeys.indexOf(code) < 0) {
lastPressedKeyCode = code;
return;
}
var c = new Char(PConstants.CODED);
p.key = c;
p.keyCode = code;
pressedKeysMap[code] = c;
p.keyPressed();
lastPressedKeyCode = null;
updateKeyPressed();
return suppressKeyEvent(e);
}
function handleKeypress(e) {
if (lastPressedKeyCode === null) {
return; // processed in handleKeydown
}
var code = lastPressedKeyCode, c = getKeyChar(e);
simulateKeyTyped(code, c);
return suppressKeyEvent(e);
}
function handleKeyup(e) {
var code = getKeyCode(e), c = pressedKeysMap[code];
if (c === undef) {
return; // no keyPressed event was generated.
}
p.key = c;
p.keyCode = code;
p.keyReleased();
delete pressedKeysMap[code];
updateKeyPressed();
}
// Send aCode Processing syntax to be converted to JavaScript
if (!pgraphicsMode) {
if (aCode instanceof Processing.Sketch) {
// Use sketch as is
curSketch = aCode;
} else if (typeof aCode === "function") {
// Wrap function with default sketch parameters
curSketch = new Processing.Sketch(aCode);
} else if (!aCode) {
// Empty sketch
curSketch = new Processing.Sketch(function (){});
} else {
//#if PARSER
// Compile the code
curSketch = Processing.compile(aCode);
//#else
// throw "PJS compile is not supported";
//#endif
}
// Expose internal field for diagnostics and testing
p.externals.sketch = curSketch;
wireDimensionalFunctions();
// the onfocus and onblur events are handled in two parts.
// 1) the p.focused property is handled per sketch
curElement.onfocus = function() {
p.focused = true;
};
curElement.onblur = function() {
p.focused = false;
if (!curSketch.options.globalKeyEvents) {
resetKeyPressed();
}
};
// 2) looping status is handled per page, based on the pauseOnBlur @pjs directive
if (curSketch.options.pauseOnBlur) {
attachEventHandler(window, 'focus', function() {
if (doLoop) {
p.loop();
}
});
attachEventHandler(window, 'blur', function() {
if (doLoop && loopStarted) {
p.noLoop();
doLoop = true; // make sure to keep this true after the noLoop call
}
resetKeyPressed();
});
}
// if keyboard events should be handled globally, the listeners should
// be bound to the document window, rather than to the current canvas
var keyTrigger = curSketch.options.globalKeyEvents ? window : curElement;
attachEventHandler(keyTrigger, "keydown", handleKeydown);
attachEventHandler(keyTrigger, "keypress", handleKeypress);
attachEventHandler(keyTrigger, "keyup", handleKeyup);
// Step through the libraries that were attached at doc load...
for (var i in Processing.lib) {
if (Processing.lib.hasOwnProperty(i)) {
if(Processing.lib[i].hasOwnProperty("attach")) {
// use attach function if present
Processing.lib[i].attach(p);
} else if(Processing.lib[i] instanceof Function) {
// Init the libraries in the context of this p_instance (legacy)
Processing.lib[i].call(this);
}
}
}
// sketch execute test interval, used to reschedule
// an execute when preloads have not yet finished.
var retryInterval = 100;
var executeSketch = function(processing) {
// Don't start until all specified images and fonts in the cache are preloaded
if (!(curSketch.imageCache.pending || PFont.preloading.pending(retryInterval))) {
// the opera preload cache can only be cleared once we start
if (window.opera) {
var link,
element,
operaCache=curSketch.imageCache.operaCache;
for (link in operaCache) {
if(operaCache.hasOwnProperty(link)) {
element = operaCache[link];
if (element !== null) {
document.body.removeChild(element);
}
delete(operaCache[link]);
}
}
}
curSketch.attach(processing, defaultScope);
// pass a reference to the p instance for this sketch.
curSketch.onLoad(processing);
// Run void setup()
if (processing.setup) {
processing.setup();
// if any transforms were performed in setup reset to identity matrix
// so draw loop is unpolluted
processing.resetMatrix();
curSketch.onSetup();
}
// some pixels can be cached, flushing
resetContext();
if (processing.draw) {
if (!doLoop) {
processing.redraw();
} else {
processing.loop();
}
}
} else {
window.setTimeout(function() { executeSketch(processing); }, retryInterval);
}
};
// Only store an instance of non-createGraphics instances.
addInstance(this);
// The parser adds custom methods to the processing context
// this renames p to processing so these methods will run
executeSketch(p);
} else {
// No executable sketch was specified
// or called via createGraphics
curSketch = new Processing.Sketch();
wireDimensionalFunctions();
// Hack to make PGraphics work again after splitting size()
p.size = function(w, h, render) {
if (render && render === PConstants.WEBGL) {
wireDimensionalFunctions('3D');
} else {
wireDimensionalFunctions('2D');
}
p.size(w, h, render);
};
}
};
// Place-holder for overridable debugging function
Processing.debug = (function() {
if ("console" in window) {
return function(msg) {
window.console.log('Processing.js: ' + msg);
};
}
return noop;
}());
// bind prototype
Processing.prototype = defaultScope;
/**
* instance store and lookup
*/
Processing.instances = processingInstances;
Processing.getInstanceById = function(name) {
return processingInstances[processingInstanceIds[name]];
};
// 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));
// we're done. Return our object.
return Processing;
};
},{}],27:[function(require,module,exports){
// Base source files
var source = {
virtEquals: require("./Helpers/virtEquals"),
virtHashCode: require("./Helpers/virtHashCode"),
ObjectIterator: require("./Helpers/ObjectIterator"),
PConstants: require("./Helpers/PConstants"),
ArrayList: require("./Objects/ArrayList"),
HashMap: require("./Objects/HashMap"),
PVector: require("./Objects/PVector"),
PFont: require("./Objects/PFont"),
Char: require("./Objects/Char"),
XMLAttribute: require("./Objects/XMLAttribute"),
XMLElement: require("./Objects/XMLElement"),
PMatrix2D: require("./Objects/PMatrix2D"),
PMatrix3D: require("./Objects/PMatrix3D"),
PShape: require("./Objects/PShape"),
colors: require("./Objects/webcolors"),
PShapeSVG: require("./Objects/PShapeSVG"),
CommonFunctions: require("./P5Functions/commonFunctions"),
defaultScope: require("./Helpers/defaultScope"),
Processing: require("./Processing"),
setupParser: require("./Parser/Parser"),
finalize: require("./Helpers/finalizeProcessing")
};
// Additional code that gets tacked onto "p" during
// instantiation of a Processing sketch.
source.extend = {
withMath: require("./P5Functions/Math.js"),
withProxyFunctions: require("./P5Functions/JavaProxyFunctions")(source.virtHashCode, source.virtEquals),
withTouch: require("./P5Functions/touchmouse"),
withCommonFunctions: source.CommonFunctions.withCommonFunctions
};
/**
* Processing.js building function
*/
module.exports = function buildProcessingJS(Browser, testHarness) {
var noop = function(){},
virtEquals = source.virtEquals,
virtHashCode = source.virtHashCode,
PConstants = source.PConstants,
CommonFunctions = source.CommonFunctions,
ObjectIterator = source.ObjectIterator,
Char = source.Char,
XMLAttribute = source.XMLAttribute(),
ArrayList = source.ArrayList({
virtHashCode: virtHashCode,
virtEquals: virtEquals
}),
HashMap = source.HashMap({
virtHashCode: virtHashCode,
virtEquals: virtEquals
}),
PVector = source.PVector({
PConstants: PConstants
}),
PFont = source.PFont({
Browser: Browser,
noop: noop
}),
XMLElement = source.XMLElement({
Browser: Browser,
XMLAttribute: XMLAttribute
}),
PMatrix2D = source.PMatrix2D({
p:CommonFunctions
}),
PMatrix3D = source.PMatrix3D({
p:CommonFunctions
}),
PShape = source.PShape({
PConstants: PConstants,
PMatrix2D: PMatrix2D,
PMatrix3D: PMatrix3D
}),
PShapeSVG = source.PShapeSVG({
CommonFunctions: CommonFunctions,
PConstants: PConstants,
PShape: PShape,
XMLElement: XMLElement,
colors: source.colors
}),
defaultScope = source.defaultScope({
ArrayList: ArrayList,
HashMap: HashMap,
PVector: PVector,
PFont: PFont,
PShapeSVG: PShapeSVG,
ObjectIterator: ObjectIterator,
PConstants: PConstants,
Char: Char,
XMLElement: XMLElement,
XML: XMLElement
}),
Processing = source.Processing({
defaultScope: defaultScope,
Browser: Browser,
extend: source.extend,
noop: noop
});
// set up the Processing syntax parser
Processing = source.setupParser(Processing, {
Browser: Browser,
aFunctions: testHarness,
defaultScope: defaultScope
});
// finalise the Processing object
Processing = source.finalize(Processing, {
version: require('../package.json').version,
isDomPresent: false || Browser.isDomPresent,
window: Browser.window,
document: Browser.document,
noop: noop
});
// done.
return Processing;
};
},{"../package.json":2,"./Helpers/ObjectIterator":3,"./Helpers/PConstants":4,"./Helpers/defaultScope":5,"./Helpers/finalizeProcessing":6,"./Helpers/virtEquals":7,"./Helpers/virtHashCode":8,"./Objects/ArrayList":9,"./Objects/Char":10,"./Objects/HashMap":11,"./Objects/PFont":12,"./Objects/PMatrix2D":13,"./Objects/PMatrix3D":14,"./Objects/PShape":15,"./Objects/PShapeSVG":16,"./Objects/PVector":17,"./Objects/XMLAttribute":18,"./Objects/XMLElement":19,"./Objects/webcolors":20,"./P5Functions/JavaProxyFunctions":21,"./P5Functions/Math.js":22,"./P5Functions/commonFunctions":23,"./P5Functions/touchmouse":24,"./Parser/Parser":25,"./Processing":26}]},{},[1])