From 686106d544ecc3b6ffd4db2b665d3bc879a58d8c Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Mon, 24 Sep 2012 16:22:07 -0400 Subject: ok --- public/css/warpfx.css | 0 public/img/black-cursor.png | Bin 0 -> 468 bytes public/img/cursor-blue.png | Bin 0 -> 538 bytes public/img/cursor-purple.png | Bin 0 -> 516 bytes public/img/cursor-red.png | Bin 0 -> 472 bytes public/img/cursor.png | Bin 0 -> 459 bytes public/img/handloader.gif | Bin 0 -> 525 bytes public/img/pointer.png | Bin 0 -> 227 bytes public/img/white-cursor.png | Bin 0 -> 459 bytes public/js/app.js | 68 + public/js/chat.js | 53 + public/js/grid.js | 202 + public/js/mouse.js | 9 + public/js/user.js | 14 + public/js/util.js | 7 + public/js/vendor/jquery-1.8.2.js | 2 + public/js/vendor/processing-1.1.0.js | 18736 +++++++++++++++++++++++++++++++++ public/wav/CYmbal0001.wav | Bin 0 -> 111562 bytes public/wav/Clap.wav | Bin 0 -> 369638 bytes public/wav/Clav.wav | Bin 0 -> 4434 bytes public/wav/Closed Hihat0001.wav | Bin 0 -> 8148 bytes public/wav/Cowbell.wav | Bin 0 -> 136518 bytes public/wav/Hi Conga0001.wav | Bin 0 -> 18314 bytes public/wav/Hi Tom0001.wav | Bin 0 -> 78250 bytes public/wav/KickDrum0001.wav | Bin 0 -> 11312 bytes public/wav/Low Conga0001.wav | Bin 0 -> 64356 bytes public/wav/Low Tom0001.wav | Bin 0 -> 83898 bytes public/wav/Maraca.wav | Bin 0 -> 6566 bytes public/wav/Mid Conga0001.wav | Bin 0 -> 27304 bytes public/wav/Mid Tom0001.wav | Bin 0 -> 63516 bytes public/wav/Open Hihat0001.wav | Bin 0 -> 11286 bytes public/wav/Rimshot.wav | Bin 0 -> 4830 bytes public/wav/SnareDrum0001.wav | Bin 0 -> 140468 bytes 33 files changed, 19091 insertions(+) create mode 100644 public/css/warpfx.css create mode 100644 public/img/black-cursor.png create mode 100644 public/img/cursor-blue.png create mode 100644 public/img/cursor-purple.png create mode 100644 public/img/cursor-red.png create mode 100644 public/img/cursor.png create mode 100644 public/img/handloader.gif create mode 100644 public/img/pointer.png create mode 100644 public/img/white-cursor.png create mode 100644 public/js/app.js create mode 100644 public/js/chat.js create mode 100644 public/js/grid.js create mode 100644 public/js/mouse.js create mode 100644 public/js/user.js create mode 100644 public/js/util.js create mode 100644 public/js/vendor/jquery-1.8.2.js create mode 100644 public/js/vendor/processing-1.1.0.js create mode 100644 public/wav/CYmbal0001.wav create mode 100644 public/wav/Clap.wav create mode 100644 public/wav/Clav.wav create mode 100644 public/wav/Closed Hihat0001.wav create mode 100644 public/wav/Cowbell.wav create mode 100644 public/wav/Hi Conga0001.wav create mode 100644 public/wav/Hi Tom0001.wav create mode 100644 public/wav/KickDrum0001.wav create mode 100644 public/wav/Low Conga0001.wav create mode 100644 public/wav/Low Tom0001.wav create mode 100644 public/wav/Maraca.wav create mode 100644 public/wav/Mid Conga0001.wav create mode 100644 public/wav/Mid Tom0001.wav create mode 100644 public/wav/Open Hihat0001.wav create mode 100644 public/wav/Rimshot.wav create mode 100644 public/wav/SnareDrum0001.wav (limited to 'public') diff --git a/public/css/warpfx.css b/public/css/warpfx.css new file mode 100644 index 0000000..e69de29 diff --git a/public/img/black-cursor.png b/public/img/black-cursor.png new file mode 100644 index 0000000..9f9293a Binary files /dev/null and b/public/img/black-cursor.png differ diff --git a/public/img/cursor-blue.png b/public/img/cursor-blue.png new file mode 100644 index 0000000..a82bf52 Binary files /dev/null and b/public/img/cursor-blue.png differ diff --git a/public/img/cursor-purple.png b/public/img/cursor-purple.png new file mode 100644 index 0000000..fa010ab Binary files /dev/null and b/public/img/cursor-purple.png differ diff --git a/public/img/cursor-red.png b/public/img/cursor-red.png new file mode 100644 index 0000000..bb356e9 Binary files /dev/null and b/public/img/cursor-red.png differ diff --git a/public/img/cursor.png b/public/img/cursor.png new file mode 100644 index 0000000..89db769 Binary files /dev/null and b/public/img/cursor.png differ diff --git a/public/img/handloader.gif b/public/img/handloader.gif new file mode 100644 index 0000000..6733284 Binary files /dev/null and b/public/img/handloader.gif differ diff --git a/public/img/pointer.png b/public/img/pointer.png new file mode 100644 index 0000000..ea0dc1e Binary files /dev/null and b/public/img/pointer.png differ diff --git a/public/img/white-cursor.png b/public/img/white-cursor.png new file mode 100644 index 0000000..89db769 Binary files /dev/null and b/public/img/white-cursor.png differ diff --git a/public/js/app.js b/public/js/app.js new file mode 100644 index 0000000..caca9f8 --- /dev/null +++ b/public/js/app.js @@ -0,0 +1,68 @@ +function App(){ + var app = this; + var socket = io.connect(window.location.hostname); + + // Initialize all the modules of this app + function init(){ + app.user = new User (app); + app.chat = new Chat (app); + app.grid = new Grid (app); + bind(); + } + + // Bind events that should be handled by the app generically + function bind(){ + $(window).on({ + blur: function(){ + $("#message").blur(); + }, + focus: function(){ + // $message.focus(); + }, + keydown: function(e) { + // console.log(e.keyCode); + switch(e.keyCode) { + case 32: // space + if (! app.chat.focused) { + app.grid.toggle(); + return false; + } + break; + case 9: // tab + $("#message").focus(); + return false; + } + } + }); + } + + // Send a JSON object to the other clients + app.send = function(ns, message){ + socket.emit(ns, JSON.stringify(message)); + } + + // Listen for events from other clients + app.receive = function(ns, callback){ + // socket.on(ns, callback); + socket.on(ns, function(json){ + var data; + console.log(ns, json); + try { + console.log(typeof json); + data = (typeof json === "string") ? JSON.parse(json) : json; + } + catch (e) { + console.log("ERROR PARSING JSON"); + data = {}; + } + callback(data); + }); + } + + init(); +} + +// Load the app when the DOM is ready +$(function(){ + var app = new App (); +}); diff --git a/public/js/chat.js b/public/js/chat.js new file mode 100644 index 0000000..8f9a82c --- /dev/null +++ b/public/js/chat.js @@ -0,0 +1,53 @@ +function Chat (app) { + var base = this; + var $messages = $("#messages"), + $message = $("#message"), + $send = $("#send"); + base.focused = false; + + // Initialize the chat module + function init(){ + bind(); + $message.focus(); + } + + // Bind events + function bind(){ + // Listen for chat messages + app.receive("event-chat", function(data){ + base.receive(data.name, data.message); + }) + + // Bind to the chat input form + $message.on({ + focus: function(){ + base.focused = true; + }, + blur: function(){ + base.focused = false; + }, + keydown: function(e){ + switch (e.keyCode) { + case 13: // enter + base.send(); + break; + } + } + }); + } + + // Get a message from the message box and send it out + base.send = function () { + var message = $("#message").val(); + $("#message").val(""); + base.receive(app.user.name, message); + app.send("event-chat", { 'name': app.user.name, 'message': message }); + } + + // Receive a message and drop it in the chat + base.receive = function (name, message) { + $("#messages").append("" + sanitize(name) + "" + sanitize(message) + "
"); + } + + init(); +}; diff --git a/public/js/grid.js b/public/js/grid.js new file mode 100644 index 0000000..e23c0e6 --- /dev/null +++ b/public/js/grid.js @@ -0,0 +1,202 @@ +function Grid (app){ + var base = this; + + function setNote (data) { + console.log(data.step, data.channel, data.state, pattern[data.step]); + pattern[data.step][data.channel] = data.state; + drawNote(data.step, data.channel); + }; + base.setBeat = function(beatId) { + beat = beatId; + }; + base.setTempo = function(tempo) { + tick = tempo; + } + base.toggle = toggle; + + var tog = 0; + var playing = false; + var playingInterval = false; + var tick = 125; + var pattern = undefined; + var channels = []; + var channelsOn = []; + var activecoloron = 'rgb(255,255,255)'; + var activecoloroff = 'rgb(202,202,202)'; + var inactivecoloron = 'rgb(152,152,152)'; + var inactivecoloroff = 'rgb(28,28,28)'; + var gridSize = Math.floor(600/16); + var lastStep = 16; + var channelCount = 8; + var beat = lastStep-1; + var inset = 5; + + var canvas = document.getElementById('canvas'); //.offset(); + var ctx = canvas.getContext('2d'); + init(); + + function init(){ + bind(); + load(); + } + + function bind(){ + app.receive("event-grid", loadGrid); + app.receive("event-note", setNote); + + $('#start').click(start); + $('#stop').click(stop); + $('#tempo').change(function() { + resetTempo( document.getElementById('tempo').value ) + }); + $('#canvas').click(function(e){ + var x = e.pageX-canvas.offsetLeft-10; + var y = e.pageY-canvas.offsetTop-10; + + x = Math.floor(x / gridSize); + y = Math.floor(y / gridSize); + + toggleNote(x, y); + + app.send("event-note", { + 'step': x, + 'channel': y, + 'state': pattern[x][y] + }); + }); + } + + function loadGrid(data){ + pattern = data.pattern; + tempo = data.tempo; + stop(); + start(); + } + + function load(){ + if (playing) { + playingInterval = setInterval(make, tick); + } + makeGrid(); + } + + function toggle(){ + playing ? stop() : start(); + } + + function start() { + reset(); + playing = true; + load(); + document.body.bgColor = "#000000"; + for (var i = 0; i < channelCount; i++) { + channels[i] = document.getElementById('sound' + i); + channelsOn[i] = false; + } + for (var i = 0; i < channelCount; i++) { + channels[i+8] = document.getElementById('sound' + i + 'a'); + } + } + function stop() { + playing = false; + document.body.bgColor = "#444444"; + for (var i = 0; i < channelCount; i++) { + document.getElementById('sound' + i).pause(); + document.getElementById('sound' + i + 'a').pause(); + } + reset(); + } + function reset() { + clearInterval(playingInterval); + ctx.clearRect(0,0, canvas.width, canvas.height); + beat = lastStep-1; + } + function resetTempo(tempo) { + if (tempo <= 1 || tempo >= 800) { + tempo = 120; + } + tick = (1000 / (tempo / 60))/4; + $("#alertDisp").html( "Set tempo to " + tempo ); + if (playing) { + clearInterval(playingInterval); + playingInterval = setInterval(make, tick); + } + } + + function make() { + beat += 1; + if (beat == lastStep) { + makeColumn(beat-1,activecoloroff,inactivecoloroff,false); + beat = 0; + makeColumn(beat,activecoloron,inactivecoloron,true); + } + else { + makeColumn(beat,activecoloron,inactivecoloron,true); + makeColumn(beat-1,activecoloroff,inactivecoloroff,false); + } + } + function makeColumn(x,active,inactive,goplaysample) { + for (var y = 0; y < channelCount; y += 1) { + if (pattern[x][y] == 1) { + if (goplaysample === true) { + // ctx.fillStyle = active; + ctx.fillStyle = random_color(); + var tmpAudio; + if (channelsOn[y] == true) { + channelsOn[y] = false; + tmpAudio = channels[y+8]; + } + else { + tmpAudio = channels[y]; + } + tmpAudio.currentTime = 0; + if (tmpAudio.paused) + tmpAudio.play(); + } + else { + ctx.fillStyle = active; + } + ctx.fillRect((x*gridSize) + inset, (y*gridSize) + inset, gridSize - 2*inset, gridSize - 2*inset); + } + else { + ctx.fillStyle = inactive; + } + } + } + function makeGrid() { + for (var x = 0; x < lastStep; x += 1) { + for (var y = 0; y < channelCount; y += 1) { + if (pattern && pattern[x][y] == 1) { + ctx.fillStyle = activecoloroff; + } + else { + ctx.fillStyle = inactivecoloroff; + } + ctx.fillRect((x*gridSize) + inset, (y*gridSize) + inset, gridSize - 2*inset, gridSize - 2*inset); + } + } + } + function drawNote(x,y) { + if (pattern[x][y] == 1) { + ctx.fillStyle = activecoloroff; + } else { + ctx.fillStyle = inactivecoloroff; + } + ctx.fillRect((x*gridSize) + inset, (y*gridSize) + inset, gridSize - 2*inset, gridSize - 2*inset); + } + function toggleNote(x,y) { + if (pattern[x][y] == 1) { + pattern[x][y] = 0; + } + else { + pattern[x][y] = 1; + } + drawNote(x,y); + } + + function random_color() { + var rint = Math.round(0xffffff * Math.random()); + return 'rgb(' + (rint >> 16) + ',' + (rint >> 8 & 255) + ',' + (rint & 255) + ')'; + } + +}; diff --git a/public/js/mouse.js b/public/js/mouse.js new file mode 100644 index 0000000..ac3eb82 --- /dev/null +++ b/public/js/mouse.js @@ -0,0 +1,9 @@ +function Mouse (){ + var base = this; + function init () { + + }; + function move(x,y){ + + }; +} diff --git a/public/js/user.js b/public/js/user.js new file mode 100644 index 0000000..4233c5a --- /dev/null +++ b/public/js/user.js @@ -0,0 +1,14 @@ +function User (app) { + var base = this; + base.name = Math.floor(Math.random() * 1000000) + ""; + + app.send('event-join', { + 'name': base.name + }); + + app.receive('event-join', function(json){ + console.log(json); + app.chat.receive(json.name, "has joined") + }) + +} diff --git a/public/js/util.js b/public/js/util.js new file mode 100644 index 0000000..e69d002 --- /dev/null +++ b/public/js/util.js @@ -0,0 +1,7 @@ +var LESS_THAN = //g, AMPERSAND = /&/g; +function sanitize (s) { + if (s) { + return s.replace(LESS_THAN, '<').replace(GREATER_THAN, '>').replace(AMPERSAND, '&'); + } + return ""; +} \ No newline at end of file diff --git a/public/js/vendor/jquery-1.8.2.js b/public/js/vendor/jquery-1.8.2.js new file mode 100644 index 0000000..8d529ce --- /dev/null +++ b/public/js/vendor/jquery-1.8.2.js @@ -0,0 +1,2 @@ +/*! jQuery v1.8.2 jquery.com | jquery.org/license */ +(function(a,b){function G(a){var b=F[a]={};return p.each(a.split(s),function(a,c){b[c]=!0}),b}function J(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(I,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:+d+""===d?+d:H.test(d)?p.parseJSON(d):d}catch(f){}p.data(a,c,d)}else d=b}return d}function K(a){var b;for(b in a){if(b==="data"&&p.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function ba(){return!1}function bb(){return!0}function bh(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function bi(a,b){do a=a[b];while(a&&a.nodeType!==1);return a}function bj(a,b,c){b=b||0;if(p.isFunction(b))return p.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return p.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=p.grep(a,function(a){return a.nodeType===1});if(be.test(b))return p.filter(b,d,!c);b=p.filter(b,d)}return p.grep(a,function(a,d){return p.inArray(a,b)>=0===c})}function bk(a){var b=bl.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}function bC(a,b){return a.getElementsByTagName(b)[0]||a.appendChild(a.ownerDocument.createElement(b))}function bD(a,b){if(b.nodeType!==1||!p.hasData(a))return;var c,d,e,f=p._data(a),g=p._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;d").appendTo(e.body),c=b.css("display");b.remove();if(c==="none"||c===""){bI=e.body.appendChild(bI||p.extend(e.createElement("iframe"),{frameBorder:0,width:0,height:0}));if(!bJ||!bI.createElement)bJ=(bI.contentWindow||bI.contentDocument).document,bJ.write(""),bJ.close();b=bJ.body.appendChild(bJ.createElement(a)),c=bH(b,"display"),e.body.removeChild(bI)}return bS[a]=c,c}function ci(a,b,c,d){var e;if(p.isArray(b))p.each(b,function(b,e){c||ce.test(a)?d(a,e):ci(a+"["+(typeof e=="object"?b:"")+"]",e,c,d)});else if(!c&&p.type(b)==="object")for(e in b)ci(a+"["+e+"]",b[e],c,d);else d(a,b)}function cz(a){return function(b,c){typeof b!="string"&&(c=b,b="*");var d,e,f,g=b.toLowerCase().split(s),h=0,i=g.length;if(p.isFunction(c))for(;h)[^>]*$|#([\w\-]*)$)/,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^[\],:{}\s]*$/,x=/(?:^|:|,)(?:\s*\[)+/g,y=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,z=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,A=/^-ms-/,B=/-([\da-z])/gi,C=function(a,b){return(b+"").toUpperCase()},D=function(){e.addEventListener?(e.removeEventListener("DOMContentLoaded",D,!1),p.ready()):e.readyState==="complete"&&(e.detachEvent("onreadystatechange",D),p.ready())},E={};p.fn=p.prototype={constructor:p,init:function(a,c,d){var f,g,h,i;if(!a)return this;if(a.nodeType)return this.context=this[0]=a,this.length=1,this;if(typeof a=="string"){a.charAt(0)==="<"&&a.charAt(a.length-1)===">"&&a.length>=3?f=[null,a,null]:f=u.exec(a);if(f&&(f[1]||!c)){if(f[1])return c=c instanceof p?c[0]:c,i=c&&c.nodeType?c.ownerDocument||c:e,a=p.parseHTML(f[1],i,!0),v.test(f[1])&&p.isPlainObject(c)&&this.attr.call(a,c,!0),p.merge(this,a);g=e.getElementById(f[2]);if(g&&g.parentNode){if(g.id!==f[2])return d.find(a);this.length=1,this[0]=g}return this.context=e,this.selector=a,this}return!c||c.jquery?(c||d).find(a):this.constructor(c).find(a)}return p.isFunction(a)?d.ready(a):(a.selector!==b&&(this.selector=a.selector,this.context=a.context),p.makeArray(a,this))},selector:"",jquery:"1.8.2",length:0,size:function(){return this.length},toArray:function(){return k.call(this)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=p.merge(this.constructor(),a);return d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")"),d},each:function(a,b){return p.each(this,a,b)},ready:function(a){return p.ready.promise().done(a),this},eq:function(a){return a=+a,a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(k.apply(this,arguments),"slice",k.call(arguments).join(","))},map:function(a){return this.pushStack(p.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:j,sort:[].sort,splice:[].splice},p.fn.init.prototype=p.fn,p.extend=p.fn.extend=function(){var a,c,d,e,f,g,h=arguments[0]||{},i=1,j=arguments.length,k=!1;typeof h=="boolean"&&(k=h,h=arguments[1]||{},i=2),typeof h!="object"&&!p.isFunction(h)&&(h={}),j===i&&(h=this,--i);for(;i0)return;d.resolveWith(e,[p]),p.fn.trigger&&p(e).trigger("ready").off("ready")},isFunction:function(a){return p.type(a)==="function"},isArray:Array.isArray||function(a){return p.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):E[m.call(a)]||"object"},isPlainObject:function(a){if(!a||p.type(a)!=="object"||a.nodeType||p.isWindow(a))return!1;try{if(a.constructor&&!n.call(a,"constructor")&&!n.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||n.call(a,d)},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},error:function(a){throw new Error(a)},parseHTML:function(a,b,c){var d;return!a||typeof a!="string"?null:(typeof b=="boolean"&&(c=b,b=0),b=b||e,(d=v.exec(a))?[b.createElement(d[1])]:(d=p.buildFragment([a],b,c?null:[]),p.merge([],(d.cacheable?p.clone(d.fragment):d.fragment).childNodes)))},parseJSON:function(b){if(!b||typeof b!="string")return null;b=p.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(w.test(b.replace(y,"@").replace(z,"]").replace(x,"")))return(new Function("return "+b))();p.error("Invalid JSON: "+b)},parseXML:function(c){var d,e;if(!c||typeof c!="string")return null;try{a.DOMParser?(e=new DOMParser,d=e.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(f){d=b}return(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&p.error("Invalid XML: "+c),d},noop:function(){},globalEval:function(b){b&&r.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(A,"ms-").replace(B,C)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,c,d){var e,f=0,g=a.length,h=g===b||p.isFunction(a);if(d){if(h){for(e in a)if(c.apply(a[e],d)===!1)break}else for(;f0&&a[0]&&a[i-1]||i===0||p.isArray(a));if(j)for(;h-1)i.splice(c,1),e&&(c<=g&&g--,c<=h&&h--)}),this},has:function(a){return p.inArray(a,i)>-1},empty:function(){return i=[],this},disable:function(){return i=j=c=b,this},disabled:function(){return!i},lock:function(){return j=b,c||l.disable(),this},locked:function(){return!j},fireWith:function(a,b){return b=b||[],b=[a,b.slice?b.slice():b],i&&(!d||j)&&(e?j.push(b):k(b)),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!d}};return l},p.extend({Deferred:function(a){var b=[["resolve","done",p.Callbacks("once memory"),"resolved"],["reject","fail",p.Callbacks("once memory"),"rejected"],["notify","progress",p.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return p.Deferred(function(c){p.each(b,function(b,d){var f=d[0],g=a[b];e[d[1]](p.isFunction(g)?function(){var a=g.apply(this,arguments);a&&p.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f+"With"](this===e?c:this,[a])}:c[f])}),a=null}).promise()},promise:function(a){return a!=null?p.extend(a,d):d}},e={};return d.pipe=d.then,p.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[a^1][2].disable,b[2][2].lock),e[f[0]]=g.fire,e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=k.call(arguments),d=c.length,e=d!==1||a&&p.isFunction(a.promise)?d:0,f=e===1?a:p.Deferred(),g=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?k.call(arguments):d,c===h?f.notifyWith(b,c):--e||f.resolveWith(b,c)}},h,i,j;if(d>1){h=new Array(d),i=new Array(d),j=new Array(d);for(;b
a",c=n.getElementsByTagName("*"),d=n.getElementsByTagName("a")[0],d.style.cssText="top:1px;float:left;opacity:.5";if(!c||!c.length)return{};f=e.createElement("select"),g=f.appendChild(e.createElement("option")),h=n.getElementsByTagName("input")[0],b={leadingWhitespace:n.firstChild.nodeType===3,tbody:!n.getElementsByTagName("tbody").length,htmlSerialize:!!n.getElementsByTagName("link").length,style:/top/.test(d.getAttribute("style")),hrefNormalized:d.getAttribute("href")==="/a",opacity:/^0.5/.test(d.style.opacity),cssFloat:!!d.style.cssFloat,checkOn:h.value==="on",optSelected:g.selected,getSetAttribute:n.className!=="t",enctype:!!e.createElement("form").enctype,html5Clone:e.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",boxModel:e.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},h.checked=!0,b.noCloneChecked=h.cloneNode(!0).checked,f.disabled=!0,b.optDisabled=!g.disabled;try{delete n.test}catch(o){b.deleteExpando=!1}!n.addEventListener&&n.attachEvent&&n.fireEvent&&(n.attachEvent("onclick",m=function(){b.noCloneEvent=!1}),n.cloneNode(!0).fireEvent("onclick"),n.detachEvent("onclick",m)),h=e.createElement("input"),h.value="t",h.setAttribute("type","radio"),b.radioValue=h.value==="t",h.setAttribute("checked","checked"),h.setAttribute("name","t"),n.appendChild(h),i=e.createDocumentFragment(),i.appendChild(n.lastChild),b.checkClone=i.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=h.checked,i.removeChild(h),i.appendChild(n);if(n.attachEvent)for(k in{submit:!0,change:!0,focusin:!0})j="on"+k,l=j in n,l||(n.setAttribute(j,"return;"),l=typeof n[j]=="function"),b[k+"Bubbles"]=l;return p(function(){var c,d,f,g,h="padding:0;margin:0;border:0;display:block;overflow:hidden;",i=e.getElementsByTagName("body")[0];if(!i)return;c=e.createElement("div"),c.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",i.insertBefore(c,i.firstChild),d=e.createElement("div"),c.appendChild(d),d.innerHTML="
t
",f=d.getElementsByTagName("td"),f[0].style.cssText="padding:0;margin:0;border:0;display:none",l=f[0].offsetHeight===0,f[0].style.display="",f[1].style.display="none",b.reliableHiddenOffsets=l&&f[0].offsetHeight===0,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",b.boxSizing=d.offsetWidth===4,b.doesNotIncludeMarginInBodyOffset=i.offsetTop!==1,a.getComputedStyle&&(b.pixelPosition=(a.getComputedStyle(d,null)||{}).top!=="1%",b.boxSizingReliable=(a.getComputedStyle(d,null)||{width:"4px"}).width==="4px",g=e.createElement("div"),g.style.cssText=d.style.cssText=h,g.style.marginRight=g.style.width="0",d.style.width="1px",d.appendChild(g),b.reliableMarginRight=!parseFloat((a.getComputedStyle(g,null)||{}).marginRight)),typeof d.style.zoom!="undefined"&&(d.innerHTML="",d.style.cssText=h+"width:1px;padding:1px;display:inline;zoom:1",b.inlineBlockNeedsLayout=d.offsetWidth===3,d.style.display="block",d.style.overflow="visible",d.innerHTML="
",d.firstChild.style.width="5px",b.shrinkWrapBlocks=d.offsetWidth!==3,c.style.zoom=1),i.removeChild(c),c=d=f=g=null}),i.removeChild(n),c=d=f=g=h=i=n=null,b}();var H=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,I=/([A-Z])/g;p.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(p.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){return a=a.nodeType?p.cache[a[p.expando]]:a[p.expando],!!a&&!K(a)},data:function(a,c,d,e){if(!p.acceptData(a))return;var f,g,h=p.expando,i=typeof c=="string",j=a.nodeType,k=j?p.cache:a,l=j?a[h]:a[h]&&h;if((!l||!k[l]||!e&&!k[l].data)&&i&&d===b)return;l||(j?a[h]=l=p.deletedIds.pop()||p.guid++:l=h),k[l]||(k[l]={},j||(k[l].toJSON=p.noop));if(typeof c=="object"||typeof c=="function")e?k[l]=p.extend(k[l],c):k[l].data=p.extend(k[l].data,c);return f=k[l],e||(f.data||(f.data={}),f=f.data),d!==b&&(f[p.camelCase(c)]=d),i?(g=f[c],g==null&&(g=f[p.camelCase(c)])):g=f,g},removeData:function(a,b,c){if(!p.acceptData(a))return;var d,e,f,g=a.nodeType,h=g?p.cache:a,i=g?a[p.expando]:p.expando;if(!h[i])return;if(b){d=c?h[i]:h[i].data;if(d){p.isArray(b)||(b in d?b=[b]:(b=p.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,f=b.length;e1,null,!1))},removeData:function(a){return this.each(function(){p.removeData(this,a)})}}),p.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=p._data(a,b),c&&(!d||p.isArray(c)?d=p._data(a,b,p.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=p.queue(a,b),d=c.length,e=c.shift(),f=p._queueHooks(a,b),g=function(){p.dequeue(a,b)};e==="inprogress"&&(e=c.shift(),d--),e&&(b==="fx"&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return p._data(a,c)||p._data(a,c,{empty:p.Callbacks("once memory").add(function(){p.removeData(a,b+"queue",!0),p.removeData(a,c,!0)})})}}),p.fn.extend({queue:function(a,c){var d=2;return typeof a!="string"&&(c=a,a="fx",d--),arguments.length1)},removeAttr:function(a){return this.each(function(){p.removeAttr(this,a)})},prop:function(a,b){return p.access(this,p.prop,a,b,arguments.length>1)},removeProp:function(a){return a=p.propFix[a]||a,this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,f,g,h;if(p.isFunction(a))return this.each(function(b){p(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(s);for(c=0,d=this.length;c=0)d=d.replace(" "+c[f]+" "," ");e.className=a?p.trim(d):""}}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";return p.isFunction(a)?this.each(function(c){p(this).toggleClass(a.call(this,c,this.className,b),b)}):this.each(function(){if(c==="string"){var e,f=0,g=p(this),h=b,i=a.split(s);while(e=i[f++])h=d?h:!g.hasClass(e),g[h?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&p._data(this,"__className__",this.className),this.className=this.className||a===!1?"":p._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c=0)return!0;return!1},val:function(a){var c,d,e,f=this[0];if(!arguments.length){if(f)return c=p.valHooks[f.type]||p.valHooks[f.nodeName.toLowerCase()],c&&"get"in c&&(d=c.get(f,"value"))!==b?d:(d=f.value,typeof d=="string"?d.replace(P,""):d==null?"":d);return}return e=p.isFunction(a),this.each(function(d){var f,g=p(this);if(this.nodeType!==1)return;e?f=a.call(this,d,g.val()):f=a,f==null?f="":typeof f=="number"?f+="":p.isArray(f)&&(f=p.map(f,function(a){return a==null?"":a+""})),c=p.valHooks[this.type]||p.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,f,"value")===b)this.value=f})}}),p.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,f=a.selectedIndex,g=[],h=a.options,i=a.type==="select-one";if(f<0)return null;c=i?f:0,d=i?f+1:h.length;for(;c=0}),c.length||(a.selectedIndex=-1),c}}},attrFn:{},attr:function(a,c,d,e){var f,g,h,i=a.nodeType;if(!a||i===3||i===8||i===2)return;if(e&&p.isFunction(p.fn[c]))return p(a)[c](d);if(typeof a.getAttribute=="undefined")return p.prop(a,c,d);h=i!==1||!p.isXMLDoc(a),h&&(c=c.toLowerCase(),g=p.attrHooks[c]||(T.test(c)?M:L));if(d!==b){if(d===null){p.removeAttr(a,c);return}return g&&"set"in g&&h&&(f=g.set(a,d,c))!==b?f:(a.setAttribute(c,d+""),d)}return g&&"get"in g&&h&&(f=g.get(a,c))!==null?f:(f=a.getAttribute(c),f===null?b:f)},removeAttr:function(a,b){var c,d,e,f,g=0;if(b&&a.nodeType===1){d=b.split(s);for(;g=0}})});var V=/^(?:textarea|input|select)$/i,W=/^([^\.]*|)(?:\.(.+)|)$/,X=/(?:^|\s)hover(\.\S+|)\b/,Y=/^key/,Z=/^(?:mouse|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=function(a){return p.event.special.hover?a:a.replace(X,"mouseenter$1 mouseleave$1")};p.event={add:function(a,c,d,e,f){var g,h,i,j,k,l,m,n,o,q,r;if(a.nodeType===3||a.nodeType===8||!c||!d||!(g=p._data(a)))return;d.handler&&(o=d,d=o.handler,f=o.selector),d.guid||(d.guid=p.guid++),i=g.events,i||(g.events=i={}),h=g.handle,h||(g.handle=h=function(a){return typeof p!="undefined"&&(!a||p.event.triggered!==a.type)?p.event.dispatch.apply(h.elem,arguments):b},h.elem=a),c=p.trim(_(c)).split(" ");for(j=0;j=0&&(s=s.slice(0,-1),i=!0),s.indexOf(".")>=0&&(t=s.split("."),s=t.shift(),t.sort());if((!f||p.event.customEvent[s])&&!p.event.global[s])return;c=typeof c=="object"?c[p.expando]?c:new p.Event(s,c):new p.Event(s),c.type=s,c.isTrigger=!0,c.exclusive=i,c.namespace=t.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+t.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,m=s.indexOf(":")<0?"on"+s:"";if(!f){h=p.cache;for(j in h)h[j].events&&h[j].events[s]&&p.event.trigger(c,d,h[j].handle.elem,!0);return}c.result=b,c.target||(c.target=f),d=d!=null?p.makeArray(d):[],d.unshift(c),n=p.event.special[s]||{};if(n.trigger&&n.trigger.apply(f,d)===!1)return;q=[[f,n.bindType||s]];if(!g&&!n.noBubble&&!p.isWindow(f)){r=n.delegateType||s,k=$.test(r+s)?f:f.parentNode;for(l=f;k;k=k.parentNode)q.push([k,r]),l=k;l===(f.ownerDocument||e)&&q.push([l.defaultView||l.parentWindow||a,r])}for(j=0;j=0:p.find(m,this,null,[f]).length),h[m]&&j.push(l);j.length&&u.push({elem:f,matches:j})}o.length>q&&u.push({elem:this,matches:o.slice(q)});for(d=0;d0?this.on(b,null,a,c):this.trigger(b)},Y.test(b)&&(p.event.fixHooks[b]=p.event.keyHooks),Z.test(b)&&(p.event.fixHooks[b]=p.event.mouseHooks)}),function(a,b){function bc(a,b,c,d){c=c||[],b=b||r;var e,f,i,j,k=b.nodeType;if(!a||typeof a!="string")return c;if(k!==1&&k!==9)return[];i=g(b);if(!i&&!d)if(e=P.exec(a))if(j=e[1]){if(k===9){f=b.getElementById(j);if(!f||!f.parentNode)return c;if(f.id===j)return c.push(f),c}else if(b.ownerDocument&&(f=b.ownerDocument.getElementById(j))&&h(b,f)&&f.id===j)return c.push(f),c}else{if(e[2])return w.apply(c,x.call(b.getElementsByTagName(a),0)),c;if((j=e[3])&&_&&b.getElementsByClassName)return w.apply(c,x.call(b.getElementsByClassName(j),0)),c}return bp(a.replace(L,"$1"),b,c,d,i)}function bd(a){return function(b){var c=b.nodeName.toLowerCase();return c==="input"&&b.type===a}}function be(a){return function(b){var c=b.nodeName.toLowerCase();return(c==="input"||c==="button")&&b.type===a}}function bf(a){return z(function(b){return b=+b,z(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function bg(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}function bh(a,b){var c,d,f,g,h,i,j,k=C[o][a];if(k)return b?0:k.slice(0);h=a,i=[],j=e.preFilter;while(h){if(!c||(d=M.exec(h)))d&&(h=h.slice(d[0].length)),i.push(f=[]);c=!1;if(d=N.exec(h))f.push(c=new q(d.shift())),h=h.slice(c.length),c.type=d[0].replace(L," ");for(g in e.filter)(d=W[g].exec(h))&&(!j[g]||(d=j[g](d,r,!0)))&&(f.push(c=new q(d.shift())),h=h.slice(c.length),c.type=g,c.matches=d);if(!c)break}return b?h.length:h?bc.error(a):C(a,i).slice(0)}function bi(a,b,d){var e=b.dir,f=d&&b.dir==="parentNode",g=u++;return b.first?function(b,c,d){while(b=b[e])if(f||b.nodeType===1)return a(b,c,d)}:function(b,d,h){if(!h){var i,j=t+" "+g+" ",k=j+c;while(b=b[e])if(f||b.nodeType===1){if((i=b[o])===k)return b.sizset;if(typeof i=="string"&&i.indexOf(j)===0){if(b.sizset)return b}else{b[o]=k;if(a(b,d,h))return b.sizset=!0,b;b.sizset=!1}}}else while(b=b[e])if(f||b.nodeType===1)if(a(b,d,h))return b}}function bj(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function bk(a,b,c,d,e){var f,g=[],h=0,i=a.length,j=b!=null;for(;h-1},h,!0),m=[function(a,c,d){return!g&&(d||c!==l)||((b=c).nodeType?j(a,c,d):k(a,c,d))}];for(;i1&&bj(m),i>1&&a.slice(0,i-1).join("").replace(L,"$1"),c,i0,f=a.length>0,g=function(h,i,j,k,m){var n,o,p,q=[],s=0,u="0",x=h&&[],y=m!=null,z=l,A=h||f&&e.find.TAG("*",m&&i.parentNode||i),B=t+=z==null?1:Math.E;y&&(l=i!==r&&i,c=g.el);for(;(n=A[u])!=null;u++){if(f&&n){for(o=0;p=a[o];o++)if(p(n,i,j)){k.push(n);break}y&&(t=B,c=++g.el)}d&&((n=!p&&n)&&s--,h&&x.push(n))}s+=u;if(d&&u!==s){for(o=0;p=b[o];o++)p(x,q,i,j);if(h){if(s>0)while(u--)!x[u]&&!q[u]&&(q[u]=v.call(k));q=bk(q)}w.apply(k,q),y&&!h&&q.length>0&&s+b.length>1&&bc.uniqueSort(k)}return y&&(t=B,l=z),x};return g.el=0,d?z(g):g}function bo(a,b,c,d){var e=0,f=b.length;for(;e2&&(j=h[0]).type==="ID"&&b.nodeType===9&&!f&&e.relative[h[1].type]){b=e.find.ID(j.matches[0].replace(V,""),b,f)[0];if(!b)return c;a=a.slice(h.shift().length)}for(g=W.POS.test(a)?-1:h.length-1;g>=0;g--){j=h[g];if(e.relative[k=j.type])break;if(l=e.find[k])if(d=l(j.matches[0].replace(V,""),R.test(h[0].type)&&b.parentNode||b,f)){h.splice(g,1),a=d.length&&h.join("");if(!a)return w.apply(c,x.call(d,0)),c;break}}}return i(a,m)(d,b,f,c,R.test(a)),c}function bq(){}var c,d,e,f,g,h,i,j,k,l,m=!0,n="undefined",o=("sizcache"+Math.random()).replace(".",""),q=String,r=a.document,s=r.documentElement,t=0,u=0,v=[].pop,w=[].push,x=[].slice,y=[].indexOf||function(a){var b=0,c=this.length;for(;be.cacheLength&&delete a[b.shift()],a[c]=d},a)},B=A(),C=A(),D=A(),E="[\\x20\\t\\r\\n\\f]",F="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",G=F.replace("w","w#"),H="([*^$|!~]?=)",I="\\["+E+"*("+F+")"+E+"*(?:"+H+E+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+G+")|)|)"+E+"*\\]",J=":("+F+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:"+I+")|[^:]|\\\\.)*|.*))\\)|)",K=":(even|odd|eq|gt|lt|nth|first|last)(?:\\("+E+"*((?:-\\d)?\\d*)"+E+"*\\)|)(?=[^-]|$)",L=new RegExp("^"+E+"+|((?:^|[^\\\\])(?:\\\\.)*)"+E+"+$","g"),M=new RegExp("^"+E+"*,"+E+"*"),N=new RegExp("^"+E+"*([\\x20\\t\\r\\n\\f>+~])"+E+"*"),O=new RegExp(J),P=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,Q=/^:not/,R=/[\x20\t\r\n\f]*[+~]/,S=/:not\($/,T=/h\d/i,U=/input|select|textarea|button/i,V=/\\(?!\\)/g,W={ID:new RegExp("^#("+F+")"),CLASS:new RegExp("^\\.("+F+")"),NAME:new RegExp("^\\[name=['\"]?("+F+")['\"]?\\]"),TAG:new RegExp("^("+F.replace("w","w*")+")"),ATTR:new RegExp("^"+I),PSEUDO:new RegExp("^"+J),POS:new RegExp(K,"i"),CHILD:new RegExp("^:(only|nth|first|last)-child(?:\\("+E+"*(even|odd|(([+-]|)(\\d*)n|)"+E+"*(?:([+-]|)"+E+"*(\\d+)|))"+E+"*\\)|)","i"),needsContext:new RegExp("^"+E+"*[>+~]|"+K,"i")},X=function(a){var b=r.createElement("div");try{return a(b)}catch(c){return!1}finally{b=null}},Y=X(function(a){return a.appendChild(r.createComment("")),!a.getElementsByTagName("*").length}),Z=X(function(a){return a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!==n&&a.firstChild.getAttribute("href")==="#"}),$=X(function(a){a.innerHTML="";var b=typeof a.lastChild.getAttribute("multiple");return b!=="boolean"&&b!=="string"}),_=X(function(a){return a.innerHTML="",!a.getElementsByClassName||!a.getElementsByClassName("e").length?!1:(a.lastChild.className="e",a.getElementsByClassName("e").length===2)}),ba=X(function(a){a.id=o+0,a.innerHTML="
",s.insertBefore(a,s.firstChild);var b=r.getElementsByName&&r.getElementsByName(o).length===2+r.getElementsByName(o+0).length;return d=!r.getElementById(o),s.removeChild(a),b});try{x.call(s.childNodes,0)[0].nodeType}catch(bb){x=function(a){var b,c=[];for(;b=this[a];a++)c.push(b);return c}}bc.matches=function(a,b){return bc(a,null,null,b)},bc.matchesSelector=function(a,b){return bc(b,null,null,[a]).length>0},f=bc.getText=function(a){var b,c="",d=0,e=a.nodeType;if(e){if(e===1||e===9||e===11){if(typeof a.textContent=="string")return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=f(a)}else if(e===3||e===4)return a.nodeValue}else for(;b=a[d];d++)c+=f(b);return c},g=bc.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?b.nodeName!=="HTML":!1},h=bc.contains=s.contains?function(a,b){var c=a.nodeType===9?a.documentElement:a,d=b&&b.parentNode;return a===d||!!(d&&d.nodeType===1&&c.contains&&c.contains(d))}:s.compareDocumentPosition?function(a,b){return b&&!!(a.compareDocumentPosition(b)&16)}:function(a,b){while(b=b.parentNode)if(b===a)return!0;return!1},bc.attr=function(a,b){var c,d=g(a);return d||(b=b.toLowerCase()),(c=e.attrHandle[b])?c(a):d||$?a.getAttribute(b):(c=a.getAttributeNode(b),c?typeof a[b]=="boolean"?a[b]?b:null:c.specified?c.value:null:null)},e=bc.selectors={cacheLength:50,createPseudo:z,match:W,attrHandle:Z?{}:{href:function(a){return a.getAttribute("href",2)},type:function(a){return a.getAttribute("type")}},find:{ID:d?function(a,b,c){if(typeof b.getElementById!==n&&!c){var d=b.getElementById(a);return d&&d.parentNode?[d]:[]}}:function(a,c,d){if(typeof c.getElementById!==n&&!d){var e=c.getElementById(a);return e?e.id===a||typeof e.getAttributeNode!==n&&e.getAttributeNode("id").value===a?[e]:b:[]}},TAG:Y?function(a,b){if(typeof b.getElementsByTagName!==n)return b.getElementsByTagName(a)}:function(a,b){var c=b.getElementsByTagName(a);if(a==="*"){var d,e=[],f=0;for(;d=c[f];f++)d.nodeType===1&&e.push(d);return e}return c},NAME:ba&&function(a,b){if(typeof b.getElementsByName!==n)return b.getElementsByName(name)},CLASS:_&&function(a,b,c){if(typeof b.getElementsByClassName!==n&&!c)return b.getElementsByClassName(a)}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(V,""),a[3]=(a[4]||a[5]||"").replace(V,""),a[2]==="~="&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),a[1]==="nth"?(a[2]||bc.error(a[0]),a[3]=+(a[3]?a[4]+(a[5]||1):2*(a[2]==="even"||a[2]==="odd")),a[4]=+(a[6]+a[7]||a[2]==="odd")):a[2]&&bc.error(a[0]),a},PSEUDO:function(a){var b,c;if(W.CHILD.test(a[0]))return null;if(a[3])a[2]=a[3];else if(b=a[4])O.test(b)&&(c=bh(b,!0))&&(c=b.indexOf(")",b.length-c)-b.length)&&(b=b.slice(0,c),a[0]=a[0].slice(0,c)),a[2]=b;return a.slice(0,3)}},filter:{ID:d?function(a){return a=a.replace(V,""),function(b){return b.getAttribute("id")===a}}:function(a){return a=a.replace(V,""),function(b){var c=typeof b.getAttributeNode!==n&&b.getAttributeNode("id");return c&&c.value===a}},TAG:function(a){return a==="*"?function(){return!0}:(a=a.replace(V,"").toLowerCase(),function(b){return b.nodeName&&b.nodeName.toLowerCase()===a})},CLASS:function(a){var b=B[o][a];return b||(b=B(a,new RegExp("(^|"+E+")"+a+"("+E+"|$)"))),function(a){return b.test(a.className||typeof a.getAttribute!==n&&a.getAttribute("class")||"")}},ATTR:function(a,b,c){return function(d,e){var f=bc.attr(d,a);return f==null?b==="!=":b?(f+="",b==="="?f===c:b==="!="?f!==c:b==="^="?c&&f.indexOf(c)===0:b==="*="?c&&f.indexOf(c)>-1:b==="$="?c&&f.substr(f.length-c.length)===c:b==="~="?(" "+f+" ").indexOf(c)>-1:b==="|="?f===c||f.substr(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d){return a==="nth"?function(a){var b,e,f=a.parentNode;if(c===1&&d===0)return!0;if(f){e=0;for(b=f.firstChild;b;b=b.nextSibling)if(b.nodeType===1){e++;if(a===b)break}}return e-=d,e===c||e%c===0&&e/c>=0}:function(b){var c=b;switch(a){case"only":case"first":while(c=c.previousSibling)if(c.nodeType===1)return!1;if(a==="first")return!0;c=b;case"last":while(c=c.nextSibling)if(c.nodeType===1)return!1;return!0}}},PSEUDO:function(a,b){var c,d=e.pseudos[a]||e.setFilters[a.toLowerCase()]||bc.error("unsupported pseudo: "+a);return d[o]?d(b):d.length>1?(c=[a,a,"",b],e.setFilters.hasOwnProperty(a.toLowerCase())?z(function(a,c){var e,f=d(a,b),g=f.length;while(g--)e=y.call(a,f[g]),a[e]=!(c[e]=f[g])}):function(a){return d(a,0,c)}):d}},pseudos:{not:z(function(a){var b=[],c=[],d=i(a.replace(L,"$1"));return d[o]?z(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)if(f=g[h])a[h]=!(b[h]=f)}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:z(function(a){return function(b){return bc(a,b).length>0}}),contains:z(function(a){return function(b){return(b.textContent||b.innerText||f(b)).indexOf(a)>-1}}),enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&!!a.checked||b==="option"&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},parent:function(a){return!e.pseudos.empty(a)},empty:function(a){var b;a=a.firstChild;while(a){if(a.nodeName>"@"||(b=a.nodeType)===3||b===4)return!1;a=a.nextSibling}return!0},header:function(a){return T.test(a.nodeName)},text:function(a){var b,c;return a.nodeName.toLowerCase()==="input"&&(b=a.type)==="text"&&((c=a.getAttribute("type"))==null||c.toLowerCase()===b)},radio:bd("radio"),checkbox:bd("checkbox"),file:bd("file"),password:bd("password"),image:bd("image"),submit:be("submit"),reset:be("reset"),button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&a.type==="button"||b==="button"},input:function(a){return U.test(a.nodeName)},focus:function(a){var b=a.ownerDocument;return a===b.activeElement&&(!b.hasFocus||b.hasFocus())&&(!!a.type||!!a.href)},active:function(a){return a===a.ownerDocument.activeElement},first:bf(function(a,b,c){return[0]}),last:bf(function(a,b,c){return[b-1]}),eq:bf(function(a,b,c){return[c<0?c+b:c]}),even:bf(function(a,b,c){for(var d=0;d=0;)a.push(d);return a}),gt:bf(function(a,b,c){for(var d=c<0?c+b:c;++d",a.querySelectorAll("[selected]").length||e.push("\\["+E+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),a.querySelectorAll(":checked").length||e.push(":checked")}),X(function(a){a.innerHTML="

",a.querySelectorAll("[test^='']").length&&e.push("[*^$]="+E+"*(?:\"\"|'')"),a.innerHTML="",a.querySelectorAll(":enabled").length||e.push(":enabled",":disabled")}),e=new RegExp(e.join("|")),bp=function(a,d,f,g,h){if(!g&&!h&&(!e||!e.test(a))){var i,j,k=!0,l=o,m=d,n=d.nodeType===9&&a;if(d.nodeType===1&&d.nodeName.toLowerCase()!=="object"){i=bh(a),(k=d.getAttribute("id"))?l=k.replace(c,"\\$&"):d.setAttribute("id",l),l="[id='"+l+"'] ",j=i.length;while(j--)i[j]=l+i[j].join("");m=R.test(a)&&d.parentNode||d,n=i.join(",")}if(n)try{return w.apply(f,x.call(m.querySelectorAll(n),0)),f}catch(p){}finally{k||d.removeAttribute("id")}}return b(a,d,f,g,h)},h&&(X(function(b){a=h.call(b,"div");try{h.call(b,"[test!='']:sizzle"),f.push("!=",J)}catch(c){}}),f=new RegExp(f.join("|")),bc.matchesSelector=function(b,c){c=c.replace(d,"='$1']");if(!g(b)&&!f.test(c)&&(!e||!e.test(c)))try{var i=h.call(b,c);if(i||a||b.document&&b.document.nodeType!==11)return i}catch(j){}return bc(c,null,null,[b]).length>0})}(),e.pseudos.nth=e.pseudos.eq,e.filters=bq.prototype=e.pseudos,e.setFilters=new bq,bc.attr=p.attr,p.find=bc,p.expr=bc.selectors,p.expr[":"]=p.expr.pseudos,p.unique=bc.uniqueSort,p.text=bc.getText,p.isXMLDoc=bc.isXML,p.contains=bc.contains}(a);var bc=/Until$/,bd=/^(?:parents|prev(?:Until|All))/,be=/^.[^:#\[\.,]*$/,bf=p.expr.match.needsContext,bg={children:!0,contents:!0,next:!0,prev:!0};p.fn.extend({find:function(a){var b,c,d,e,f,g,h=this;if(typeof a!="string")return p(a).filter(function(){for(b=0,c=h.length;b0)for(e=d;e=0:p.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c,d=0,e=this.length,f=[],g=bf.test(a)||typeof a!="string"?p(a,b||this.context):0;for(;d-1:p.find.matchesSelector(c,a)){f.push(c);break}c=c.parentNode}}return f=f.length>1?p.unique(f):f,this.pushStack(f,"closest",a)},index:function(a){return a?typeof a=="string"?p.inArray(this[0],p(a)):p.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(a,b){var c=typeof a=="string"?p(a,b):p.makeArray(a&&a.nodeType?[a]:a),d=p.merge(this.get(),c);return this.pushStack(bh(c[0])||bh(d[0])?d:p.unique(d))},addBack:function(a){return this.add(a==null?this.prevObject:this.prevObject.filter(a))}}),p.fn.andSelf=p.fn.addBack,p.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return p.dir(a,"parentNode")},parentsUntil:function(a,b,c){return p.dir(a,"parentNode",c)},next:function(a){return bi(a,"nextSibling")},prev:function(a){return bi(a,"previousSibling")},nextAll:function(a){return p.dir(a,"nextSibling")},prevAll:function(a){return p.dir(a,"previousSibling")},nextUntil:function(a,b,c){return p.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return p.dir(a,"previousSibling",c)},siblings:function(a){return p.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return p.sibling(a.firstChild)},contents:function(a){return p.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:p.merge([],a.childNodes)}},function(a,b){p.fn[a]=function(c,d){var e=p.map(this,b,c);return bc.test(a)||(d=c),d&&typeof d=="string"&&(e=p.filter(d,e)),e=this.length>1&&!bg[a]?p.unique(e):e,this.length>1&&bd.test(a)&&(e=e.reverse()),this.pushStack(e,a,k.call(arguments).join(","))}}),p.extend({filter:function(a,b,c){return c&&(a=":not("+a+")"),b.length===1?p.find.matchesSelector(b[0],a)?[b[0]]:[]:p.find.matches(a,b)},dir:function(a,c,d){var e=[],f=a[c];while(f&&f.nodeType!==9&&(d===b||f.nodeType!==1||!p(f).is(d)))f.nodeType===1&&e.push(f),f=f[c];return e},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var bl="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",bm=/ jQuery\d+="(?:null|\d+)"/g,bn=/^\s+/,bo=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bp=/<([\w:]+)/,bq=/]","i"),bv=/^(?:checkbox|radio)$/,bw=/checked\s*(?:[^=]|=\s*.checked.)/i,bx=/\/(java|ecma)script/i,by=/^\s*\s*$/g,bz={option:[1,""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]},bA=bk(e),bB=bA.appendChild(e.createElement("div"));bz.optgroup=bz.option,bz.tbody=bz.tfoot=bz.colgroup=bz.caption=bz.thead,bz.th=bz.td,p.support.htmlSerialize||(bz._default=[1,"X
","
"]),p.fn.extend({text:function(a){return p.access(this,function(a){return a===b?p.text(this):this.empty().append((this[0]&&this[0].ownerDocument||e).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(p.isFunction(a))return this.each(function(b){p(this).wrapAll(a.call(this,b))});if(this[0]){var b=p(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){return p.isFunction(a)?this.each(function(b){p(this).wrapInner(a.call(this,b))}):this.each(function(){var b=p(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=p.isFunction(a);return this.each(function(c){p(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){p.nodeName(this,"body")||p(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(a,this.firstChild)})},before:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(a,this),"before",this.selector)}},after:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(this,a),"after",this.selector)}},remove:function(a,b){var c,d=0;for(;(c=this[d])!=null;d++)if(!a||p.filter(a,[c]).length)!b&&c.nodeType===1&&(p.cleanData(c.getElementsByTagName("*")),p.cleanData([c])),c.parentNode&&c.parentNode.removeChild(c);return this},empty:function(){var a,b=0;for(;(a=this[b])!=null;b++){a.nodeType===1&&p.cleanData(a.getElementsByTagName("*"));while(a.firstChild)a.removeChild(a.firstChild)}return this},clone:function(a,b){return a=a==null?!1:a,b=b==null?a:b,this.map(function(){return p.clone(this,a,b)})},html:function(a){return p.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(bm,""):b;if(typeof a=="string"&&!bs.test(a)&&(p.support.htmlSerialize||!bu.test(a))&&(p.support.leadingWhitespace||!bn.test(a))&&!bz[(bp.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(bo,"<$1>");try{for(;d1&&typeof j=="string"&&bw.test(j))return this.each(function(){p(this).domManip(a,c,d)});if(p.isFunction(j))return this.each(function(e){var f=p(this);a[0]=j.call(this,e,c?f.html():b),f.domManip(a,c,d)});if(this[0]){e=p.buildFragment(a,this,k),g=e.fragment,f=g.firstChild,g.childNodes.length===1&&(g=f);if(f){c=c&&p.nodeName(f,"tr");for(h=e.cacheable||l-1;i0?this.clone(!0):this).get(),p(g[e])[b](d),f=f.concat(d);return this.pushStack(f,a,g.selector)}}),p.extend({clone:function(a,b,c){var d,e,f,g;p.support.html5Clone||p.isXMLDoc(a)||!bu.test("<"+a.nodeName+">")?g=a.cloneNode(!0):(bB.innerHTML=a.outerHTML,bB.removeChild(g=bB.firstChild));if((!p.support.noCloneEvent||!p.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!p.isXMLDoc(a)){bE(a,g),d=bF(a),e=bF(g);for(f=0;d[f];++f)e[f]&&bE(d[f],e[f])}if(b){bD(a,g);if(c){d=bF(a),e=bF(g);for(f=0;d[f];++f)bD(d[f],e[f])}}return d=e=null,g},clean:function(a,b,c,d){var f,g,h,i,j,k,l,m,n,o,q,r,s=b===e&&bA,t=[];if(!b||typeof b.createDocumentFragment=="undefined")b=e;for(f=0;(h=a[f])!=null;f++){typeof h=="number"&&(h+="");if(!h)continue;if(typeof h=="string")if(!br.test(h))h=b.createTextNode(h);else{s=s||bk(b),l=b.createElement("div"),s.appendChild(l),h=h.replace(bo,"<$1>"),i=(bp.exec(h)||["",""])[1].toLowerCase(),j=bz[i]||bz._default,k=j[0],l.innerHTML=j[1]+h+j[2];while(k--)l=l.lastChild;if(!p.support.tbody){m=bq.test(h),n=i==="table"&&!m?l.firstChild&&l.firstChild.childNodes:j[1]===""&&!m?l.childNodes:[];for(g=n.length-1;g>=0;--g)p.nodeName(n[g],"tbody")&&!n[g].childNodes.length&&n[g].parentNode.removeChild(n[g])}!p.support.leadingWhitespace&&bn.test(h)&&l.insertBefore(b.createTextNode(bn.exec(h)[0]),l.firstChild),h=l.childNodes,l.parentNode.removeChild(l)}h.nodeType?t.push(h):p.merge(t,h)}l&&(h=l=s=null);if(!p.support.appendChecked)for(f=0;(h=t[f])!=null;f++)p.nodeName(h,"input")?bG(h):typeof h.getElementsByTagName!="undefined"&&p.grep(h.getElementsByTagName("input"),bG);if(c){q=function(a){if(!a.type||bx.test(a.type))return d?d.push(a.parentNode?a.parentNode.removeChild(a):a):c.appendChild(a)};for(f=0;(h=t[f])!=null;f++)if(!p.nodeName(h,"script")||!q(h))c.appendChild(h),typeof h.getElementsByTagName!="undefined"&&(r=p.grep(p.merge([],h.getElementsByTagName("script")),q),t.splice.apply(t,[f+1,0].concat(r)),f+=r.length)}return t},cleanData:function(a,b){var c,d,e,f,g=0,h=p.expando,i=p.cache,j=p.support.deleteExpando,k=p.event.special;for(;(e=a[g])!=null;g++)if(b||p.acceptData(e)){d=e[h],c=d&&i[d];if(c){if(c.events)for(f in c.events)k[f]?p.event.remove(e,f):p.removeEvent(e,f,c.handle);i[d]&&(delete i[d],j?delete e[h]:e.removeAttribute?e.removeAttribute(h):e[h]=null,p.deletedIds.push(d))}}}}),function(){var a,b;p.uaMatch=function(a){a=a.toLowerCase();var b=/(chrome)[ \/]([\w.]+)/.exec(a)||/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||a.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},a=p.uaMatch(g.userAgent),b={},a.browser&&(b[a.browser]=!0,b.version=a.version),b.chrome?b.webkit=!0:b.webkit&&(b.safari=!0),p.browser=b,p.sub=function(){function a(b,c){return new a.fn.init(b,c)}p.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function c(c,d){return d&&d instanceof p&&!(d instanceof a)&&(d=a(d)),p.fn.init.call(this,c,d,b)},a.fn.init.prototype=a.fn;var b=a(e);return a}}();var bH,bI,bJ,bK=/alpha\([^)]*\)/i,bL=/opacity=([^)]*)/,bM=/^(top|right|bottom|left)$/,bN=/^(none|table(?!-c[ea]).+)/,bO=/^margin/,bP=new RegExp("^("+q+")(.*)$","i"),bQ=new RegExp("^("+q+")(?!px)[a-z%]+$","i"),bR=new RegExp("^([-+])=("+q+")","i"),bS={},bT={position:"absolute",visibility:"hidden",display:"block"},bU={letterSpacing:0,fontWeight:400},bV=["Top","Right","Bottom","Left"],bW=["Webkit","O","Moz","ms"],bX=p.fn.toggle;p.fn.extend({css:function(a,c){return p.access(this,function(a,c,d){return d!==b?p.style(a,c,d):p.css(a,c)},a,c,arguments.length>1)},show:function(){return b$(this,!0)},hide:function(){return b$(this)},toggle:function(a,b){var c=typeof a=="boolean";return p.isFunction(a)&&p.isFunction(b)?bX.apply(this,arguments):this.each(function(){(c?a:bZ(this))?p(this).show():p(this).hide()})}}),p.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bH(a,"opacity");return c===""?"1":c}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":p.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!a||a.nodeType===3||a.nodeType===8||!a.style)return;var f,g,h,i=p.camelCase(c),j=a.style;c=p.cssProps[i]||(p.cssProps[i]=bY(j,i)),h=p.cssHooks[c]||p.cssHooks[i];if(d===b)return h&&"get"in h&&(f=h.get(a,!1,e))!==b?f:j[c];g=typeof d,g==="string"&&(f=bR.exec(d))&&(d=(f[1]+1)*f[2]+parseFloat(p.css(a,c)),g="number");if(d==null||g==="number"&&isNaN(d))return;g==="number"&&!p.cssNumber[i]&&(d+="px");if(!h||!("set"in h)||(d=h.set(a,d,e))!==b)try{j[c]=d}catch(k){}},css:function(a,c,d,e){var f,g,h,i=p.camelCase(c);return c=p.cssProps[i]||(p.cssProps[i]=bY(a.style,i)),h=p.cssHooks[c]||p.cssHooks[i],h&&"get"in h&&(f=h.get(a,!0,e)),f===b&&(f=bH(a,c)),f==="normal"&&c in bU&&(f=bU[c]),d||e!==b?(g=parseFloat(f),d||p.isNumeric(g)?g||0:f):f},swap:function(a,b,c){var d,e,f={};for(e in b)f[e]=a.style[e],a.style[e]=b[e];d=c.call(a);for(e in b)a.style[e]=f[e];return d}}),a.getComputedStyle?bH=function(b,c){var d,e,f,g,h=a.getComputedStyle(b,null),i=b.style;return h&&(d=h[c],d===""&&!p.contains(b.ownerDocument,b)&&(d=p.style(b,c)),bQ.test(d)&&bO.test(c)&&(e=i.width,f=i.minWidth,g=i.maxWidth,i.minWidth=i.maxWidth=i.width=d,d=h.width,i.width=e,i.minWidth=f,i.maxWidth=g)),d}:e.documentElement.currentStyle&&(bH=function(a,b){var c,d,e=a.currentStyle&&a.currentStyle[b],f=a.style;return e==null&&f&&f[b]&&(e=f[b]),bQ.test(e)&&!bM.test(b)&&(c=f.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":e,e=f.pixelLeft+"px",f.left=c,d&&(a.runtimeStyle.left=d)),e===""?"auto":e}),p.each(["height","width"],function(a,b){p.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth===0&&bN.test(bH(a,"display"))?p.swap(a,bT,function(){return cb(a,b,d)}):cb(a,b,d)},set:function(a,c,d){return b_(a,c,d?ca(a,b,d,p.support.boxSizing&&p.css(a,"boxSizing")==="border-box"):0)}}}),p.support.opacity||(p.cssHooks.opacity={get:function(a,b){return bL.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=p.isNumeric(b)?"alpha(opacity="+b*100+")":"",f=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&p.trim(f.replace(bK,""))===""&&c.removeAttribute){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bK.test(f)?f.replace(bK,e):f+" "+e}}),p(function(){p.support.reliableMarginRight||(p.cssHooks.marginRight={get:function(a,b){return p.swap(a,{display:"inline-block"},function(){if(b)return bH(a,"marginRight")})}}),!p.support.pixelPosition&&p.fn.position&&p.each(["top","left"],function(a,b){p.cssHooks[b]={get:function(a,c){if(c){var d=bH(a,b);return bQ.test(d)?p(a).position()[b]+"px":d}}}})}),p.expr&&p.expr.filters&&(p.expr.filters.hidden=function(a){return a.offsetWidth===0&&a.offsetHeight===0||!p.support.reliableHiddenOffsets&&(a.style&&a.style.display||bH(a,"display"))==="none"},p.expr.filters.visible=function(a){return!p.expr.filters.hidden(a)}),p.each({margin:"",padding:"",border:"Width"},function(a,b){p.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bV[d]+b]=e[d]||e[d-2]||e[0];return f}},bO.test(a)||(p.cssHooks[a+b].set=b_)});var cd=/%20/g,ce=/\[\]$/,cf=/\r?\n/g,cg=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,ch=/^(?:select|textarea)/i;p.fn.extend({serialize:function(){return p.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?p.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ch.test(this.nodeName)||cg.test(this.type))}).map(function(a,b){var c=p(this).val();return c==null?null:p.isArray(c)?p.map(c,function(a,c){return{name:b.name,value:a.replace(cf,"\r\n")}}):{name:b.name,value:c.replace(cf,"\r\n")}}).get()}}),p.param=function(a,c){var d,e=[],f=function(a,b){b=p.isFunction(b)?b():b==null?"":b,e[e.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=p.ajaxSettings&&p.ajaxSettings.traditional);if(p.isArray(a)||a.jquery&&!p.isPlainObject(a))p.each(a,function(){f(this.name,this.value)});else for(d in a)ci(d,a[d],c,f);return e.join("&").replace(cd,"+")};var cj,ck,cl=/#.*$/,cm=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,cn=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,co=/^(?:GET|HEAD)$/,cp=/^\/\//,cq=/\?/,cr=/)<[^<]*)*<\/script>/gi,cs=/([?&])_=[^&]*/,ct=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,cu=p.fn.load,cv={},cw={},cx=["*/"]+["*"];try{ck=f.href}catch(cy){ck=e.createElement("a"),ck.href="",ck=ck.href}cj=ct.exec(ck.toLowerCase())||[],p.fn.load=function(a,c,d){if(typeof a!="string"&&cu)return cu.apply(this,arguments);if(!this.length)return this;var e,f,g,h=this,i=a.indexOf(" ");return i>=0&&(e=a.slice(i,a.length),a=a.slice(0,i)),p.isFunction(c)?(d=c,c=b):c&&typeof c=="object"&&(f="POST"),p.ajax({url:a,type:f,dataType:"html",data:c,complete:function(a,b){d&&h.each(d,g||[a.responseText,b,a])}}).done(function(a){g=arguments,h.html(e?p("
").append(a.replace(cr,"")).find(e):a)}),this},p.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){p.fn[b]=function(a){return this.on(b,a)}}),p.each(["get","post"],function(a,c){p[c]=function(a,d,e,f){return p.isFunction(d)&&(f=f||e,e=d,d=b),p.ajax({type:c,url:a,data:d,success:e,dataType:f})}}),p.extend({getScript:function(a,c){return p.get(a,b,c,"script")},getJSON:function(a,b,c){return p.get(a,b,c,"json")},ajaxSetup:function(a,b){return b?cB(a,p.ajaxSettings):(b=a,a=p.ajaxSettings),cB(a,b),a},ajaxSettings:{url:ck,isLocal:cn.test(cj[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":cx},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":p.parseJSON,"text xml":p.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:cz(cv),ajaxTransport:cz(cw),ajax:function(a,c){function y(a,c,f,i){var k,s,t,u,w,y=c;if(v===2)return;v=2,h&&clearTimeout(h),g=b,e=i||"",x.readyState=a>0?4:0,f&&(u=cC(l,x,f));if(a>=200&&a<300||a===304)l.ifModified&&(w=x.getResponseHeader("Last-Modified"),w&&(p.lastModified[d]=w),w=x.getResponseHeader("Etag"),w&&(p.etag[d]=w)),a===304?(y="notmodified",k=!0):(k=cD(l,u),y=k.state,s=k.data,t=k.error,k=!t);else{t=y;if(!y||a)y="error",a<0&&(a=0)}x.status=a,x.statusText=(c||y)+"",k?o.resolveWith(m,[s,y,x]):o.rejectWith(m,[x,y,t]),x.statusCode(r),r=b,j&&n.trigger("ajax"+(k?"Success":"Error"),[x,l,k?s:t]),q.fireWith(m,[x,y]),j&&(n.trigger("ajaxComplete",[x,l]),--p.active||p.event.trigger("ajaxStop"))}typeof a=="object"&&(c=a,a=b),c=c||{};var d,e,f,g,h,i,j,k,l=p.ajaxSetup({},c),m=l.context||l,n=m!==l&&(m.nodeType||m instanceof p)?p(m):p.event,o=p.Deferred(),q=p.Callbacks("once memory"),r=l.statusCode||{},t={},u={},v=0,w="canceled",x={readyState:0,setRequestHeader:function(a,b){if(!v){var c=a.toLowerCase();a=u[c]=u[c]||a,t[a]=b}return this},getAllResponseHeaders:function(){return v===2?e:null},getResponseHeader:function(a){var c;if(v===2){if(!f){f={};while(c=cm.exec(e))f[c[1].toLowerCase()]=c[2]}c=f[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){return v||(l.mimeType=a),this},abort:function(a){return a=a||w,g&&g.abort(a),y(0,a),this}};o.promise(x),x.success=x.done,x.error=x.fail,x.complete=q.add,x.statusCode=function(a){if(a){var b;if(v<2)for(b in a)r[b]=[r[b],a[b]];else b=a[x.status],x.always(b)}return this},l.url=((a||l.url)+"").replace(cl,"").replace(cp,cj[1]+"//"),l.dataTypes=p.trim(l.dataType||"*").toLowerCase().split(s),l.crossDomain==null&&(i=ct.exec(l.url.toLowerCase())||!1,l.crossDomain=i&&i.join(":")+(i[3]?"":i[1]==="http:"?80:443)!==cj.join(":")+(cj[3]?"":cj[1]==="http:"?80:443)),l.data&&l.processData&&typeof l.data!="string"&&(l.data=p.param(l.data,l.traditional)),cA(cv,l,c,x);if(v===2)return x;j=l.global,l.type=l.type.toUpperCase(),l.hasContent=!co.test(l.type),j&&p.active++===0&&p.event.trigger("ajaxStart");if(!l.hasContent){l.data&&(l.url+=(cq.test(l.url)?"&":"?")+l.data,delete l.data),d=l.url;if(l.cache===!1){var z=p.now(),A=l.url.replace(cs,"$1_="+z);l.url=A+(A===l.url?(cq.test(l.url)?"&":"?")+"_="+z:"")}}(l.data&&l.hasContent&&l.contentType!==!1||c.contentType)&&x.setRequestHeader("Content-Type",l.contentType),l.ifModified&&(d=d||l.url,p.lastModified[d]&&x.setRequestHeader("If-Modified-Since",p.lastModified[d]),p.etag[d]&&x.setRequestHeader("If-None-Match",p.etag[d])),x.setRequestHeader("Accept",l.dataTypes[0]&&l.accepts[l.dataTypes[0]]?l.accepts[l.dataTypes[0]]+(l.dataTypes[0]!=="*"?", "+cx+"; q=0.01":""):l.accepts["*"]);for(k in l.headers)x.setRequestHeader(k,l.headers[k]);if(!l.beforeSend||l.beforeSend.call(m,x,l)!==!1&&v!==2){w="abort";for(k in{success:1,error:1,complete:1})x[k](l[k]);g=cA(cw,l,c,x);if(!g)y(-1,"No Transport");else{x.readyState=1,j&&n.trigger("ajaxSend",[x,l]),l.async&&l.timeout>0&&(h=setTimeout(function(){x.abort("timeout")},l.timeout));try{v=1,g.send(t,y)}catch(B){if(v<2)y(-1,B);else throw B}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var cE=[],cF=/\?/,cG=/(=)\?(?=&|$)|\?\?/,cH=p.now();p.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=cE.pop()||p.expando+"_"+cH++;return this[a]=!0,a}}),p.ajaxPrefilter("json jsonp",function(c,d,e){var f,g,h,i=c.data,j=c.url,k=c.jsonp!==!1,l=k&&cG.test(j),m=k&&!l&&typeof i=="string"&&!(c.contentType||"").indexOf("application/x-www-form-urlencoded")&&cG.test(i);if(c.dataTypes[0]==="jsonp"||l||m)return f=c.jsonpCallback=p.isFunction(c.jsonpCallback)?c.jsonpCallback():c.jsonpCallback,g=a[f],l?c.url=j.replace(cG,"$1"+f):m?c.data=i.replace(cG,"$1"+f):k&&(c.url+=(cF.test(j)?"&":"?")+c.jsonp+"="+f),c.converters["script json"]=function(){return h||p.error(f+" was not called"),h[0]},c.dataTypes[0]="json",a[f]=function(){h=arguments},e.always(function(){a[f]=g,c[f]&&(c.jsonpCallback=d.jsonpCallback,cE.push(f)),h&&p.isFunction(g)&&g(h[0]),h=g=b}),"script"}),p.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){return p.globalEval(a),a}}}),p.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),p.ajaxTransport("script",function(a){if(a.crossDomain){var c,d=e.head||e.getElementsByTagName("head")[0]||e.documentElement;return{send:function(f,g){c=e.createElement("script"),c.async="async",a.scriptCharset&&(c.charset=a.scriptCharset),c.src=a.url,c.onload=c.onreadystatechange=function(a,e){if(e||!c.readyState||/loaded|complete/.test(c.readyState))c.onload=c.onreadystatechange=null,d&&c.parentNode&&d.removeChild(c),c=b,e||g(200,"success")},d.insertBefore(c,d.firstChild)},abort:function(){c&&c.onload(0,1)}}}});var cI,cJ=a.ActiveXObject?function(){for(var a in cI)cI[a](0,1)}:!1,cK=0;p.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&cL()||cM()}:cL,function(a){p.extend(p.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(p.ajaxSettings.xhr()),p.support.ajax&&p.ajaxTransport(function(c){if(!c.crossDomain||p.support.cors){var d;return{send:function(e,f){var g,h,i=c.xhr();c.username?i.open(c.type,c.url,c.async,c.username,c.password):i.open(c.type,c.url,c.async);if(c.xhrFields)for(h in c.xhrFields)i[h]=c.xhrFields[h];c.mimeType&&i.overrideMimeType&&i.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(h in e)i.setRequestHeader(h,e[h])}catch(j){}i.send(c.hasContent&&c.data||null),d=function(a,e){var h,j,k,l,m;try{if(d&&(e||i.readyState===4)){d=b,g&&(i.onreadystatechange=p.noop,cJ&&delete cI[g]);if(e)i.readyState!==4&&i.abort();else{h=i.status,k=i.getAllResponseHeaders(),l={},m=i.responseXML,m&&m.documentElement&&(l.xml=m);try{l.text=i.responseText}catch(a){}try{j=i.statusText}catch(n){j=""}!h&&c.isLocal&&!c.crossDomain?h=l.text?200:404:h===1223&&(h=204)}}}catch(o){e||f(-1,o)}l&&f(h,j,l,k)},c.async?i.readyState===4?setTimeout(d,0):(g=++cK,cJ&&(cI||(cI={},p(a).unload(cJ)),cI[g]=d),i.onreadystatechange=d):d()},abort:function(){d&&d(0,1)}}}});var cN,cO,cP=/^(?:toggle|show|hide)$/,cQ=new RegExp("^(?:([-+])=|)("+q+")([a-z%]*)$","i"),cR=/queueHooks$/,cS=[cY],cT={"*":[function(a,b){var c,d,e=this.createTween(a,b),f=cQ.exec(b),g=e.cur(),h=+g||0,i=1,j=20;if(f){c=+f[2],d=f[3]||(p.cssNumber[a]?"":"px");if(d!=="px"&&h){h=p.css(e.elem,a,!0)||c||1;do i=i||".5",h=h/i,p.style(e.elem,a,h+d);while(i!==(i=e.cur()/g)&&i!==1&&--j)}e.unit=d,e.start=h,e.end=f[1]?h+(f[1]+1)*c:c}return e}]};p.Animation=p.extend(cW,{tweener:function(a,b){p.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");var c,d=0,e=a.length;for(;d-1,j={},k={},l,m;i?(k=e.position(),l=k.top,m=k.left):(l=parseFloat(g)||0,m=parseFloat(h)||0),p.isFunction(b)&&(b=b.call(a,c,f)),b.top!=null&&(j.top=b.top-f.top+l),b.left!=null&&(j.left=b.left-f.left+m),"using"in b?b.using.call(a,j):e.css(j)}},p.fn.extend({position:function(){if(!this[0])return;var a=this[0],b=this.offsetParent(),c=this.offset(),d=c_.test(b[0].nodeName)?{top:0,left:0}:b.offset();return c.top-=parseFloat(p.css(a,"marginTop"))||0,c.left-=parseFloat(p.css(a,"marginLeft"))||0,d.top+=parseFloat(p.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(p.css(b[0],"borderLeftWidth"))||0,{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||e.body;while(a&&!c_.test(a.nodeName)&&p.css(a,"position")==="static")a=a.offsetParent;return a||e.body})}}),p.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);p.fn[a]=function(e){return p.access(this,function(a,e,f){var g=da(a);if(f===b)return g?c in g?g[c]:g.document.documentElement[e]:a[e];g?g.scrollTo(d?p(g).scrollLeft():f,d?f:p(g).scrollTop()):a[e]=f},a,e,arguments.length,null)}}),p.each({Height:"height",Width:"width"},function(a,c){p.each({padding:"inner"+a,content:c,"":"outer"+a},function(d,e){p.fn[e]=function(e,f){var g=arguments.length&&(d||typeof e!="boolean"),h=d||(e===!0||f===!0?"margin":"border");return p.access(this,function(c,d,e){var f;return p.isWindow(c)?c.document.documentElement["client"+a]:c.nodeType===9?(f=c.documentElement,Math.max(c.body["scroll"+a],f["scroll"+a],c.body["offset"+a],f["offset"+a],f["client"+a])):e===b?p.css(c,d,e,h):p.style(c,d,e,h)},c,g?e:b,g,null)}})}),a.jQuery=a.$=p,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return p})})(window); diff --git a/public/js/vendor/processing-1.1.0.js b/public/js/vendor/processing-1.1.0.js new file mode 100644 index 0000000..bc35ab6 --- /dev/null +++ b/public/js/vendor/processing-1.1.0.js @@ -0,0 +1,18736 @@ +/* + + P R O C E S S I N G . J S - 1.1.0 + a port of the Processing visualization language + + License : MIT + Developer : John Resig: http://ejohn.org + Web Site : http://processingjs.org + Java Version : http://processing.org + Github Repo. : http://github.com/jeresig/processing-js + Bug Tracking : http://processing-js.lighthouseapp.com + Mozilla POW! : http://wiki.Mozilla.org/Education/Projects/ProcessingForTheWeb + Maintained by : Seneca: http://zenit.senecac.on.ca/wiki/index.php/Processing.js + Hyper-Metrix: http://hyper-metrix.com/#Processing + BuildingSky: http://weare.buildingsky.net/pages/processing-js + + */ + +(function() { + + var undef; // intentionally left undefined + + var ajax = function ajax(url) { + var xhr = new XMLHttpRequest(); + xhr.open("GET", url, false); + if (xhr.overrideMimeType) { + xhr.overrideMimeType("text/plain"); + } + xhr.setRequestHeader("If-Modified-Since", "Fri, 01 Jan 1960 00:00:00 GMT"); + xhr.send(null); + // failed request? + if (xhr.status !== 200 && xhr.status !== 0) { throw ("XMLHttpRequest failed, status code " + xhr.status); } + return xhr.responseText; + }; + + var isDOMPresent = ("document" in this) && !("fake" in this.document); + + /* Browsers fixes start */ + function fixReplaceByRegExp() { + var re = /t/g; + if ("t".replace(re,"") !== null && re.exec("t")) { + return; // it is not necessary + } + var _ie_replace = String.prototype.replace; + String.prototype.replace = function(searchValue, repaceValue) { + var result = _ie_replace.apply(this, arguments); + if (searchValue instanceof RegExp && searchValue.global) { + searchValue.lastIndex = 0; + } + return result; + }; + } + + function fixMatchByRegExp() { + var re = /t/g; + if ("t".match(re) !== null && re.exec("t")) { + return; // it is not necessary + } + var _ie_match = String.prototype.match; + String.prototype.match = function(searchValue) { + var result = _ie_match.apply(this, arguments); + if(searchValue instanceof RegExp && searchValue.global) { + searchValue.lastIndex = 0; + } + return result; + }; + } + fixReplaceByRegExp(); + fixMatchByRegExp(); + + (function fixOperaCreateImageData() { + try { + if (!("createImageData" in CanvasRenderingContext2D.prototype)) { + CanvasRenderingContext2D.prototype.createImageData = function (sw, sh) { + return new ImageData(sw, sh); + }; + } + } catch(e) {} + }()); + /* Browsers fixes end */ + + var PConstants = { + X: 0, + Y: 1, + Z: 2, + + R: 3, + G: 4, + B: 5, + A: 6, + + U: 7, + V: 8, + + NX: 9, + NY: 10, + NZ: 11, + + EDGE: 12, + + // Stroke + SR: 13, + SG: 14, + SB: 15, + SA: 16, + + SW: 17, + + // Transformations (2D and 3D) + TX: 18, + TY: 19, + TZ: 20, + + VX: 21, + VY: 22, + VZ: 23, + VW: 24, + + // Material properties + AR: 25, + AG: 26, + AB: 27, + + DR: 3, + DG: 4, + DB: 5, + DA: 6, + + SPR: 28, + SPG: 29, + SPB: 30, + + SHINE: 31, + + ER: 32, + EG: 33, + EB: 34, + + BEEN_LIT: 35, + + VERTEX_FIELD_COUNT: 36, + + // Renderers + P2D: 1, + JAVA2D: 1, + WEBGL: 2, + P3D: 2, + OPENGL: 2, + PDF: 0, + DXF: 0, + + // Platform IDs + OTHER: 0, + WINDOWS: 1, + MAXOSX: 2, + LINUX: 3, + + EPSILON: 0.0001, + + MAX_FLOAT: 3.4028235e+38, + MIN_FLOAT: -3.4028235e+38, + MAX_INT: 2147483647, + MIN_INT: -2147483648, + + PI: Math.PI, + TWO_PI: 2 * Math.PI, + HALF_PI: Math.PI / 2, + THIRD_PI: Math.PI / 3, + QUARTER_PI: Math.PI / 4, + + 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, + INS: 45, + DEL: 46, + 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, + + // 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: parseInt(360 / 0.5, 10), + 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 + }; + + // Typed Arrays: fallback to WebGL arrays or Native JS arrays if unavailable + function setupTypedArray(name, fallback) { + // check if TypedArray exists + // typeof on Minefield and Chrome return function, typeof on Webkit returns object. + if (typeof this[name] !== "function" && typeof this[name] !== "object") { + // nope.. check if WebGLArray exists + if (typeof this[fallback] === "function") { + this[name] = this[fallback]; + } else { + // nope.. set as Native JS array + this[name] = function(obj) { + if (obj instanceof Array) { + return obj; + } else if (typeof obj === "number") { + var arr = []; + arr.length = obj; + return arr; + } + }; + } + } + } + + setupTypedArray("Float32Array", "WebGLFloatArray"); + setupTypedArray("Int32Array", "WebGLIntArray"); + setupTypedArray("Uint16Array", "WebGLUnsignedShortArray"); + setupTypedArray("Uint8Array", "WebGLUnsignedByteArray"); + + /** + * Returns Java hashCode() result for the object. If the object has the "hashCode" function, + * it preforms the call of this function. Otherwise it uses/creates the "$id" property, + * which is used as the hashCode. + * + * @param {Object} obj The object. + * @returns {int} The object's hash code. + */ + function virtHashCode(obj) { + if (obj.constructor === String) { + var hash = 0; + for (var i = 0; i < obj.length; ++i) { + hash = (hash * 31 + obj.charCodeAt(i)) & 0xFFFFFFFF; + } + return hash; + } else if (typeof(obj) !== "object") { + return obj & 0xFFFFFFFF; + } else if (obj.hashCode instanceof Function) { + return obj.hashCode(); + } else { + if (obj.$id === undef) { + obj.$id = ((Math.floor(Math.random() * 0x10000) - 0x8000) << 16) | Math.floor(Math.random() * 0x10000); + } + return obj.$id; + } + } + + /** + * Returns Java equals() result for two objects. If the first object + * has the "equals" function, it preforms the call of this function. + * Otherwise the method uses the JavaScript === operator. + * + * @param {Object} obj The first object. + * @param {Object} other The second object. + * + * @returns {boolean} true if the objects are equal. + */ + function virtEquals(obj, other) { + if (obj === null || other === null) { + return (obj === null) && (other === null); + } else if (obj.constructor === String) { + return obj === other; + } else if (typeof(obj) !== "object") { + return obj === other; + } else if (obj.equals instanceof Function) { + return obj.equals(other); + } else { + return obj === other; + } + } + + /** + * An ArrayList stores a variable number of objects. + * + * @param {int} initialCapacity optional defines the initial capacity of the list, it's empty by default + * + * @returns {ArrayList} new ArrayList object + */ + var ArrayList = (function() { + function Iterator(array) { + var index = 0; + this.hasNext = function() { + return index < array.length; + }; + + this.next = function() { + return array[index++]; + }; + + this.remove = function() { + array.splice(index, 1); + }; + } + + function ArrayList() { + var array; + if (arguments.length === 0) { + array = []; + } else if (arguments.length > 0 && typeof arguments[0] !== 'number') { + array = arguments[0]; + } else { + array = []; + array.length = 0 | arguments[0]; + } + + /** + * @member ArrayList + * ArrayList.get() Returns the element at the specified position in this list. + * + * @param {int} i index of element to return + * + * @returns {Object} the element at the specified position in this list. + */ + this.get = function(i) { + return array[i]; + }; + /** + * @member ArrayList + * ArrayList.contains() Returns true if this list contains the specified element. + * + * @param {Object} item element whose presence in this List is to be tested. + * + * @returns {boolean} true if the specified element is present; false otherwise. + */ + this.contains = function(item) { + for (var i = 0, len = array.length; i < len; ++i) { + if (virtEquals(item, array[i])) { + return true; + } + } + return false; + }; + /** + * @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.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 the element at the specified position in this list. + * Shifts any subsequent elements to the left (subtracts one from their indices). + * + * @param {int} index the index of the element to removed. + * + * @returns {Object} the element that was removed from the list + */ + this.remove = function(i) { + return array.splice(i, 1)[0]; + }; + + /** + * @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(array.slice(0)); + }; + + /** + * @member ArrayList + * ArrayList.toArray() Returns an array containing all of the elements in this list in the correct order. + * + * @returns {Object[]} Returns an array containing all of the elements in this list in the correct order + */ + this.toArray = function() { + return array.slice(0); + }; + + this.iterator = function() { + return new Iterator(array); + }; + } + + return ArrayList; + }()); + + /** + * A HashMap stores a collection of objects, each referenced by a key. This is similar to an Array, only + * instead of accessing elements with a numeric index, a String is used. (If you are familiar with + * associative arrays from other languages, this is the same idea.) + * + * @param {int} initialCapacity defines the initial capacity of the map, it's 16 by default + * @param {float} loadFactor the load factor for the map, the default is 0.75 + * @param {Map} m gives the new HashMap the same mappings as this Map + */ + var HashMap = (function() { + /** + * @member HashMap + * A HashMap stores a collection of objects, each referenced by a key. This is similar to an Array, only + * instead of accessing elements with a numeric index, a String is used. (If you are familiar with + * associative arrays from other languages, this is the same idea.) + * + * @param {int} initialCapacity defines the initial capacity of the map, it's 16 by default + * @param {float} loadFactor the load factor for the map, the default is 0.75 + * @param {Map} m gives the new HashMap the same mappings as this Map + */ + function HashMap() { + if (arguments.length === 1 && arguments[0].constructor === 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 ensureLoad() { + if (count <= loadFactor * buckets.length) { + return; + } + var allEntries = []; + for (var i = 0; i < buckets.length; ++i) { + if (buckets[i] !== undef) { + allEntries = allEntries.concat(buckets[i]); + } + } + buckets = []; + buckets.length = buckets.length * 2; + for (var j = 0; j < allEntries.length; ++j) { + var index = virtHashCode(allEntries[j].key) % buckets.length; + var bucket = buckets[index]; + if (bucket === undef) { + buckets[index] = bucket = []; + } + bucket.push(allEntries[j]); + } + } + + function Iterator(conversion, removeItem) { + var bucketIndex = 0; + var itemIndex = -1; + var endOfBuckets = false; + + function findNext() { + while (!endOfBuckets) { + ++itemIndex; + if (bucketIndex >= buckets.length) { + endOfBuckets = true; + } else if (buckets[bucketIndex] === undef || itemIndex >= buckets[bucketIndex].length) { + itemIndex = -1; + ++bucketIndex; + } else { + return; + } + } + } + + /* + * @member Iterator + * Checks if the Iterator has more items + */ + this.hasNext = function() { + return !endOfBuckets; + }; + + /* + * @member Iterator + * Return the next Item + */ + this.next = function() { + var result = conversion(buckets[bucketIndex][itemIndex]); + findNext(); + return result; + }; + + /* + * @member Iterator + * Remove the current item + */ + this.remove = function() { + removeItem(this.next()); + --itemIndex; + }; + + findNext(); + } + + function Set(conversion, isIn, removeItem) { + this.clear = function() { + hashMap.clear(); + }; + + this.contains = function(o) { + return isIn(o); + }; + + this.containsAll = function(o) { + var it = o.iterator(); + while (it.hasNext()) { + if (!this.contains(it.next())) { + return false; + } + } + return true; + }; + + this.isEmpty = function() { + return hashMap.isEmpty(); + }; + + this.iterator = function() { + return new Iterator(conversion, removeItem); + }; + + this.remove = function(o) { + if (this.contains(o)) { + removeItem(o); + return true; + } + return false; + }; + + this.removeAll = function(c) { + var it = c.iterator(); + var changed = false; + while (it.hasNext()) { + var item = it.next(); + if (this.contains(item)) { + removeItem(item); + changed = true; + } + } + return true; + }; + + this.retainAll = function(c) { + var it = this.iterator(); + var toRemove = []; + while (it.hasNext()) { + var entry = it.next(); + if (!c.contains(entry)) { + toRemove.push(entry); + } + } + for (var i = 0; i < toRemove.length; ++i) { + removeItem(toRemove[i]); + } + return toRemove.length > 0; + }; + + this.size = function() { + return hashMap.size(); + }; + + this.toArray = function() { + var result = []; + var it = this.iterator(); + while (it.hasNext()) { + result.push(it.next()); + } + return result; + }; + } + + function Entry(pair) { + this._isIn = function(map) { + return map === hashMap && (pair.removed === undef); + }; + + this.equals = function(o) { + return virtEquals(pair.key, o.getKey()); + }; + + this.getKey = function() { + return pair.key; + }; + + this.getValue = function() { + return pair.value; + }; + + this.hashCode = function(o) { + return virtHashCode(pair.key); + }; + + this.setValue = function(value) { + var old = pair.value; + pair.value = value; + return old; + }; + } + + this.clear = function() { + count = 0; + buckets = []; + buckets.length = initialCapacity; + }; + + this.clone = function() { + var map = new HashMap(); + map.putAll(this); + return map; + }; + + this.containsKey = function(key) { + var index = virtHashCode(key) % buckets.length; + var bucket = buckets[index]; + if (bucket === undef) { + return false; + } + for (var i = 0; i < bucket.length; ++i) { + if (virtEquals(bucket[i].key, key)) { + return true; + } + } + return false; + }; + + this.containsValue = function(value) { + for (var i = 0; i < buckets.length; ++i) { + var bucket = buckets[i]; + if (bucket === undef) { + continue; + } + for (var j = 0; j < bucket.length; ++j) { + if (virtEquals(bucket[j].value, value)) { + return true; + } + } + } + return false; + }; + + this.entrySet = function() { + return new Set( + + function(pair) { + return new Entry(pair); + }, + + function(pair) { + return pair.constructor === Entry && pair._isIn(hashMap); + }, + + function(pair) { + return hashMap.remove(pair.getKey()); + }); + }; + + this.get = function(key) { + var index = virtHashCode(key) % buckets.length; + var bucket = buckets[index]; + if (bucket === undef) { + return null; + } + for (var i = 0; i < bucket.length; ++i) { + if (virtEquals(bucket[i].key, key)) { + return bucket[i].value; + } + } + return null; + }; + + this.isEmpty = function() { + return count === 0; + }; + + this.keySet = function() { + return new Set( + + function(pair) { + return pair.key; + }, + + function(key) { + return hashMap.containsKey(key); + }, + + function(key) { + return hashMap.remove(key); + }); + }; + + this.put = function(key, value) { + var index = virtHashCode(key) % buckets.length; + var bucket = buckets[index]; + if (bucket === undef) { + ++count; + buckets[index] = [{ + key: key, + value: value + }]; + ensureLoad(); + return null; + } + for (var i = 0; i < bucket.length; ++i) { + if (virtEquals(bucket[i].key, key)) { + var previous = bucket[i].value; + bucket[i].value = value; + return previous; + } + } + ++count; + bucket.push({ + key: key, + value: value + }); + ensureLoad(); + return null; + }; + + this.putAll = function(m) { + var it = m.entrySet().iterator(); + while (it.hasNext()) { + var entry = it.next(); + this.put(entry.getKey(), entry.getValue()); + } + }; + + this.remove = function(key) { + var index = virtHashCode(key) % buckets.length; + var bucket = buckets[index]; + if (bucket === undef) { + return null; + } + for (var i = 0; i < bucket.length; ++i) { + if (virtEquals(bucket[i].key, key)) { + --count; + var previous = bucket[i].value; + bucket[i].removed = true; + if (bucket.length > 1) { + bucket.splice(i, 1); + } else { + buckets[index] = undef; + } + return previous; + } + } + return null; + }; + + this.size = function() { + return count; + }; + + this.values = function() { + var result = []; + var it = this.entrySet().iterator(); + while (it.hasNext()) { + var entry = it.next(); + result.push(entry.getValue()); + } + return result; + }; + } + + return HashMap; + }()); + + var PVector = (function() { + function PVector(x, y, z) { + this.x = x || 0; + this.y = y || 0; + this.z = z || 0; + } + + function createPVectorMethod(method) { + return function(v1, v2) { + var v = v1.get(); + v[method](v2); + return v; + }; + } + + function createSimplePVectorMethod(method) { + return function(v1, v2) { + return v1[method](v2); + }; + } + + var simplePVMethods = "dist dot cross".split(" "); + var method = simplePVMethods.length; + + PVector.angleBetween = function(v1, v2) { + return Math.acos(v1.dot(v2) / (v1.mag() * v2.mag())); + }; + + // Common vector operations for PVector + PVector.prototype = { + set: function(v, y, z) { + if (arguments.length === 1) { + this.set(v.x || v[0], v.y || v[1], v.z || v[2]); + } else { + this.x = v; + this.y = y; + this.z = z; + } + }, + get: function() { + return new PVector(this.x, this.y, this.z); + }, + mag: function() { + return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); + }, + add: function(v, y, z) { + if (arguments.length === 3) { + this.x += v; + this.y += y; + this.z += z; + } else if (arguments.length === 1) { + this.x += v.x; + this.y += v.y; + this.z += v.z; + } + }, + sub: function(v, y, z) { + if (arguments.length === 3) { + this.x -= v; + this.y -= y; + this.z -= z; + } else if (arguments.length === 1) { + this.x -= v.x; + this.y -= v.y; + this.z -= v.z; + } + }, + mult: function(v) { + if (typeof v === 'number') { + this.x *= v; + this.y *= v; + this.z *= v; + } else if (typeof v === 'object') { + 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 if (typeof v === 'object') { + this.x /= v.x; + this.y /= v.y; + this.z /= v.z; + } + }, + 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 === 3) { + return (this.x * v + this.y * y + this.z * z); + } else if (arguments.length === 1) { + return (this.x * v.x + this.y * v.y + this.z * v.z); + } + }, + cross: function(v) { + return new PVector(this.y * v.z - v.y * this.z, + this.z * v.x - v.z * this.x, + this.x * v.y - v.x * this.y); + }, + normalize: function() { + var m = this.mag(); + if (m > 0) { + this.div(m); + } + }, + limit: function(high) { + if (this.mag() > high) { + this.normalize(); + this.mult(high); + } + }, + heading2D: function() { + return (-Math.atan2(-this.y, this.x)); + }, + toString: function() { + return "[" + this.x + ", " + this.y + ", " + this.z + "]"; + }, + array: function() { + return [this.x, this.y, this.z]; + } + }; + + while (method--) { + PVector[simplePVMethods[method]] = createSimplePVectorMethod(simplePVMethods[method]); + } + + for (method in PVector.prototype) { + if (PVector.prototype.hasOwnProperty(method) && !PVector.hasOwnProperty(method)) { + PVector[method] = createPVectorMethod(method); + } + } + + return PVector; + }()); + + /** + * A ObjectIterator is an iterator wrapper for objects. If passed object contains + * the iterator method, the object instance will be replaced by the result returned by + * this method call. If passed object is an array, the ObjectIterator instance iterates + * through its items. + * + * @param {Object} obj The object to be iterated. + */ + var ObjectIterator = function(obj) { + if (obj.iterator instanceof Function) { + return obj.iterator(); + } else if (obj instanceof Array) { + // iterate through array items + var index = -1; + this.hasNext = function() { + return ++index < obj.length; + }; + this.next = function() { + return obj[index]; + }; + } else { + throw "Unable to iterate: " + obj; + } + }; + + // Building defaultScope. Changing of the prototype protects + // internal Processing code from the changes in defaultScope + function DefaultScope() {} + DefaultScope.prototype = PConstants; + + var defaultScope = new DefaultScope(); + defaultScope.ArrayList = ArrayList; + defaultScope.HashMap = HashMap; + defaultScope.PVector = PVector; + defaultScope.ObjectIterator = ObjectIterator; + //defaultScope.PImage = PImage; // TODO + //defaultScope.PShape = PShape; // TODO + //defaultScope.PShapeSVG = PShapeSVG; // TODO + + var Processing = this.Processing = function Processing(curElement, aCode) { + // Previously we allowed calling Processing as a func instead of ctor, but no longer. + if (!(this instanceof Processing)) { + throw("called Processing constructor as if it were a function: missing 'new'."); + } + + // When something new is added to "p." it must also be added to the "names" array. + // The names array contains the names of everything that is inside "p." + var p = this; + + // PJS specific (non-p5) methods and properties to externalize + p.externals = { + canvas: curElement, + context: undef, + sketch: undef + }; + + p.name = 'Processing.js Instance'; // Set Processing defaults / environment variables + p.use3DContext = false; // default '2d' canvas context + + /** + * Confirms if a Processing program is "focused", meaning that it is + * active and will accept input from mouse or keyboard. This variable + * is "true" if it is focused and "false" if not. This variable is + * often used when you want to warn people they need to click on the + * browser before it will work. + */ + p.focused = false; + p.breakShape = false; + + // Glyph path storage for textFonts + p.glyphTable = {}; + + // Global vars for tracking mouse position + p.pmouseX = 0; + p.pmouseY = 0; + p.mouseX = 0; + p.mouseY = 0; + p.mouseButton = 0; + p.mouseScroll = 0; + + // Undefined event handlers to be replaced by user when needed + p.mouseClicked = undef; + p.mouseDragged = undef; + p.mouseMoved = undef; + p.mousePressed = undef; + p.mouseReleased = undef; + p.mouseScrolled = undef; + p.mouseOver = undef; + p.mouseOut = undef; + p.touchStart = undef; + p.touchEnd = undef; + p.touchMove = undef; + p.touchCancel = undef; + p.key = undef; + p.keyCode = undef; + p.keyPressed = function(){}; // needed to remove function checks + p.keyReleased = function(){}; + p.keyTyped = function(){}; + p.draw = undef; + p.setup = undef; + + // Remapped vars + p.__mousePressed = false; + p.__keyPressed = false; + p.__frameRate = 0; + + // The current animation frame + p.frameCount = 0; + + // The height/width of the canvas + p.width = curElement.width - 0; + p.height = curElement.height - 0; + + p.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); + } + } + }; + + // "Private" variables used to maintain state + var curContext, + curSketch, + online = true, + doFill = true, + fillStyle = [1.0, 1.0, 1.0, 1.0], + currentFillColor = 0xFFFFFFFF, + isFillDirty = true, + doStroke = true, + strokeStyle = [0.8, 0.8, 0.8, 1.0], + currentStrokeColor = 0xFFFDFDFD, + isStrokeDirty = true, + lineWidth = 1, + loopStarted = false, + doLoop = true, + looping = 0, + curRectMode = PConstants.CORNER, + curEllipseMode = PConstants.CENTER, + normalX = 0, + normalY = 0, + normalZ = 0, + normalMode = PConstants.NORMAL_MODE_AUTO, + inDraw = false, + curFrameRate = 60, + curCursor = PConstants.ARROW, + oldCursor = curElement.style.cursor, + curMsPerFrame = 1, + curShape = PConstants.POLYGON, + curShapeCount = 0, + curvePoints = [], + curTightness = 0, + curveDet = 20, + curveInited = false, + bezDetail = 20, + colorModeA = 255, + colorModeX = 255, + colorModeY = 255, + colorModeZ = 255, + pathOpen = false, + mouseDragging = false, + curColorMode = PConstants.RGB, + curTint = null, + curTextSize = 12, + curTextFont = {name: "\"Arial\", sans-serif", origName: "Arial"}, + curTextLeading = 14, + getLoaded = false, + start = new Date().getTime(), + timeSinceLastFPS = start, + framesSinceLastFPS = 0, + textcanvas, + curveBasisMatrix, + curveToBezierMatrix, + curveDrawMatrix, + bezierDrawMatrix, + bezierBasisInverse, + bezierBasisMatrix, + // Keys and Keystrokes + firstCodedDown = true, // first coded key stroke + firstEDGKeyDown = true, // first Enter - Delete Google key stroke + firstEDMKeyDown = true, // first Enter - Delete Mozilla key stroke + firstMKeyDown = true, // first Mozilla key stroke + firstGKeyDown = true, // first Google key stroke + gRefire = false, // Google refire + 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, + baselineOffset = 0.2, // percent + tMode = PConstants.MODEL, + // Pixels cache + originalContext, + proxyContext = null, + isContextReplaced = false, + setPixelsCached, + maxPixelsCached = 1000, + 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.INS, PConstants.F1, PConstants.F2, PConstants.F3, PConstants.F4, PConstants.F5, PConstants.F6, PConstants.F7, + PConstants.F8, PConstants.F9, PConstants.F10, PConstants.F11, PConstants.F12 ]; + + // Get padding and border style widths for mouse offsets + var stylePaddingLeft, stylePaddingTop, styleBorderLeft, styleBorderTop; + + if (document.defaultView && document.defaultView.getComputedStyle) { + stylePaddingLeft = parseInt(document.defaultView.getComputedStyle(curElement, null)['paddingLeft'], 10) || 0; + stylePaddingTop = parseInt(document.defaultView.getComputedStyle(curElement, null)['paddingTop'], 10) || 0; + styleBorderLeft = parseInt(document.defaultView.getComputedStyle(curElement, null)['borderLeftWidth'], 10) || 0; + styleBorderTop = parseInt(document.defaultView.getComputedStyle(curElement, null)['borderTopWidth'], 10) || 0; + } + + // User can only have MAX_LIGHTS lights + var lightCount = 0; + + //sphere stuff + var sphereDetailV = 0, + sphereDetailU = 0, + sphereX = [], + sphereY = [], + sphereZ = [], + sinLUT = new Float32Array(PConstants.SINCOS_LENGTH), + cosLUT = new Float32Array(PConstants.SINCOS_LENGTH), + sphereVerts, + sphereNorms; + + // Camera defaults and settings + var cam, + cameraInv, + forwardTransform, + reverseTransform, + modelView, + modelViewInv, + userMatrixStack, + inverseCopy, + projection, + manipulatingCamera = false, + frustumMode = false, + cameraFOV = 60 * (Math.PI / 180), + cameraX = curElement.width / 2, + cameraY = curElement.height / 2, + cameraZ = cameraY / Math.tan(cameraFOV / 2), + cameraNear = cameraZ / 10, + cameraFar = cameraZ * 10, + cameraAspect = curElement.width / curElement.height; + + var vertArray = [], + curveVertArray = [], + curveVertCount = 0, + isCurve = false, + isBezier = false, + firstVert = true; + + //PShape stuff + var curShapeMode = PConstants.CORNER; + + var colors = { + aliceblue: "#f0f8ff", + antiquewhite: "#faebd7", + aqua: "#00ffff", + aquamarine: "#7fffd4", + azure: "#f0ffff", + beige: "#f5f5dc", + bisque: "#ffe4c4", + black: "#000000", + blanchedalmond: "#ffebcd", + blue: "#0000ff", + blueviolet: "#8a2be2", + brown: "#a52a2a", + burlywood: "#deb887", + cadetblue: "#5f9ea0", + chartreuse: "#7fff00", + chocolate: "#d2691e", + coral: "#ff7f50", + cornflowerblue: "#6495ed", + cornsilk: "#fff8dc", + crimson: "#dc143c", + cyan: "#00ffff", + darkblue: "#00008b", + darkcyan: "#008b8b", + darkgoldenrod: "#b8860b", + darkgray: "#a9a9a9", + darkgreen: "#006400", + darkkhaki: "#bdb76b", + darkmagenta: "#8b008b", + darkolivegreen: "#556b2f", + darkorange: "#ff8c00", + darkorchid: "#9932cc", + darkred: "#8b0000", + darksalmon: "#e9967a", + darkseagreen: "#8fbc8f", + darkslateblue: "#483d8b", + darkslategray: "#2f4f4f", + darkturquoise: "#00ced1", + darkviolet: "#9400d3", + deeppink: "#ff1493", + deepskyblue: "#00bfff", + dimgray: "#696969", + dodgerblue: "#1e90ff", + firebrick: "#b22222", + floralwhite: "#fffaf0", + forestgreen: "#228b22", + fuchsia: "#ff00ff", + gainsboro: "#dcdcdc", + ghostwhite: "#f8f8ff", + gold: "#ffd700", + goldenrod: "#daa520", + gray: "#808080", + green: "#008000", + greenyellow: "#adff2f", + honeydew: "#f0fff0", + hotpink: "#ff69b4", + indianred: "#cd5c5c", + indigo: "#4b0082", + ivory: "#fffff0", + khaki: "#f0e68c", + lavender: "#e6e6fa", + lavenderblush: "#fff0f5", + lawngreen: "#7cfc00", + lemonchiffon: "#fffacd", + lightblue: "#add8e6", + lightcoral: "#f08080", + lightcyan: "#e0ffff", + lightgoldenrodyellow: "#fafad2", + lightgrey: "#d3d3d3", + lightgreen: "#90ee90", + lightpink: "#ffb6c1", + lightsalmon: "#ffa07a", + lightseagreen: "#20b2aa", + lightskyblue: "#87cefa", + lightslategray: "#778899", + lightsteelblue: "#b0c4de", + lightyellow: "#ffffe0", + lime: "#00ff00", + limegreen: "#32cd32", + linen: "#faf0e6", + magenta: "#ff00ff", + maroon: "#800000", + mediumaquamarine: "#66cdaa", + mediumblue: "#0000cd", + mediumorchid: "#ba55d3", + mediumpurple: "#9370d8", + mediumseagreen: "#3cb371", + mediumslateblue: "#7b68ee", + mediumspringgreen: "#00fa9a", + mediumturquoise: "#48d1cc", + mediumvioletred: "#c71585", + midnightblue: "#191970", + mintcream: "#f5fffa", + mistyrose: "#ffe4e1", + moccasin: "#ffe4b5", + navajowhite: "#ffdead", + navy: "#000080", + oldlace: "#fdf5e6", + olive: "#808000", + olivedrab: "#6b8e23", + orange: "#ffa500", + orangered: "#ff4500", + orchid: "#da70d6", + palegoldenrod: "#eee8aa", + palegreen: "#98fb98", + paleturquoise: "#afeeee", + palevioletred: "#d87093", + papayawhip: "#ffefd5", + peachpuff: "#ffdab9", + peru: "#cd853f", + pink: "#ffc0cb", + plum: "#dda0dd", + powderblue: "#b0e0e6", + purple: "#800080", + red: "#ff0000", + rosybrown: "#bc8f8f", + royalblue: "#4169e1", + saddlebrown: "#8b4513", + salmon: "#fa8072", + sandybrown: "#f4a460", + seagreen: "#2e8b57", + seashell: "#fff5ee", + sienna: "#a0522d", + silver: "#c0c0c0", + skyblue: "#87ceeb", + slateblue: "#6a5acd", + slategray: "#708090", + snow: "#fffafa", + springgreen: "#00ff7f", + steelblue: "#4682b4", + tan: "#d2b48c", + teal: "#008080", + thistle: "#d8bfd8", + tomato: "#ff6347", + turquoise: "#40e0d0", + violet: "#ee82ee", + wheat: "#f5deb3", + white: "#ffffff", + whitesmoke: "#f5f5f5", + yellow: "#ffff00", + yellowgreen: "#9acd32" + }; + + // Stores states for pushStyle() and popStyle(). + var styleArray = []; + + // Vertices are specified in a counter-clockwise order + // triangles are in this order: back, front, right, bottom, left, top + var boxVerts = new Float32Array([ + 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, + 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, + 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, + 0.5, -0.5, -0.5, 0.5, -0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, + -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, -0.5, -0.5, + 0.5, 0.5, 0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5, 0.5, 0.5]); + + var boxOutlineVerts = new Float32Array([ + 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, + -0.5, 0.5, -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, + 0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5, + -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, 0.5, + 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, -0.5, -0.5, -0.5, + -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5]); + + var boxNorms = new Float32Array([ + 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, + 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, + 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, + 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, + -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, + 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0]); + + // These verts are used for the fill and stroke using TRIANGLE_FAN and LINE_LOOP + var rectVerts = new Float32Array([0,0,0, 0,1,0, 1,1,0, 1,0,0]); + + var rectNorms = new Float32Array([0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1]); + + // Vertex shader for points and lines + var vShaderSrcUnlitShape = + "varying vec4 frontColor;" + + + "attribute vec3 aVertex;" + + "attribute vec4 aColor;" + + + "uniform mat4 uView;" + + "uniform mat4 uProjection;" + + + "void main(void) {" + + " frontColor = aColor;" + + " gl_Position = uProjection * uView * vec4(aVertex, 1.0);" + + "}"; + + var fShaderSrcUnlitShape = + "#ifdef GL_ES\n" + + "precision highp float;\n" + + "#endif\n" + + + "varying vec4 frontColor;" + + + "void main(void){" + + " gl_FragColor = frontColor;" + + "}"; + + // Vertex shader for points and lines + var vertexShaderSource2D = + "varying vec4 frontColor;" + + + "attribute vec3 Vertex;" + + "attribute vec2 aTextureCoord;" + + "uniform vec4 color;" + + + "uniform mat4 model;" + + "uniform mat4 view;" + + "uniform mat4 projection;" + + "uniform float pointSize;" + + "varying vec2 vTextureCoord;"+ + + "void main(void) {" + + " gl_PointSize = pointSize;" + + " frontColor = color;" + + " gl_Position = projection * view * model * vec4(Vertex, 1.0);" + + " vTextureCoord = aTextureCoord;" + + "}"; + + var fragmentShaderSource2D = + "#ifdef GL_ES\n" + + "precision highp float;\n" + + "#endif\n" + + + "varying vec4 frontColor;" + + "varying vec2 vTextureCoord;"+ + + "uniform sampler2D uSampler;"+ + "uniform int picktype;"+ + + "void main(void){" + + " if(picktype == 0){"+ + " gl_FragColor = frontColor;" + + " }" + + " else if(picktype == 1){"+ + " float alpha = texture2D(uSampler, vTextureCoord).a;"+ + " gl_FragColor = vec4(frontColor.rgb*alpha, alpha);\n"+ + " }"+ + "}"; + + // Vertex shader for boxes and spheres + var vertexShaderSource3D = + "varying vec4 frontColor;" + + + "attribute vec3 Vertex;" + + "attribute vec3 Normal;" + + "attribute vec4 aColor;" + + "attribute vec2 aTexture;" + + "varying vec2 vTexture;" + + + "uniform vec4 color;" + + + "uniform bool usingMat;" + + "uniform vec3 specular;" + + "uniform vec3 mat_emissive;" + + "uniform vec3 mat_ambient;" + + "uniform vec3 mat_specular;" + + "uniform float shininess;" + + + "uniform mat4 model;" + + "uniform mat4 view;" + + "uniform mat4 projection;" + + "uniform mat4 normalTransform;" + + + "uniform int lightCount;" + + "uniform vec3 falloff;" + + + // careful changing the order of these fields. Some cards + // have issues with memory alignment + "struct Light {" + + " int type;" + + " vec3 color;" + + " vec3 position;" + + " vec3 direction;" + + " float angle;" + + " vec3 halfVector;" + + " float concentration;" + + "};" + + + // nVidia cards have issues with arrays of structures + // so instead we create 8 instances of Light + "uniform Light lights0;" + + "uniform Light lights1;" + + "uniform Light lights2;" + + "uniform Light lights3;" + + "uniform Light lights4;" + + "uniform Light lights5;" + + "uniform Light lights6;" + + "uniform Light lights7;" + + + // GLSL does not support switch + "Light getLight(int index){" + + " if(index == 0) return lights0;" + + " if(index == 1) return lights1;" + + " if(index == 2) return lights2;" + + " if(index == 3) return lights3;" + + " if(index == 4) return lights4;" + + " if(index == 5) return lights5;" + + " if(index == 6) return lights6;" + + // some cards complain that not all paths return if we have + // this last one in a conditional. + " return lights7;" + + "}" + + + "void AmbientLight( inout vec3 totalAmbient, in vec3 ecPos, in Light light ) {" + + // Get the vector from the light to the vertex + // Get the distance from the current vector to the light position + " float d = length( light.position - ecPos );" + + " float attenuation = 1.0 / ( falloff[0] + ( falloff[1] * d ) + ( falloff[2] * d * d ));" + + " totalAmbient += light.color * attenuation;" + + "}" + + + "void DirectionalLight( inout vec3 col, inout vec3 spec, in vec3 vertNormal, in vec3 ecPos, in Light light ) {" + + " float powerfactor = 0.0;" + + " float nDotVP = max(0.0, dot( vertNormal, normalize(-light.position) ));" + + " float nDotVH = max(0.0, dot( vertNormal, normalize(-light.position-ecPos )));" + + + " if( nDotVP != 0.0 ){" + + " powerfactor = pow( nDotVH, shininess );" + + " }" + + + " col += light.color * nDotVP;" + + " spec += specular * powerfactor;" + + "}" + + + "void PointLight( inout vec3 col, inout vec3 spec, in vec3 vertNormal, in vec3 ecPos, in vec3 eye, in Light light ) {" + + " float powerfactor;" + + + // Get the vector from the light to the vertex + " vec3 VP = light.position - ecPos;" + + + // Get the distance from the current vector to the light position + " float d = length( VP ); " + + + // Normalize the light ray so it can be used in the dot product operation. + " VP = normalize( VP );" + + + " float attenuation = 1.0 / ( falloff[0] + ( falloff[1] * d ) + ( falloff[2] * d * d ));" + + + " float nDotVP = max( 0.0, dot( vertNormal, VP ));" + + " vec3 halfVector = normalize( VP + eye );" + + " float nDotHV = max( 0.0, dot( vertNormal, halfVector ));" + + + " if( nDotVP == 0.0) {" + + " powerfactor = 0.0;" + + " }" + + " else{" + + " powerfactor = pow( nDotHV, shininess );" + + " }" + + + " spec += specular * powerfactor * attenuation;" + + " col += light.color * nDotVP * attenuation;" + + "}" + + + /* + */ + "void SpotLight( inout vec3 col, inout vec3 spec, in vec3 vertNormal, in vec3 ecPos, in vec3 eye, in Light light ) {" + + " float spotAttenuation;" + + " float powerfactor;" + + + // calculate the vector from the current vertex to the light. + " vec3 VP = light.position - ecPos; " + + " vec3 ldir = normalize( -light.direction );" + + + // get the distance from the spotlight and the vertex + " float d = length( VP );" + + " VP = normalize( VP );" + + + " float attenuation = 1.0 / ( falloff[0] + ( falloff[1] * d ) + ( falloff[2] * d * d ) );" + + + // dot product of the vector from vertex to light and light direction. + " float spotDot = dot( VP, ldir );" + + + // if the vertex falls inside the cone + // The following is failing on Windows systems + // removed until we find a workaround + //" if( spotDot < cos( light.angle ) ) {" + + //" spotAttenuation = pow( spotDot, light.concentration );" + + //" }" + + //" else{" + + " spotAttenuation = 1.0;" + + //" }" + + " attenuation *= spotAttenuation;" + + + " float nDotVP = max( 0.0, dot( vertNormal, VP ));" + + " vec3 halfVector = normalize( VP + eye );" + + " float nDotHV = max( 0.0, dot( vertNormal, halfVector ));" + + + " if( nDotVP == 0.0 ) {" + + " powerfactor = 0.0;" + + " }" + + " else {" + + " powerfactor = pow( nDotHV, shininess );" + + " }" + + + " spec += specular * powerfactor * attenuation;" + + " col += light.color * nDotVP * attenuation;" + + "}" + + + "void main(void) {" + + " vec3 finalAmbient = vec3( 0.0, 0.0, 0.0 );" + + " vec3 finalDiffuse = vec3( 0.0, 0.0, 0.0 );" + + " vec3 finalSpecular = vec3( 0.0, 0.0, 0.0 );" + + + " vec4 col = color;" + + + " if(color[0] == -1.0){" + + " col = aColor;" + + " }" + + + " vec3 norm = vec3( normalTransform * vec4( Normal, 0.0 ) );" + + + " vec4 ecPos4 = view * model * vec4(Vertex,1.0);" + + " vec3 ecPos = (vec3(ecPos4))/ecPos4.w;" + + " vec3 eye = vec3( 0.0, 0.0, 1.0 );" + + + // If there were no lights this draw call, just use the + // assigned fill color of the shape and the specular value + " if( lightCount == 0 ) {" + + " frontColor = col + vec4(mat_specular,1.0);" + + " }" + + " else {" + + // WebGL forces us to iterate over a constant value + // so we can't iterate using lightCount + " for( int i = 0; i < 8; i++ ) {" + + " Light l = getLight(i);" + + + // We can stop iterating if we know we have gone past + // the number of lights which are on + " if( i >= lightCount ){" + + " break;" + + " }" + + + " if( l.type == 0 ) {" + + " AmbientLight( finalAmbient, ecPos, l );" + + " }" + + " else if( l.type == 1 ) {" + + " DirectionalLight( finalDiffuse, finalSpecular, norm, ecPos, l );" + + " }" + + " else if( l.type == 2 ) {" + + " PointLight( finalDiffuse, finalSpecular, norm, ecPos, eye, l );" + + " }" + + " else {" + + " SpotLight( finalDiffuse, finalSpecular, norm, ecPos, eye, l );" + + " }" + + " }" + + + " if( usingMat == false ) {" + + " frontColor = vec4(" + + " vec3(col) * finalAmbient +" + + " vec3(col) * finalDiffuse +" + + " vec3(col) * finalSpecular," + + " col[3] );" + + " }" + + " else{" + + " frontColor = vec4( " + + " mat_emissive + " + + " (vec3(col) * mat_ambient * finalAmbient) + " + + " (vec3(col) * finalDiffuse) + " + + " (mat_specular * finalSpecular), " + + " col[3] );" + + " }" + + " }" + + + " vTexture.xy = aTexture.xy;" + + " gl_Position = projection * view * model * vec4( Vertex, 1.0 );" + + "}"; + + var fragmentShaderSource3D = + "#ifdef GL_ES\n" + + "precision highp float;\n" + + "#endif\n" + + + "varying vec4 frontColor;" + + + "uniform sampler2D sampler;" + + "uniform bool usingTexture;" + + "varying vec2 vTexture;" + + + // In Processing, when a texture is used, the fill color is ignored + "void main(void){" + + " if(usingTexture){" + + " gl_FragColor = vec4(texture2D(sampler, vTexture.xy));" + + " }"+ + " else{" + + " gl_FragColor = frontColor;" + + " }" + + "}"; + + //////////////////////////////////////////////////////////////////////////// + // 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 {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 !== -1) { + 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 {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 !== -1) { + 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); + } + } + } + + /** + * 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 {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 {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); + } + } + + /** + * 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 {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); + } + } + } + + var imageModeCorner = function imageModeCorner(x, y, w, h, whAreSizes) { + return { + x: x, + y: y, + w: w, + h: h + }; + }; + var imageModeConvert = imageModeCorner; + + var imageModeCorners = function imageModeCorners(x, y, w, h, whAreSizes) { + return { + x: x, + y: y, + w: whAreSizes ? w : w - x, + h: whAreSizes ? h : h - y + }; + }; + + var imageModeCenter = function imageModeCenter(x, y, w, h, whAreSizes) { + return { + x: x - w / 2, + y: y - h / 2, + w: w, + h: h + }; + }; + + /** + * 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; + }; + + //////////////////////////////////////////////////////////////////////////// + // Char handling + //////////////////////////////////////////////////////////////////////////// + var charMap = {}; + + var Char = p.Character = function Char(chr) { + if (typeof chr === 'string' && chr.length === 1) { + this.code = chr.charCodeAt(0); + } 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; + }; + + /** + * 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 = p.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 part of the SVG document. + */ + drawPath: function(){ + var i, j; + if (this.vertices.length === 0) { return; } + p.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++) { + p.vertex(this.vertices[i][0], this.vertices[i][1]); + } + } else { // drawing 3D vertices + for (i = 0, j = this.vertices.length; i < j; i++) { + p.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) { + p.vertex(this.vertices[index][0], this.vertices[index][1]); + 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; + } + p.breakShape = false; + index++; + } else if (this.vertexCodes[i] === PConstants.BEZIER_VERTEX) { + p.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) { + p.curveVertex(this.vertices[index][0], + this.vertices[index][1]); + index++; + } else if (this.vertexCodes[i] === PConstants.BREAK) { + p.breakShape = true; + } + } + } else { // drawing a 3D path + for (i = 0, j = this.vertexCodes.length; i < j; i++) { + if (this.vertexCodes[i] === PConstants.VERTEX) { + p.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; + } + p.breakShape = false; + } else if (this.vertexCodes[i] === PConstants.BEZIER_VERTEX) { + p.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) { + p.curveVertex(this.vertices[index][0], + this.vertices[index][1], + this.vertices[index][2]); + index++; + } else if (this.vertexCodes[i] === PConstants.BREAK) { + p.breakShape = true; + } + } + } + } + p.endShape(this.close ? PConstants.CLOSE : PConstants.OPEN); + }, + /** + * @member PShape + * The drawGeometry() function draws the geometry part of the SVG document. + */ + drawGeometry: function() { + var i, j; + p.beginShape(this.kind); + if (this.style) { + for (i = 0, j = this.vertices.length; i < j; i++) { + p.vertex(this.vertices[i]); + } + } else { + for (i = 0, j = this.vertices.length; i < j; i++) { + var vert = this.vertices[i]; + if (vert[2] === 0) { + p.vertex(vert[0], vert[1]); + } else { + p.vertex(vert[0], vert[1], vert[2]); + } + } + } + p.endShape(); + }, + /** + * @member PShape + * The drawGroup() function draws the part of the SVG document. + */ + drawGroup: function() { + for (var i = 0, j = this.children.length; i < j; i++) { + this.children[i].draw(); + } + }, + /** + * @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() { + if (this.kind === PConstants.POINT) { + p.point(this.params[0], this.params[1]); + } else if (this.kind === PConstants.LINE) { + if (this.params.length === 4) { // 2D + p.line(this.params[0], this.params[1], + this.params[2], this.params[3]); + } else { // 3D + p.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) { + p.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) { + p.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) { + p.imageMode(PConstants.CORNER); + p.image(this.image, + this.params[0], + this.params[1], + this.params[2], + this.params[3]); + } else { + p.rectMode(PConstants.CORNER); + p.rect(this.params[0], + this.params[1], + this.params[2], + this.params[3]); + } + } else if (this.kind === PConstants.ELLIPSE) { + p.ellipseMode(PConstants.CORNER); + p.ellipse(this.params[0], + this.params[1], + this.params[2], + this.params[3]); + } else if (this.kind === PConstants.ARC) { + p.ellipseMode(PConstants.CORNER); + p.arc(this.params[0], + this.params[1], + this.params[2], + this.params[3], + this.params[4], + this.params[5]); + } else if (this.kind === PConstants.BOX) { + if (this.params.length === 1) { + p.box(this.params[0]); + } else { + p.box(this.params[0], this.params[1], this.params[2]); + } + } else if (this.kind === PConstants.SPHERE) { + p.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() { + if (this.matrix) { + p.pushMatrix(); + curContext.transform(this.matrix.elements[0], + this.matrix.elements[3], + this.matrix.elements[1], + this.matrix.elements[4], + this.matrix.elements[2], + this.matrix.elements[5]); + //p.applyMatrix(this.matrix.elements[0],this.matrix.elements[0]); + } + if (this.style) { + p.pushStyle(); + this.styles(); + } + }, + /** + * @member PShape + * The post() function performs the necessary actions after the SVG is drawn. This includes removing transformations and removing added styles. + */ + post: function() { + if (this.matrix) { + p.popMatrix(); + } + if (this.style) { + p.popStyle(); + } + }, + /** + * @member PShape + * The styles() function changes the Processing's current styles + */ + styles: function() { + if (this.stroke) { + p.stroke(this.strokeColor); + p.strokeWeight(this.strokeWeight); + p.strokeCap(this.strokeCap); + p.strokeJoin(this.strokeJoin); + } else { + p.noStroke(); + } + + if (this.fill) { + p.fill(this.fillColor); + + } else { + p.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]; + } else { + var found; + if(child === "" || this.name === child){ + return this; + } else { + 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]; + } + } + 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 p.PMatrix2D(); + } else { + this.matrix = new p.PMatrix3D(); + } + }else if(dimensions === 3 && this.matrix instanceof p.PMatrix2D) { + this.matrix = new p.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]); + } + } + }; + + /** + * 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 + */ + var PShapeSVG = p.PShapeSVG = function() { + p.PShape.call( this ); // PShape is the base class. + if (arguments.length === 1) { //xml element coming in + this.element = arguments[0] ;//new p.XMLElement(null, 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 , 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 p.XMLElement(null, 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 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 + * this section + * of the SVG documentation. + * + * @param {String} str text of the matrix param. + * + * @return {PMatrix2D} a PMatrix2D + */ + PShapeSVG.prototype.parseMatrix = function(str) { + this.checkMatrix(2); + var pieces = []; + str.replace(/\s*(\w+)\((.*?)\)/g, function(all) { + // get a list of transform definitions + pieces.push(p.trim(all)); + }); + if (pieces.length === 0) { + //p.println("Transformation:" + str + " is empty"); + return null; + } + for (var i = 0, j = pieces.length; i < j; i++) { + var m = []; + pieces[i].replace(/\((.*?)\)/, (function() { + return function(all, params) { + // get the coordinates that can be separated by spaces or a comma + m = params.replace(/,+/g, " ").split(/\s+/); + }; + }())); + + 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(p.radians(angle)); + } else if (m.length === 3) { + this.matrix.translate(m[1], m[2]); + this.matrix.rotate(p.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]); + } + } + 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 children = new p.PShape(); + for (var i = 0, j = newelement.length; i < j; i++) { + var kid = this.parseChild(newelement[i]); + if (kid) { + children.addChild(kid); + } + } + this.children.push(children); + }; + /** + * @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); + } else if (name === "linearGradient") { + //return new LinearGradient(this, elem); + } else if (name === "text") { + //p.println("Text in SVG files is not currently supported " + + // "convert text to outlines instead."); + } else if (name === "filter") { + //p.println("Filters are not supported."); + } else if (name === "mask") { + //p.println("Masks are not supported."); + } else { + //p.println("Ignoring <" + name + "> tag."); + } + return shape; + }; + /** + * @member PShapeSVG + * The parsePath() function parses the 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 = p.trim(this.element.getStringAttribute("d") + .replace(/[\s,]+/g,' ')); + if (pathData === null) { + return; + } + pathData = p.__toCharArray(pathData); + 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].valueOf(); + 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].valueOf(); + 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].valueOf() === 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].valueOf(); + } + } + } + if (str !== "") { + tmpArray.push(parseFloat(str)); + str = ""; + } + command = pathData[j]; + valOf = command.valueOf(); + 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 + 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) { + //Z + } else if (valOf === 122) { //z + 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 = p.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 { + //p.println("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"); + }; + /** + * @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"); + this.params[1] = this.element.getFloatAttribute("cy"); + + var rx, ry; + if (val) { + rx = ry = this.element.getFloatAttribute("r"); + } else { + rx = this.element.getFloatAttribute("rx"); + ry = this.element.getFloatAttribute("ry"); + } + 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 = p.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; + 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 ); + /*Object fillObject = findChild(fillName); + if (fillObject instanceof Gradient) { + fill = true; + fillGradient = (Gradient) fillObject; + fillGradientPaint = calcGradientPaint(fillGradient); //, opacity); + } else { + System.err.println("url " + fillName + " refers to unexpected data"); + }*/ + } 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; + 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); + //this.strokeObject = findChild(strokeName); + /*if (strokeObject instanceof Gradient) { + strokeGradient = (Gradient) strokeObject; + strokeGradientPaint = calcGradientPaint(strokeGradient); + //, opacity); + } else { + System.err.println("url " + strokeName + + " refers to unexpected data"); + }*/ + } 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 units 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; + } else if (text.indexOf("pc") === len) { + return parseFloat( text.substring( 0, len)) * 15; + } else if (text.indexOf("mm") === len) { + return parseFloat( text.substring(0, len)) * 3.543307; + } else if (text.indexOf("cm") === len) { + return parseFloat(text.substring(0, len)) * 35.43307; + } else if (text.indexOf("in") === len) { + return parseFloat(text.substring(0, len)) * 90; + } else if (text.indexOf("px") === len) { + return parseFloat(text.substring(0, len)); + } else { + return parseFloat(text); + } + }; + /** + * 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(); + 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; + }; + + /** + * XMLAttribute is an attribute of a XML element. This is an internal class + * + * @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 + */ + var XMLAttribute = function(fname, n, nameSpace, v, t){ + this.fullName = fname || ""; + this.name = n || ""; + this.namespace = nameSpace || ""; + this.value = v; + this.type = t; + }; + /** + * XMLAttribute methods + */ + 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; + } + }; + + /** + * 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 + */ + var XMLElement = p.XMLElement = function() { + if (arguments.length === 4) { + this.attributes = []; + this.children = []; + this.fullName = arguments[0] || ""; + if (arguments[1]) { + this.name = arguments[1]; + } else { + var index = this.fullName.indexOf(':'); + if (index >= 0) { + this.name = this.fullName.substring(index + 1); + } else { + this.name = this.fullName; + } + } + this.namespace = arguments[1]; + this.content = ""; + this.lineNr = arguments[3]; + this.systemID = arguments[2]; + this.parent = null; + } + else if ((arguments.length === 2 && arguments[1].indexOf(".") > -1) ) { // filename or svg xml element + this.attributes = []; + this.children = []; + this.fullName = ""; + this.name = ""; + this.namespace = ""; + this.content = ""; + this.systemID = ""; + this.lineNr = ""; + this.parent = null; + this.parse(arguments[arguments.length -1]); + } else if (arguments.length === 1 && typeof arguments[0] === "string"){ + //xml string + this.attributes = []; + this.children = []; + this.fullName = ""; + this.name = ""; + this.namespace = ""; + this.content = ""; + this.systemID = ""; + this.lineNr = ""; + this.parent = null; + this.parse(arguments[0]); + } + else { //empty ctor + this.attributes = []; + this.children = []; + this.fullName = ""; + this.name = ""; + this.namespace = ""; + this.content = ""; + this.systemID = ""; + this.lineNr = ""; + this.parent = null; + } + }; + /** + * 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(filename) { + var xmlDoc; + try { + if (filename.indexOf(".xml") > -1 || filename.indexOf(".svg") > -1) { + filename = ajax(filename); + } + xmlDoc = new DOMParser().parseFromString(filename, "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 + * 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 () { + if (arguments.length === 2) { + return new XMLElement(arguments[0], arguments[1], null, null); + } else { + return new XMLElement(arguments[0], arguments[1], arguments[2], arguments[3]); + } + }, + /** + * @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; + } else if (arguments.length === 2) { + return this.getAttribute(arguments[0],arguments[1]) !== null; + } + }, + /** + * @member XMLElement + * The createPCDataElement() function creates an element to be used for #PCDATA content + * + * @return {XMLElement} new XMLElement element + */ + createPCDataElement: function () { + return new XMLElement(); + }, + /** + * @member XMLElement + * The equals() function checks to see if the element being passed in equals another element + * + * @param {Object} rawElement the element to compare to + * + * @return {boolean} true if the element equals another element + */ + equals: function(object){ + if (typeof object === "Object") { + return this.equalsXMLElement(object); + } + }, + /** + * @member XMLElement + * The equalsXMLElement() 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 + */ + equalsXMLElement: function (object) { + if (object instanceof XMLElement) { + var i, j; + if (this.name !== object.getLocalName()) { return false; } + if (this.attributes.length !== object.getAttributeCount()) { return false; } + for (i = 0, j = this.attributes.length; i < j; i++){ + if (! object.hasAttribute(this.attributes[i].getName(), this.attributes[i].getNamespace())) { return false; } + if (this.attributes[i].getValue() !== object.attributes[i].getValue()) { return false; } + if (this.attributes[i].getType() !== object.attributes[i].getType()) { return false; } + } + if (this.children.length !== object.getChildCount()) { return false; } + var child1, child2; + for (i = 0, j = this.children.length; i < j; i++) { + child1 = this.getChildAtIndex(i); + child2 = object.getChildAtIndex(i); + if (! child1.equalsXMLElement(child2)) { return false; } + } + return true; + } + }, + /** + * @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(){ + return this.content; + }, + /** + * @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(); + } else { + return arguments[1]; + } + } else if (arguments.length === 1) { + attribute = this.findAttribute(arguments[0]); + if (attribute) { + return attribute.getValue(); + } else { + return null; + } + } else if (arguments.length === 3) { + attribute = this.findAttribute(arguments[0],arguments[1]); + if (attribute) { + return attribute.getValue(); + } else { + 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]); + } else if (arguments.length === 2){ + return this.getAttribute(arguments[0], arguments[1]); + } else { + return this.getAttribute(arguments[0], arguments[1],arguments[2]); + } + }, + /** + * @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)); + } else if (arguments.length === 2 ){ + return this.getAttribute(arguments[0], arguments[1]); + } else { + return this.getAttribute(arguments[0], arguments[1],arguments[2]); + } + }, + /** + * @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 ); + } else if (arguments.length === 2) { + return this.getAttribute(arguments[0], arguments[1]); + } else { + return this.getAttribute(arguments[0], arguments[1],arguments[2]); + } + }, + /** + * @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 (){ + if (typeof arguments[0] === "number") { + return this.children[arguments[0]]; + } + else if (arguments[0].indexOf('/') !== -1) { // path was given + this.getChildRecursive(arguments[0].split("/"), 0); + } else { + 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]) { + 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]); + } else if (arguments[0].indexOf('/') !== -1) { // path was given + return this.getChildrenRecursive( arguments[0].split("/"), 0); + } else { + 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; + } + }else { + 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) { + var kid, kidName; + for(var i = 0, j = this.getChildCount(); i < j; i++) { + kid = this.getChild(i); + kidName = kid.getName(); + if (kidName !== null && kidName === items[offset]) { + if (offset === items.length-1) { + return kid; + } else { + offset += 1; + return kid.getChildRecursive(items, offset); + } + } + } + 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 + * 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; + if (!parent) { + this.fullName = elementpath.localName; + this.name = elementpath.nodeName; + this.content = elementpath.textContent || ""; + xmlelement = this; + } else { // a parent + xmlelement = new XMLElement(elementpath.localName, elementpath.nodeName, "", ""); + xmlelement.content = elementpath.textContent || ""; + xmlelement.parent = parent; + } + + 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); + } + + for (l = 0, m = elementpath.childNodes.length; l < m; l++) { + var node = elementpath.childNodes[l]; + if (node.nodeType === 1) { // ELEMENT_NODE type + xmlelement.children.push(xmlelement.parseChildrenRecursive(xmlelement, node)); + } + } + return xmlelement; + }, + /** + * @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].equalsXMLElement(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); + return; + } + }, + /** + * @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]; + } + } + }, + /** + * @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); + } + } + }, + /** + * @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) { + 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; + }, + getLocalName: function() { + return this.name; + }, + getAttributeCount: function() { + return this.attributes.length; + } + }; + + + //////////////////////////////////////////////////////////////////////////// + // 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 printMatrixHelper(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 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 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 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 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 PMatrix3D() { + // 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.set([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 PMatrix2D + * The transpose() function transpose this matrix. + */ + transpose: function() { + var temp = this.elements.slice(); + this.elements[0] = temp[0]; + this.elements[1] = temp[4]; + this.elements[2] = temp[8]; + this.elements[3] = temp[12]; + this.elements[4] = temp[1]; + this.elements[5] = temp[5]; + this.elements[6] = temp[9]; + this.elements[7] = temp[13]; + this.elements[8] = temp[2]; + this.elements[9] = temp[6]; + this.elements[10] = temp[10]; + this.elements[11] = temp[14]; + this.elements[12] = temp[3]; + this.elements[13] = temp[7]; + this.elements[14] = temp[11]; + this.elements[15] = temp[15]; + }, + /** + * @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 = 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 PMatrix2D + * 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); + }, + multX: function(x, y, z, w) { + if (!z) { + return this.elements[0] * x + this.elements[1] * y + this.elements[3]; + } else if (!w) { + return this.elements[0] * x + this.elements[1] * y + this.elements[2] * z + this.elements[3]; + } else { + 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]; + } else if (!w) { + return this.elements[4] * x + this.elements[5] * y + this.elements[6] * z + this.elements[7]; + } else { + 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]; + } else { + 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]; + } else { + 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 PMatrixStack() { + 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 load() { + var tmpMatrix; + if (p.use3DContext) { + tmpMatrix = new PMatrix3D(); + } else { + tmpMatrix = new PMatrix2D(); + } + + if (arguments.length === 1) { + tmpMatrix.set(arguments[0]); + } else { + tmpMatrix.set(arguments); + } + this.matrixStack.push(tmpMatrix); + }; + + /** + * @member PMatrixStack + * push adds a duplicate of the top of the stack onto the stack - uses the peek function + */ + PMatrixStack.prototype.push = function push() { + 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 pop() { + 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 peek() { + var tmpMatrix; + if (p.use3DContext) { + tmpMatrix = new PMatrix3D(); + } else { + tmpMatrix = new PMatrix2D(); + } + + 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 mult(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 (arguments.length === 1) { + tokens = "\n\t\r\f "; + } + + tokens = "[" + tokens + "]"; + + var ary = []; + var index = 0; + var pos = str.search(tokens); + + while (pos >= 0) { + if (pos === 0) { + str = str.substring(1); + } else { + ary[index] = str.substring(0, pos); + index++; + str = str.substring(pos); + } + pos = str.search(tokens); + } + + if (str.length > 0) { + ary[index] = str; + } + + if (ary.length === 0) { + ary = undef; + } + + return ary; + }; + + /** + * 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) { + if (arguments.length === 2) { + return array.slice(offset, array.length - offset); + } else if (arguments.length === 3) { + return array.slice(offset, offset + length); + } + }; + + /** + * 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, newSize) { + var temp = ary.slice(0); + if (arguments.length === 1) { + // double size of array + temp.length = ary.length * 2; + return temp; + } else if (arguments.length === 2) { + // size is newSize + 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 = { + replace: function(c1, c2) { + return c2; + }, + blend: function(c1, c2) { + var f = (c2 & PConstants.ALPHA_MASK) >>> 24; + return (Math.min(((c1 & PConstants.ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + p.mix(c1 & PConstants.RED_MASK, c2 & PConstants.RED_MASK, f) & PConstants.RED_MASK | + p.mix(c1 & PConstants.GREEN_MASK, c2 & PConstants.GREEN_MASK, f) & PConstants.GREEN_MASK | + p.mix(c1 & PConstants.BLUE_MASK, c2 & PConstants.BLUE_MASK, f)); + }, + add: function(c1, c2) { + var f = (c2 & PConstants.ALPHA_MASK) >>> 24; + return (Math.min(((c1 & PConstants.ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + Math.min(((c1 & PConstants.RED_MASK) + ((c2 & PConstants.RED_MASK) >> 8) * f), PConstants.RED_MASK) & PConstants.RED_MASK | + Math.min(((c1 & PConstants.GREEN_MASK) + ((c2 & PConstants.GREEN_MASK) >> 8) * f), PConstants.GREEN_MASK) & PConstants.GREEN_MASK | + Math.min((c1 & PConstants.BLUE_MASK) + (((c2 & PConstants.BLUE_MASK) * f) >> 8), PConstants.BLUE_MASK)); + }, + subtract: function(c1, c2) { + var f = (c2 & PConstants.ALPHA_MASK) >>> 24; + return (Math.min(((c1 & PConstants.ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + Math.max(((c1 & PConstants.RED_MASK) - ((c2 & PConstants.RED_MASK) >> 8) * f), PConstants.GREEN_MASK) & PConstants.RED_MASK | + Math.max(((c1 & PConstants.GREEN_MASK) - ((c2 & PConstants.GREEN_MASK) >> 8) * f), PConstants.BLUE_MASK) & PConstants.GREEN_MASK | + Math.max((c1 & PConstants.BLUE_MASK) - (((c2 & PConstants.BLUE_MASK) * f) >> 8), 0)); + }, + lightest: function(c1, c2) { + var f = (c2 & PConstants.ALPHA_MASK) >>> 24; + return (Math.min(((c1 & PConstants.ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + Math.max(c1 & PConstants.RED_MASK, ((c2 & PConstants.RED_MASK) >> 8) * f) & PConstants.RED_MASK | + Math.max(c1 & PConstants.GREEN_MASK, ((c2 & PConstants.GREEN_MASK) >> 8) * f) & PConstants.GREEN_MASK | + Math.max(c1 & PConstants.BLUE_MASK, ((c2 & PConstants.BLUE_MASK) * f) >> 8)); + }, + darkest: function(c1, c2) { + var f = (c2 & PConstants.ALPHA_MASK) >>> 24; + return (Math.min(((c1 & PConstants.ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + p.mix(c1 & PConstants.RED_MASK, Math.min(c1 & PConstants.RED_MASK, ((c2 & PConstants.RED_MASK) >> 8) * f), f) & PConstants.RED_MASK | + p.mix(c1 & PConstants.GREEN_MASK, Math.min(c1 & PConstants.GREEN_MASK, ((c2 & PConstants.GREEN_MASK) >> 8) * f), f) & PConstants.GREEN_MASK | + p.mix(c1 & PConstants.BLUE_MASK, Math.min(c1 & PConstants.BLUE_MASK, ((c2 & PConstants.BLUE_MASK) * f) >> 8), f)); + }, + difference: function(c1, c2) { + var f = (c2 & PConstants.ALPHA_MASK) >>> 24; + var ar = (c1 & PConstants.RED_MASK) >> 16; + var ag = (c1 & PConstants.GREEN_MASK) >> 8; + var ab = (c1 & PConstants.BLUE_MASK); + var br = (c2 & PConstants.RED_MASK) >> 16; + var bg = (c2 & PConstants.GREEN_MASK) >> 8; + var bb = (c2 & PConstants.BLUE_MASK); + // formula: + var cr = (ar > br) ? (ar - br) : (br - ar); + var cg = (ag > bg) ? (ag - bg) : (bg - ag); + var cb = (ab > bb) ? (ab - bb) : (bb - ab); + // alpha blend (this portion will always be the same) + return (Math.min(((c1 & PConstants.ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + (p.peg(ar + (((cr - ar) * f) >> 8)) << 16) | + (p.peg(ag + (((cg - ag) * f) >> 8)) << 8) | + (p.peg(ab + (((cb - ab) * f) >> 8)))); + }, + exclusion: function(c1, c2) { + var f = (c2 & PConstants.ALPHA_MASK) >>> 24; + var ar = (c1 & PConstants.RED_MASK) >> 16; + var ag = (c1 & PConstants.GREEN_MASK) >> 8; + var ab = (c1 & PConstants.BLUE_MASK); + var br = (c2 & PConstants.RED_MASK) >> 16; + var bg = (c2 & PConstants.GREEN_MASK) >> 8; + var bb = (c2 & PConstants.BLUE_MASK); + // formula: + var cr = ar + br - ((ar * br) >> 7); + var cg = ag + bg - ((ag * bg) >> 7); + var cb = ab + bb - ((ab * bb) >> 7); + // alpha blend (this portion will always be the same) + return (Math.min(((c1 & PConstants.ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + (p.peg(ar + (((cr - ar) * f) >> 8)) << 16) | + (p.peg(ag + (((cg - ag) * f) >> 8)) << 8) | + (p.peg(ab + (((cb - ab) * f) >> 8)))); + }, + multiply: function(c1, c2) { + var f = (c2 & PConstants.ALPHA_MASK) >>> 24; + var ar = (c1 & PConstants.RED_MASK) >> 16; + var ag = (c1 & PConstants.GREEN_MASK) >> 8; + var ab = (c1 & PConstants.BLUE_MASK); + var br = (c2 & PConstants.RED_MASK) >> 16; + var bg = (c2 & PConstants.GREEN_MASK) >> 8; + var bb = (c2 & PConstants.BLUE_MASK); + // formula: + var cr = (ar * br) >> 8; + var cg = (ag * bg) >> 8; + var cb = (ab * bb) >> 8; + // alpha blend (this portion will always be the same) + return (Math.min(((c1 & PConstants.ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + (p.peg(ar + (((cr - ar) * f) >> 8)) << 16) | + (p.peg(ag + (((cg - ag) * f) >> 8)) << 8) | + (p.peg(ab + (((cb - ab) * f) >> 8)))); + }, + screen: function(c1, c2) { + var f = (c2 & PConstants.ALPHA_MASK) >>> 24; + var ar = (c1 & PConstants.RED_MASK) >> 16; + var ag = (c1 & PConstants.GREEN_MASK) >> 8; + var ab = (c1 & PConstants.BLUE_MASK); + var br = (c2 & PConstants.RED_MASK) >> 16; + var bg = (c2 & PConstants.GREEN_MASK) >> 8; + var bb = (c2 & PConstants.BLUE_MASK); + // formula: + var cr = 255 - (((255 - ar) * (255 - br)) >> 8); + var cg = 255 - (((255 - ag) * (255 - bg)) >> 8); + var cb = 255 - (((255 - ab) * (255 - bb)) >> 8); + // alpha blend (this portion will always be the same) + return (Math.min(((c1 & PConstants.ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + (p.peg(ar + (((cr - ar) * f) >> 8)) << 16) | + (p.peg(ag + (((cg - ag) * f) >> 8)) << 8) | + (p.peg(ab + (((cb - ab) * f) >> 8)))); + }, + hard_light: function(c1, c2) { + var f = (c2 & PConstants.ALPHA_MASK) >>> 24; + var ar = (c1 & PConstants.RED_MASK) >> 16; + var ag = (c1 & PConstants.GREEN_MASK) >> 8; + var ab = (c1 & PConstants.BLUE_MASK); + var br = (c2 & PConstants.RED_MASK) >> 16; + var bg = (c2 & PConstants.GREEN_MASK) >> 8; + var bb = (c2 & PConstants.BLUE_MASK); + // formula: + var cr = (br < 128) ? ((ar * br) >> 7) : (255 - (((255 - ar) * (255 - br)) >> 7)); + var cg = (bg < 128) ? ((ag * bg) >> 7) : (255 - (((255 - ag) * (255 - bg)) >> 7)); + var cb = (bb < 128) ? ((ab * bb) >> 7) : (255 - (((255 - ab) * (255 - bb)) >> 7)); + // alpha blend (this portion will always be the same) + return (Math.min(((c1 & PConstants.ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + (p.peg(ar + (((cr - ar) * f) >> 8)) << 16) | + (p.peg(ag + (((cg - ag) * f) >> 8)) << 8) | + (p.peg(ab + (((cb - ab) * f) >> 8)))); + }, + soft_light: function(c1, c2) { + var f = (c2 & PConstants.ALPHA_MASK) >>> 24; + var ar = (c1 & PConstants.RED_MASK) >> 16; + var ag = (c1 & PConstants.GREEN_MASK) >> 8; + var ab = (c1 & PConstants.BLUE_MASK); + var br = (c2 & PConstants.RED_MASK) >> 16; + var bg = (c2 & PConstants.GREEN_MASK) >> 8; + var bb = (c2 & PConstants.BLUE_MASK); + // formula: + var cr = ((ar * br) >> 7) + ((ar * ar) >> 8) - ((ar * ar * br) >> 15); + var cg = ((ag * bg) >> 7) + ((ag * ag) >> 8) - ((ag * ag * bg) >> 15); + var cb = ((ab * bb) >> 7) + ((ab * ab) >> 8) - ((ab * ab * bb) >> 15); + // alpha blend (this portion will always be the same) + return (Math.min(((c1 & PConstants.ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + (p.peg(ar + (((cr - ar) * f) >> 8)) << 16) | + (p.peg(ag + (((cg - ag) * f) >> 8)) << 8) | + (p.peg(ab + (((cb - ab) * f) >> 8)))); + }, + overlay: function(c1, c2) { + var f = (c2 & PConstants.ALPHA_MASK) >>> 24; + var ar = (c1 & PConstants.RED_MASK) >> 16; + var ag = (c1 & PConstants.GREEN_MASK) >> 8; + var ab = (c1 & PConstants.BLUE_MASK); + var br = (c2 & PConstants.RED_MASK) >> 16; + var bg = (c2 & PConstants.GREEN_MASK) >> 8; + var bb = (c2 & PConstants.BLUE_MASK); + // formula: + var cr = (ar < 128) ? ((ar * br) >> 7) : (255 - (((255 - ar) * (255 - br)) >> 7)); + var cg = (ag < 128) ? ((ag * bg) >> 7) : (255 - (((255 - ag) * (255 - bg)) >> 7)); + var cb = (ab < 128) ? ((ab * bb) >> 7) : (255 - (((255 - ab) * (255 - bb)) >> 7)); + // alpha blend (this portion will always be the same) + return (Math.min(((c1 & PConstants.ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + (p.peg(ar + (((cr - ar) * f) >> 8)) << 16) | + (p.peg(ag + (((cg - ag) * f) >> 8)) << 8) | + (p.peg(ab + (((cb - ab) * f) >> 8)))); + }, + dodge: function(c1, c2) { + var f = (c2 & PConstants.ALPHA_MASK) >>> 24; + var ar = (c1 & PConstants.RED_MASK) >> 16; + var ag = (c1 & PConstants.GREEN_MASK) >> 8; + var ab = (c1 & PConstants.BLUE_MASK); + var br = (c2 & PConstants.RED_MASK) >> 16; + var bg = (c2 & PConstants.GREEN_MASK) >> 8; + var bb = (c2 & PConstants.BLUE_MASK); + // formula: + var cr = (br === 255) ? 255 : p.peg((ar << 8) / (255 - br)); // division requires pre-peg()-ing + var cg = (bg === 255) ? 255 : p.peg((ag << 8) / (255 - bg)); // " + var cb = (bb === 255) ? 255 : p.peg((ab << 8) / (255 - bb)); // " + // alpha blend (this portion will always be the same) + return (Math.min(((c1 & PConstants.ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + (p.peg(ar + (((cr - ar) * f) >> 8)) << 16) | + (p.peg(ag + (((cg - ag) * f) >> 8)) << 8) | + (p.peg(ab + (((cb - ab) * f) >> 8)))); + }, + burn: function(c1, c2) { + var f = (c2 & PConstants.ALPHA_MASK) >>> 24; + var ar = (c1 & PConstants.RED_MASK) >> 16; + var ag = (c1 & PConstants.GREEN_MASK) >> 8; + var ab = (c1 & PConstants.BLUE_MASK); + var br = (c2 & PConstants.RED_MASK) >> 16; + var bg = (c2 & PConstants.GREEN_MASK) >> 8; + var bb = (c2 & PConstants.BLUE_MASK); + // formula: + var cr = (br === 0) ? 0 : 255 - p.peg(((255 - ar) << 8) / br); // division requires pre-peg()-ing + var cg = (bg === 0) ? 0 : 255 - p.peg(((255 - ag) << 8) / bg); // " + var cb = (bb === 0) ? 0 : 255 - p.peg(((255 - ab) << 8) / bb); // " + // alpha blend (this portion will always be the same) + return (Math.min(((c1 & PConstants.ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + (p.peg(ar + (((cr - ar) * f) >> 8)) << 16) | + (p.peg(ag + (((cg - ag) * f) >> 8)) << 8) | + (p.peg(ab + (((cb - ab) * f) >> 8)))); + } + }; + + 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 greater than 255 + 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)); + a = (a > 255) ? 255 : a; + + return aValue1 - (aValue1 & PConstants.ALPHA_MASK) + ((a << 24) & PConstants.ALPHA_MASK); + } + // Grayscale and alpha + else { + if (curColorMode === PConstants.RGB) { + return color$4(aValue1, aValue1, aValue1, aValue2); + } else 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); + } else if (curColorMode === PConstants.HSB) { + return color$4(0, 0, (aValue1 / colorModeX) * colorModeZ, colorModeA); + } + } + // Color int + else if (aValue1) { + 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 color(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]; + } else { + 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]; + } + } + }; + + p.color.toHSB = function( 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]; + } else { + 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 p.color.toHSB(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 p.color.toHSB(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 p.color.toHSB(colInt)[0]; + }; + + var verifyChannel = function verifyChannel(aColor) { + if (aColor.constructor === Array) { + return aColor; + } else { + return p.color(aColor); + } + }; + + /** + * 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 lerpColor(c1, c2, amt) { + // Get RGBA values for Color 1 to floats + var colorBits1 = p.color(c1); + var r1 = (colorBits1 & PConstants.RED_MASK) >>> 16; + var g1 = (colorBits1 & PConstants.GREEN_MASK) >>> 8; + var b1 = (colorBits1 & PConstants.BLUE_MASK); + var a1 = ((colorBits1 & PConstants.ALPHA_MASK) >>> 24) / colorModeA; + + // Get RGBA values for Color 2 to floats + var colorBits2 = p.color(c2); + var r2 = (colorBits2 & PConstants.RED_MASK) >>> 16; + var g2 = (colorBits2 & PConstants.GREEN_MASK) >>> 8; + var b2 = (colorBits2 & PConstants.BLUE_MASK); + var a2 = ((colorBits2 & PConstants.ALPHA_MASK) >>> 24) / colorModeA; + + // Return lerp value for each channel, INT for color, Float for Alpha-range + var r = parseInt(p.lerp(r1, r2, amt), 10); + var g = parseInt(p.lerp(g1, g2, amt), 10); + var b = parseInt(p.lerp(b1, b2, amt), 10); + var a = parseFloat(p.lerp(a1, a2, amt) * colorModeA); + + return p.color.toInt(r, g, b, a); + }; + + // Forced default color mode for #aaaaaa style + /** + * Convert 3 int values to a color in the default color mode RGB even if curColorMode is not set to RGB + * + * @param {int} aValue1 range for the red color + * @param {int} aValue2 range for the green color + * @param {int} aValue3 range for the blue color + * + * @returns {Color} + * + * @see color + */ + p.defaultColor = function(aValue1, aValue2, aValue3) { + var tmpColorMode = curColorMode; + curColorMode = PConstants.RGB; + var c = p.color(aValue1 / 255 * colorModeX, aValue2 / 255 * colorModeY, aValue3 / 255 * colorModeZ); + curColorMode = tmpColorMode; + return c; + }; + + /** + * 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 colorMode() { // 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) { + var color = 0; + switch (mode) { + case PConstants.REPLACE: + color = p.modes.replace(c1, c2); + break; + case PConstants.BLEND: + color = p.modes.blend(c1, c2); + break; + case PConstants.ADD: + color = p.modes.add(c1, c2); + break; + case PConstants.SUBTRACT: + color = p.modes.subtract(c1, c2); + break; + case PConstants.LIGHTEST: + color = p.modes.lightest(c1, c2); + break; + case PConstants.DARKEST: + color = p.modes.darkest(c1, c2); + break; + case PConstants.DIFFERENCE: + color = p.modes.difference(c1, c2); + break; + case PConstants.EXCLUSION: + color = p.modes.exclusion(c1, c2); + break; + case PConstants.MULTIPLY: + color = p.modes.multiply(c1, c2); + break; + case PConstants.SCREEN: + color = p.modes.screen(c1, c2); + break; + case PConstants.HARD_LIGHT: + color = p.modes.hard_light(c1, c2); + break; + case PConstants.SOFT_LIGHT: + color = p.modes.soft_light(c1, c2); + break; + case PConstants.OVERLAY: + color = p.modes.overlay(c1, c2); + break; + case PConstants.DODGE: + color = p.modes.dodge(c1, c2); + break; + case PConstants.BURN: + color = p.modes.burn(c1, c2); + break; + } + return color; + }; + + //////////////////////////////////////////////////////////////////////////// + // 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 printMatrix() { + 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 + */ + p.translate = function translate(x, y, z) { + if (p.use3DContext) { + forwardTransform.translate(x, y, z); + reverseTransform.invTranslate(x, y, z); + } else { + curContext.translate(x, y); + } + }; + + /** + * 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 + */ + p.scale = function scale(x, y, z) { + if (p.use3DContext) { + forwardTransform.scale(x, y, z); + reverseTransform.invScale(x, y, z); + } else { + curContext.scale(x, y || x); + } + }; + + /** + * 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 + */ + p.pushMatrix = function pushMatrix() { + if (p.use3DContext) { + userMatrixStack.load(modelView); + } else { + saveContext(); + } + }; + + /** + * 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 + */ + p.popMatrix = function popMatrix() { + if (p.use3DContext) { + modelView.set(userMatrixStack.pop()); + } else { + restoreContext(); + } + }; + + /** + * 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 + */ + p.resetMatrix = function resetMatrix() { + if (p.use3DContext) { + forwardTransform.reset(); + reverseTransform.reset(); + } else { + curContext.setTransform(1,0,0,1,0,0); + } + }; + + /** + * 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 + */ + p.applyMatrix = function applyMatrix() { + var a = arguments; + if (!p.use3DContext) { + for (var cnt = a.length; cnt < 16; cnt++) { + a[cnt] = 0; + } + a[10] = a[15] = 1; + } + + forwardTransform.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]); + reverseTransform.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]); + }; + + /** + * 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) { + forwardTransform.rotateX(angleInRadians); + reverseTransform.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 + */ + p.rotateZ = function(angleInRadians) { + forwardTransform.rotateZ(angleInRadians); + reverseTransform.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) { + forwardTransform.rotateY(angleInRadians); + reverseTransform.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 + */ + p.rotate = function rotate(angleInRadians) { + if (p.use3DContext) { + forwardTransform.rotateZ(angleInRadians); + reverseTransform.invRotateZ(angleInRadians); + } else { + curContext.rotate(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 pushStyle() { + // 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, + 'curTextSize': curTextSize + }; + + 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 popStyle() { + 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; + curTextSize = oldState.curTextSize; + } 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 year() { + 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 month() { + 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 day() { + 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 hour() { + 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 minute() { + 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 second() { + 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 millis() { + return new Date().getTime() - 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 + */ + p.redraw = function redraw() { + var sec = (new Date().getTime() - timeSinceLastFPS) / 1000; + framesSinceLastFPS++; + var fps = framesSinceLastFPS / sec; + + // recalculate FPS every half second for better accuracy. + if (sec > 0.5) { + timeSinceLastFPS = new Date().getTime(); + framesSinceLastFPS = 0; + p.__frameRate = fps; + } + + p.frameCount++; + + inDraw = true; + + if (p.use3DContext) { + // 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.camera(); + p.draw(); + } else { + saveContext(); + p.draw(); + restoreContext(); + } + + inDraw = false; + }; + + /** + * 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 noLoop() { + doLoop = false; + loopStarted = false; + clearInterval(looping); + }; + + /** + * 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 loop() { + if (loopStarted) { + return; + } + + timeSinceLastFPS = new Date().getTime(); + framesSinceLastFPS = 0; + + looping = window.setInterval(function() { + try { + p.redraw(); + } catch(e_loop) { + window.clearInterval(looping); + throw e_loop; + } + }, curMsPerFrame); + doLoop = true; + loopStarted = true; + }; + + /** + * 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 frameRate(aRate) { + curFrameRate = aRate; + curMsPerFrame = 1000 / curFrameRate; + + // clear and reset interval + if (doLoop) { + p.noLoop(); + p.loop(); + } + }; + + var eventHandlers = []; + + /** + * Quits/stops/exits the program. Programs without a draw() function exit automatically + * after the last line has run, but programs with draw() run continuously until the + * program is manually stopped or exit() is run. + * 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 exit() { + window.clearInterval(looping); + + removeInstance(p.externals.canvas.id); + + // 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); + } + } + } + + for (var i=0, ehl=eventHandlers.length; i 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 noCursor() { + 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 + // TODO: These functions are suppose to be called before any operations are called on the + // PGraphics object. They currently do nothing. + p.beginDraw = function beginDraw() {}; + p.endDraw = function endDraw() {}; + + // Imports an external Processing.js library + p.Import = function Import(lib) { + // Replace evil-eval method with a DOM